Qt自定义聊天消息控件ChatMessage:初步实现仿微信聊天界面

发布于:2025-09-01 ⋅ 阅读:(17) ⋅ 点赞:(0)

在视频会议或即时通讯软件中,一个美观且功能完善的聊天界面是必不可少的。本文将介绍如何使用Qt实现一个高度自定义的聊天消息控件ChatMessage,支持文本气泡、头像显示、多类型消息和自动换行等特性。

一、ChatMessage控件概述

ChatMessage是一个继承自QWidget的自定义控件,专门用于聊天场景中的消息展示。它可以显示四种类型的消息:

  1. 用户自己发送的消息(右对齐,蓝色气泡)
  2. 他人发送的消息(左对齐,白色气泡)
  3. 系统通知消息(居中,灰色文本)
  4. 时间戳消息(居中,浅灰色时间分隔符)

二、核心功能与实现原理

2.1 控件初始化

控件的构造函数负责初始化基本样式和资源:

ChatMessage::ChatMessage(QWidget *parent) : QWidget(parent)
{
    // 设置默认字体
    QFont te_font = this->font();
    te_font.setFamily("MicrosoftYaHei");
    te_font.setPointSize(12);
    this->setFont(te_font);
    
    // 加载头像资源
    m_leftPixmap = QPixmap(":/myImage/1.jpg");
    m_rightPixmap = QPixmap(":/myImage/1.jpg");
    
    // 初始化"发送中"动画
    m_loadingMovie = new QMovie(this);
    m_loadingMovie->setFileName(":/myImage/3.gif");
    m_loading = new QLabel(this);
    m_loading->setMovie(m_loadingMovie);
    m_loading->setScaledContents(true);
    m_loading->resize(40, 40);
    m_loading->setAttribute(Qt::WA_TranslucentBackground, true);
}

2.2 消息内容设置

setText方法是控件的核心接口,用于设置消息的各种属性:

void ChatMessage::setText(QString text, QString time, QSize allSize, 
                         QString ip, User_Type userType)
{
    m_msg = text;
    m_userType = userType;
    m_time = time;
    m_curTime = QDateTime::fromSecsSinceEpoch(time.toInt()).toString("ddd hh:mm");
    m_allSize = allSize;
    m_ip = ip;
    
    // 如果是自己发送的消息且未发送成功,显示加载动画
    if(userType == User_Me) {
        if(!m_isSending) {
            m_loading->move(m_kuangRightRect.x() - m_loading->width() - 10, 
                           m_kuangRightRect.y() + m_kuangRightRect.height()/2 - 
                           m_loading->height()/2);
            m_loading->show();
            m_loadingMovie->start();
        }
    } else {
        m_loading->hide();
    }
    
    this->update(); // 触发重绘
}

2.3 自动换行处理

getRealString方法负责计算文本的实际显示尺寸并处理自动换行:

QSize ChatMessage::getRealString(QString src)
{
    QFontMetricsF fm(this->font());
    m_lineHeight = fm.lineSpacing();
    int nCount = src.count("\n");
    int nMaxWidth = 0;
    
    if(nCount == 0) {
        nMaxWidth = fm.horizontalAdvance(src);
        QString value = src;
        
        if(nMaxWidth > m_textWidth) {
            nMaxWidth = m_textWidth;
            int size = m_textWidth / fm.horizontalAdvance(" ");
            int num = fm.horizontalAdvance(value) / m_textWidth;
            nCount += num;
            
            QString temp = "";
            for(int i = 0; i < num; i++) {
                temp += value.mid(i * size, (i + 1) * size) + "\n";
            }
            src.replace(value, temp);
        }
    } else {
        // 处理已包含换行符的文本
        for(int i = 0; i < (nCount + 1); i++) {
            QString value = src.split("\n").at(i);
            nMaxWidth = fm.horizontalAdvance(value) > nMaxWidth ? 
                        fm.horizontalAdvance(value) : nMaxWidth;
            
            if(nMaxWidth > m_textWidth) {
                // 自动换行逻辑
                nMaxWidth = m_textWidth;
                int size = m_textWidth / fm.horizontalAdvance(" ");
                int num = fm.horizontalAdvance(value) / m_textWidth;
                nCount += num;
                
                QString temp = "";
                for(int i = 0; i < num; i++) {
                    temp += value.mid(i * size, (i + 1) * size) + "\n";
                }
                src.replace(value, temp);
            }
        }
    }
    
    return QSize(nMaxWidth + m_spaceWid, 
                (nCount + 1) * m_lineHeight + 2 * m_lineHeight);
}

2.4 消息绘制

paintEvent方法是控件的绘制核心,根据消息类型绘制不同的UI样式:

void ChatMessage::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    QPainter painter(this);
    painter.setRenderHints(QPainter::Antialiasing | 
                          QPainter::SmoothPixmapTransform);
    painter.setPen(Qt::NoPen);
    painter.setBrush(QBrush(Qt::gray));
    
    if(m_userType == User_Type::User_She) {
        // 绘制他人消息:左对齐,白色气泡
        painter.drawPixmap(m_iconLeftRect, m_leftPixmap);
        
        // 绘制气泡边框
        QColor col_KuangB(234, 234, 234);
        painter.setBrush(QBrush(col_KuangB));
        painter.drawRoundedRect(m_kuangLeftRect.x() - 1, 
                               m_kuangLeftRect.y() - 1 + 10,
                               m_kuangLeftRect.width() + 2,
                               m_kuangLeftRect.height() + 2, 4, 4);
        
        // 绘制气泡
        QColor col_Kuang(255, 255, 255);
        painter.setBrush(QBrush(col_Kuang));
        painter.drawRoundedRect(m_kuangLeftRect, 4, 4);
        
        // 绘制气泡三角
        QPointF points[3] = {
            QPointF(m_sanjiaoLeftRect.x(), 40),
            QPointF(m_sanjiaoLeftRect.x() + m_sanjiaoLeftRect.width(), 35),
            QPointF(m_sanjiaoLeftRect.x() + m_sanjiaoLeftRect.width(), 45)
        };
        QPen pen;
        pen.setColor(col_Kuang);
        painter.setPen(pen);
        painter.drawPolygon(points, 3);
        
        // 绘制发送者IP
        QPen penIp;
        penIp.setColor(Qt::darkGray);
        painter.setPen(penIp);
        QFont f = this->font();
        f.setPointSize(10);
        painter.setFont(f);
        painter.drawText(m_ipLeftRect, m_ip, 
                        Qt::AlignHCenter | Qt::AlignVCenter);
        
        // 绘制消息内容
        QPen penText;
        penText.setColor(QColor(51, 51, 51));
        painter.setPen(penText);
        QTextOption option(Qt::AlignLeft | Qt::AlignVCenter);
        option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
        painter.setFont(this->font());
        painter.drawText(m_textLeftRect, m_msg, option);
        
    } else if(m_userType == User_Type::User_Me) {
        // 绘制自己消息:右对齐,蓝色气泡
        // 实现逻辑类似上面,位置和颜色不同
    } else if(m_userType == User_Type::User_Time) {
        // 绘制时间戳消息
        QPen penText;
        penText.setColor(QColor(153, 153, 153));
        painter.setPen(penText);
        QTextOption option(Qt::AlignCenter);
        option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
        QFont te_font = this->font();
        te_font.setFamily("MicrosoftYaHei");
        te_font.setPointSize(10);
        painter.setFont(te_font);
        painter.drawText(this->rect(), m_curTime, option);
    }
}

三、使用示例

3.1 基本使用方法

// 创建消息列表容器
QListWidget* msgList = new QListWidget(this);
msgList->setGeometry(10, 10, 500, 400);

// 创建自己发送的消息
ChatMessage* myMsg = new ChatMessage(msgList);
myMsg->setText("你好,这是我发送的消息",
              QString::number(QDateTime::currentSecsSinceEpoch()),
              QSize(msgList->width(), 0),
              "192.168.1.100", 
              ChatMessage::User_Me);

// 创建他人发送的消息
ChatMessage* otherMsg = new ChatMessage(msgList);
otherMsg->setText("收到,这是我的回复",
                 QString::number(QDateTime::currentSecsSinceEpoch()),
                 QSize(msgList->width(), 0),
                 "192.168.1.101",
                 ChatMessage::User_She);

// 添加到消息列表
QListWidgetItem* item1 = new QListWidgetItem(msgList);
item1->setSizeHint(myMsg->fontRect(myMsg->text()));
msgList->setItemWidget(item1, myMsg);

QListWidgetItem* item2 = new QListWidgetItem(msgList);
item2->setSizeHint(otherMsg->fontRect(otherMsg->text()));
msgList->setItemWidget(item2, otherMsg);

// 消息发送成功后更新状态
myMsg->setTextSuccess();

3.2 高级功能:@提及和文件选择

在实际聊天应用中,经常需要支持特殊功能如@提及他人或文件选择。可以通过扩展ChatMessage类来实现:

// 扩展ChatMessage类,增加特殊内容处理
void ChatMessage::handleSpecialContent(QString text)
{
    // 处理@提及
    QRegularExpression atPattern("@([^\\s@]+)");
    QRegularExpressionMatchIterator it = atPattern.globalMatch(text);
    while (it.hasNext()) {
        QRegularExpressionMatch match = it.next();
        QString username = match.captured(1);
        // 高亮显示@提及,并添加点击事件
    }
    
    // 处理文件选择
    if (text.startsWith("/file")) {
        // 解析文件信息,显示文件图标和下载选项
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到