Qt自定义聊天消息控件ChatMessage:初步实现仿微信聊天界面
在视频会议或即时通讯软件中,一个美观且功能完善的聊天界面是必不可少的。本文将介绍如何使用Qt实现一个高度自定义的聊天消息控件ChatMessage,支持文本气泡、头像显示、多类型消息和自动换行等特性。
一、ChatMessage控件概述
ChatMessage是一个继承自QWidget的自定义控件,专门用于聊天场景中的消息展示。它可以显示四种类型的消息:
- 用户自己发送的消息(右对齐,蓝色气泡)
- 他人发送的消息(左对齐,白色气泡)
- 系统通知消息(居中,灰色文本)
- 时间戳消息(居中,浅灰色时间分隔符)
二、核心功能与实现原理
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);// 绘制发送者IPQPen 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")) {// 解析文件信息,显示文件图标和下载选项}
}