当前位置: 首页 > news >正文

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

在视频会议或即时通讯软件中,一个美观且功能完善的聊天界面是必不可少的。本文将介绍如何使用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);// 绘制发送者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")) {// 解析文件信息,显示文件图标和下载选项}
}
http://www.dtcms.com/a/354542.html

相关文章:

  • Python 数据分析学习笔记:Pandas 逻辑运算
  • 97、23种设计模式之桥接模式(6/23)
  • 鸿蒙Harmony-从零开始构建类似于安卓GreenDao的ORM数据库(四)
  • attention is all u need
  • npm install --global @dcloudio/uni-cli 时安装失败
  • 【lucene】如何评测一款分析器Analyzer
  • CP1-1-用户管理MyUser
  • jQuery 从入门到实践:基础语法、事件与元素操作全解析
  • 通过vs code配置spring boot+maven项目
  • vxetable数据导出
  • GaussDB 数据库架构师修炼(十八) SQL执行引擎-概述
  • 【爬虫】通过模拟鼠标点击和键盘操作抓取网页数据
  • 算法 --- 二分
  • 【深度学习新浪潮】显著性检测最新研究进展(2022-2025)
  • LeetCode 刷题【55. 跳跃游戏】
  • 用 PyTorch 搭建 CNN 实现 MNIST 手写数字识别
  • 如何开发线下陪玩儿小程序
  • 【图像处理基石】DCT在图像处理中的应用及实现
  • natapp 内网穿透
  • 【iOS】Masnory自动布局的简单学习
  • 图算法详解:最短路径、拓扑排序与关键路径
  • 使用 httpsok 工具全面排查网站安全配置
  • Nginx + Certbot配置 HTTPS / SSL 证书(简化版已测试)
  • Android稳定性问题的常见原因是什么
  • JSP程序设计之JSP指令
  • react+vite+ts 组件模板
  • CVPR2025丨VL2Lite:如何将巨型VLM的“知识”精炼后灌入轻量网络?这项蒸馏技术实现了任务专用的极致压缩
  • 传统星型拓扑结构的5G,WiFi无线通信网络与替代拓扑结构自组网
  • BGP路由协议(一):基本概念
  • UE的SimpleUDPTCPSocket插件使用