You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Qt中打造现代风格的消息列表?

问题解答

1. QListView 是否适合实现聊天消息列表?

适合。QListView 基于 Qt 的 Model-View 架构,自带 item 复用机制,在消息数量较多时能保持高效的渲染性能,不会因消息堆积导致界面卡顿。唯一需要注意的是,默认的 QListView item 无法直接实现左右对齐、圆角边框这类定制化样式,需要配合自定义 Item Delegate完成界面绘制。

2. 实现该效果的最佳方案

推荐使用 QListView + 自定义 QStyledItemDelegate 的组合,这是 Qt 原生方案中兼顾性能和定制性的最优选择,完全适配现代聊天软件的需求。以下是具体实现步骤:

步骤1:定义消息数据模型

创建继承自 QAbstractListModel 的模型,存储每条消息的核心数据(内容、发送方标识、时间等):

#include <QAbstractListModel>
#include <QString>
#include <QDateTime>

struct ChatMessage {
    QString content;
    bool isSelf; // true为自己发送的消息,false为对方发送的消息
    QDateTime timestamp;
};

class ChatModel : public QAbstractListModel
{
    Q_OBJECT
public:
    explicit ChatModel(QObject *parent = nullptr) : QAbstractListModel(parent) {}

    enum Roles {
        ContentRole = Qt::UserRole + 1,
        IsSelfRole,
        TimestampRole
    };

    int rowCount(const QModelIndex &parent = QModelIndex()) const override {
        Q_UNUSED(parent)
        return m_messages.size();
    }

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
        if (!index.isValid() || index.row() >= m_messages.size())
            return QVariant();

        const ChatMessage &msg = m_messages[index.row()];
        switch (role) {
        case ContentRole:
            return msg.content;
        case IsSelfRole:
            return msg.isSelf;
        case TimestampRole:
            return msg.timestamp;
        default:
            return QVariant();
        }
    }

    void addMessage(const ChatMessage &msg) {
        beginInsertRows(QModelIndex(), m_messages.size(), m_messages.size());
        m_messages.append(msg);
        endInsertRows();
    }

private:
    QList<ChatMessage> m_messages;
};

步骤2:自定义 Item Delegate

继承 QStyledItemDelegate,重写 paint() 方法绘制定制化消息样式,重写 sizeHint() 设置消息行高:

#include <QStyledItemDelegate>
#include <QPainter>
#include <QTextDocument>

class ChatDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    explicit ChatDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
        painter->save();
        painter->setRenderHint(QPainter::Antialiasing);

        // 获取消息数据
        QString content = index.data(ChatModel::ContentRole).toString();
        bool isSelf = index.data(ChatModel::IsSelfRole).toBool();

        // 限制消息框最大宽度为ListView的70%
        int maxWidth = option.rect.width() * 0.7;
        // 计算文本所需尺寸
        QTextDocument doc;
        doc.setPlainText(content);
        doc.setTextWidth(maxWidth);
        QSize textSize = doc.size().toSize();

        // 样式参数
        const int padding = 12;
        const int borderRadius = 18;
        QRect msgRect;

        if (isSelf) {
            // 自己的消息:右对齐,浅蓝色背景
            msgRect.setX(option.rect.right() - textSize.width() - padding * 2);
            msgRect.setY(option.rect.top() + padding);
            msgRect.setWidth(textSize.width() + padding * 2);
            msgRect.setHeight(textSize.height() + padding * 2);
            painter->setBrush(QColor(165, 214, 255));
            painter->setPen(Qt::NoPen);
            painter->drawRoundedRect(msgRect, borderRadius, borderRadius);
            painter->setPen(Qt::white);
        } else {
            // 对方的消息:左对齐,灰色背景
            msgRect.setX(option.rect.left() + padding);
            msgRect.setY(option.rect.top() + padding);
            msgRect.setWidth(textSize.width() + padding * 2);
            msgRect.setHeight(textSize.height() + padding * 2);
            painter->setBrush(QColor(230, 230, 230));
            painter->setPen(Qt::NoPen);
            painter->drawRoundedRect(msgRect, borderRadius, borderRadius);
            painter->setPen(Qt::black);
        }

        // 绘制消息文本
        painter->drawText(msgRect.adjusted(padding, padding, -padding, -padding), 
                          Qt::AlignVCenter | Qt::AlignLeft, content);

        painter->restore();
    }

    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override {
        QString content = index.data(ChatModel::ContentRole).toString();
        int maxWidth = option.rect.width() * 0.7;
        QTextDocument doc;
        doc.setPlainText(content);
        doc.setTextWidth(maxWidth);
        QSize textSize = doc.size().toSize();
        // 加上上下内边距
        return QSize(option.rect.width(), textSize.height() + 24);
    }
};

步骤3:整合到界面

在主窗口中初始化组件并关联:

#include <QListView>
#include "ChatModel.h"
#include "ChatDelegate.h"

// 初始化代码
QListView *chatView = new QListView(this);
ChatModel *chatModel = new ChatModel(this);
ChatDelegate *chatDelegate = new ChatDelegate(this);

chatView->setModel(chatModel);
chatView->setItemDelegate(chatDelegate);
chatView->setSpacing(8); // 设置消息间距
chatView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); // 平滑滚动
chatView->setStyleSheet("QListView { background-color: white; border: none; }");

// 添加测试消息
chatModel->addMessage({"Hello", false, QDateTime::currentDateTime()});
chatModel->addMessage({"Hello friend!", true, QDateTime::currentDateTime()});

备选方案(适合小消息量场景)

如果应用消息量不大,也可以用 QWidget 作为消息项,通过 QVBoxLayout 动态添加到滚动区域。但每个消息都是独立的 QWidget,消息过多时会占用大量内存导致卡顿,因此不推荐用于大型聊天场景。

内容的提问来源于stack exchange,提问作者user18812922

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 00:03:30