C++ Qt6 Создание чатов в мессенджере

Всем добрый день, форумчане?

Стоит задача сделать сделать чаты в корпоративном мессенджере. Не понимаю как лучше за это взяться средствами Qt, чтобы потом не попасть в тупик.

В чатах должны поддерживаться

  • текстовые сообщения
  • голосовые
  • изображения
  • документы, контакты и т.д.

Библиотеку для связи с сервером я уже проверил и подключил в проект.


Пока такие идеи для окна самого чата:

  • QListWidget – можно запихнуть виджеты, но это тяжеловесно
  • QListView – наш вариант, но проблема со стилизацией
  • QScrollArea и класть виджеты в layout.

Но если использовать QListWidget то я не смогу делать fetch из локальной БД, ведь мне надо кэшировать сообщения - к примеру держать в памяти не более 100 элементов списка.

QListView хороший вариант, но ведь сами элементы списка (блок сообщения, блок голосового и т.д.) мне надо будет рисовать через QPainter в методе paint(), а есть требование использовать qss стили из файла...

Для QScrollArea непонятно как отмерять номера текущих строк. В общем прописывать заново логику списка.


Ответы (1 шт):

Автор решения: Alexander Chernin

Самый лучший вариант это связка QListView + QStyledItemDelegate + [кастомный виджет/ы для отображения элементов чата].

Под кастомным виджетом я понимаю что есть виджет-вопрос, есть виджет-ответ, каждый из которых может иметь собственный набор функций и внешний вид.

Здесь я приведу простой пример того, как использовать простой кастомный виджет, передавать в него строку, отрисовывать его в наследном классе QStyledItemDelegate и загружать в него стили qss из файла.

Допустим у нас где-то создается и инициализируется QListView таким образом:

// Вертикальный список
ui->listView->setFlow(QListView::TopToBottom);

// Здесь используется стандартная модель со строками
// Но, безусловно, можно использовать какую угодно модель данных
ui->listView->setModel(
    new QStringListModel({"Item 1", "Item 2", "Item 3", ...})
);

// Загружаем свой делегат отображения данных
ui->listView->setItemDelegate(new MyItemDelegate());

MyItemDelegate.hpp:

class MyItemDelegate: public QStyledItemDelegate {
public:
    // Отрисовка нашего кастомного элемента чата
    void paint(
        QPainter *painter, const QStyleOptionViewItem &option,
            const QModelIndex &index) const override;

    // Размер строки чата
    QSize sizeHint(
        const QStyleOptionViewItem &option, const QModelIndex &index) const;
}

MyItemDelegate.cpp:

void MyItemDelegate::paint(
    QPainter *painter, const QStyleOptionViewItem &option,
        const QModelIndex &index) const
{
    // Строка сообщения
    const QString& value = index.data().toString();

    // Виджет элемента чата
    ChatItemWidget chatItem;
    // инициализируем его текстом
    chatItem.setText(value);
    // Задаем размеры
    chatItem.setGeometry(option.rect);

    // Теперь надо этот виджет отрисовать в ячейке списка
    // Задаем размер буфера рендера виджета на холсте
    QPixmap pixmap(chatItem.size());

    // Рендерим виджет в буфер
    chatItem.render(&pixmap);

    // Отрисовываем буфер на холсте
    painter->drawPixmap(option.rect.topLeft(), pixmap);
}

// Метод возвращает рекомендуемый размер строки списка
QSize MyItemDelegate::sizeHint(
    const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    return QSize(option.rect.width(), 80);
}

Из данного примера видно, что конкретная реализация ChatItemWidget может быть какой угодно, состоящей из различных графических элементов/виджетов, наша реализация делегата отрисует его таким, какой он есть.

В моем примере ChatItemWidget выглядит так:

// hpp
class ChatItemWidget : public QWidget
{
    Q_OBJECT

public:
    explicit ChatItemWidget (QWidget *parent = nullptr);
    ~ChatItemWidget ();
    void setText(const QString& value);
private:
    Ui::Item *ui;
};
// cpp
ChatItemWidget::ChatItemWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Item)
{
    ui->setupUi(this);

    // Загружаем файл со стилями
    QFile styleFile("Путь/к/файлу/со/стилями/style.qss");
    if (styleFile.open(QFile::ReadOnly | QFile::Text)) {
        QString styleSheet = styleFile.readAll();
        this->setStyleSheet(styleSheet);
        styleFile.close();
    }

}

void ChatItemWidget::setText(const QString &value)
{
    ui->label->setText(value);
}

Содержимое файла style.qss выглядит так:

QLabel {
    border: 2px solid green;
    border-radius: 4px;
    padding: 2px;
}

введите сюда описание изображения

Теперь я немного поменял дизайн ChatItemWidget на такой:

введите сюда описание изображения

Чуть изменил qss:

QLabel {
    border: 2px solid green;
    border-radius: 10px;
    padding: 2px;
    background-color: white;
}

Больше ничего не трогал и получил следующий результат:

введите сюда описание изображения

→ Ссылка