C++全栈聊天项目(22) 气泡聊天对话框

气泡聊天框设计

我们期待实现如下绿色的气泡对话框

https://cdn.llfc.club/1718417551126.jpg

对于我们自己发出的信息,我们可以实现这样一个网格布局管理

https://cdn.llfc.club/1718423760358.jpg

NameLabel用来显示用户的名字,Bubble用来显示聊天信息,Spacer是个弹簧,保证将NameLabel,IconLabel,Bubble等挤压到右侧。

如果是别人发出的消息,我们设置这样一个网格布局

https://cdn.llfc.club/1718497364660.jpg

下面是实现布局的核心代码

ChatItemBase::ChatItemBase(ChatRole role, QWidget *parent): QWidget(parent), m_role(role)
{m_pNameLabel    = new QLabel();m_pNameLabel->setObjectName("chat_user_name");QFont font("Microsoft YaHei");font.setPointSize(9);m_pNameLabel->setFont(font);m_pNameLabel->setFixedHeight(20);m_pIconLabel    = new QLabel();m_pIconLabel->setScaledContents(true);m_pIconLabel->setFixedSize(42, 42);m_pBubble       = new QWidget();QGridLayout *pGLayout = new QGridLayout();pGLayout->setVerticalSpacing(3);pGLayout->setHorizontalSpacing(3);pGLayout->setMargin(3);QSpacerItem*pSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);if(m_role == ChatRole::Self){m_pNameLabel->setContentsMargins(0,0,8,0);m_pNameLabel->setAlignment(Qt::AlignRight);pGLayout->addWidget(m_pNameLabel, 0,1, 1,1);pGLayout->addWidget(m_pIconLabel, 0, 2, 2,1, Qt::AlignTop);pGLayout->addItem(pSpacer, 1, 0, 1, 1);pGLayout->addWidget(m_pBubble, 1,1, 1,1);pGLayout->setColumnStretch(0, 2);pGLayout->setColumnStretch(1, 3);}else{m_pNameLabel->setContentsMargins(8,0,0,0);m_pNameLabel->setAlignment(Qt::AlignLeft);pGLayout->addWidget(m_pIconLabel, 0, 0, 2,1, Qt::AlignTop);pGLayout->addWidget(m_pNameLabel, 0,1, 1,1);pGLayout->addWidget(m_pBubble, 1,1, 1,1);pGLayout->addItem(pSpacer, 2, 2, 1, 1);pGLayout->setColumnStretch(1, 3);pGLayout->setColumnStretch(2, 2);}this->setLayout(pGLayout);
}

设置用户名和头像

void ChatItemBase::setUserName(const QString &name)
{m_pNameLabel->setText(name);
}void ChatItemBase::setUserIcon(const QPixmap &icon)
{m_pIconLabel->setPixmap(icon);
}

因为我们还要定制化实现气泡widget,所以要写个函数更新这个widget

void ChatItemBase::setWidget(QWidget *w)
{QGridLayout *pGLayout = (qobject_cast<QGridLayout *>)(this->layout());pGLayout->replaceWidget(m_pBubble, w);delete m_pBubble;m_pBubble = w;
}

聊天气泡

我们的消息分为几种,文件,文本,图片等。所以先实现BubbleFrame作为基类

class BubbleFrame : public QFrame
{Q_OBJECT
public:BubbleFrame(ChatRole role, QWidget *parent = nullptr);void setMargin(int margin);//inline int margin(){return margin;}void setWidget(QWidget *w);
protected:void paintEvent(QPaintEvent *e);
private:QHBoxLayout *m_pHLayout;ChatRole m_role;int      m_margin;
};

BubbleFrame基类构造函数创建一个布局,要根据是自己发送的消息还是别人发送的,做margin分布

const int WIDTH_SANJIAO  = 8;  //三角宽
BubbleFrame::BubbleFrame(ChatRole role, QWidget *parent):QFrame(parent),m_role(role),m_margin(3)
{m_pHLayout = new QHBoxLayout();if(m_role == ChatRole::Self)m_pHLayout->setContentsMargins(m_margin, m_margin, WIDTH_SANJIAO + m_margin, m_margin);elsem_pHLayout->setContentsMargins(WIDTH_SANJIAO + m_margin, m_margin, m_margin, m_margin);this->setLayout(m_pHLayout);
}

将气泡框内设置文本内容,或者图片内容,所以实现了下面的函数

void BubbleFrame::setWidget(QWidget *w)
{if(m_pHLayout->count() > 0)return ;else{m_pHLayout->addWidget(w);}
}

接下来绘制气泡

void BubbleFrame::paintEvent(QPaintEvent *e)
{QPainter painter(this);painter.setPen(Qt::NoPen);if(m_role == ChatRole::Other){//画气泡QColor bk_color(Qt::white);painter.setBrush(QBrush(bk_color));QRect bk_rect = QRect(WIDTH_SANJIAO, 0, this->width()-WIDTH_SANJIAO, this->height());painter.drawRoundedRect(bk_rect,5,5);//画小三角QPointF points[3] = {QPointF(bk_rect.x(), 12),QPointF(bk_rect.x(), 10+WIDTH_SANJIAO +2),QPointF(bk_rect.x()-WIDTH_SANJIAO, 10+WIDTH_SANJIAO-WIDTH_SANJIAO/2),};painter.drawPolygon(points, 3);}else{QColor bk_color(158,234,106);painter.setBrush(QBrush(bk_color));//画气泡QRect bk_rect = QRect(0, 0, this->width()-WIDTH_SANJIAO, this->height());painter.drawRoundedRect(bk_rect,5,5);//画三角QPointF points[3] = {QPointF(bk_rect.x()+bk_rect.width(), 12),QPointF(bk_rect.x()+bk_rect.width(), 12+WIDTH_SANJIAO +2),QPointF(bk_rect.x()+bk_rect.width()+WIDTH_SANJIAO, 10+WIDTH_SANJIAO-WIDTH_SANJIAO/2),};painter.drawPolygon(points, 3);}return QFrame::paintEvent(e);
}

绘制的过程很简单,先创建QPainter,然后设置NoPen,表示不绘制轮廓线,接下来用设置指定颜色的画刷绘制图形,我们先绘制矩形再绘制三角形。

对于文本消息的绘制

TextBubble::TextBubble(ChatRole role, const QString &text, QWidget *parent):BubbleFrame(role, parent)
{m_pTextEdit = new QTextEdit();m_pTextEdit->setReadOnly(true);m_pTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);m_pTextEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);m_pTextEdit->installEventFilter(this);QFont font("Microsoft YaHei");font.setPointSize(12);m_pTextEdit->setFont(font);setPlainText(text);setWidget(m_pTextEdit);initStyleSheet();
}

setPlainText设置文本最大宽度

void TextBubble::setPlainText(const QString &text)
{m_pTextEdit->setPlainText(text);//m_pTextEdit->setHtml(text);//找到段落中最大宽度qreal doc_margin = m_pTextEdit->document()->documentMargin();int margin_left = this->layout()->contentsMargins().left();int margin_right = this->layout()->contentsMargins().right();QFontMetricsF fm(m_pTextEdit->font());QTextDocument *doc = m_pTextEdit->document();int max_width = 0;//遍历每一段找到 最宽的那一段for (QTextBlock it = doc->begin(); it != doc->end(); it = it.next())    //字体总长{int txtW = int(fm.width(it.text()));max_width = max_width < txtW ? txtW : max_width;                 //找到最长的那段}//设置这个气泡的最大宽度 只需要设置一次setMaximumWidth(max_width + doc_margin * 2 + (margin_left + margin_right));        //设置最大宽度
}

我们拉伸的时候要调整气泡的高度,这里重写事件过滤器

bool TextBubble::eventFilter(QObject *o, QEvent *e)
{if(m_pTextEdit == o && e->type() == QEvent::Paint){adjustTextHeight(); //PaintEvent中设置}return BubbleFrame::eventFilter(o, e);
}

调整高度

void TextBubble::adjustTextHeight()
{qreal doc_margin = m_pTextEdit->document()->documentMargin();    //字体到边框的距离默认为4QTextDocument *doc = m_pTextEdit->document();qreal text_height = 0;//把每一段的高度相加=文本高for (QTextBlock it = doc->begin(); it != doc->end(); it = it.next()){QTextLayout *pLayout = it.layout();QRectF text_rect = pLayout->boundingRect();                             //这段的recttext_height += text_rect.height();}int vMargin = this->layout()->contentsMargins().top();//设置这个气泡需要的高度 文本高+文本边距+TextEdit边框到气泡边框的距离setFixedHeight(text_height + doc_margin *2 + vMargin*2 );
}

设置样式表

void TextBubble::initStyleSheet()
{m_pTextEdit->setStyleSheet("QTextEdit{background:transparent;border:none}");
}

对于图像的旗袍对话框类似,只是计算图像的宽高即可

#define PIC_MAX_WIDTH 160
#define PIC_MAX_HEIGHT 90PictureBubble::PictureBubble(const QPixmap &picture, ChatRole role, QWidget *parent):BubbleFrame(role, parent)
{QLabel *lb = new QLabel();lb->setScaledContents(true);QPixmap pix = picture.scaled(QSize(PIC_MAX_WIDTH, PIC_MAX_HEIGHT), Qt::KeepAspectRatio);lb->setPixmap(pix);this->setWidget(lb);int left_margin = this->layout()->contentsMargins().left();int right_margin = this->layout()->contentsMargins().right();int v_margin = this->layout()->contentsMargins().bottom();setFixedSize(pix.width()+left_margin + right_margin, pix.height() + v_margin *2);
}

发送测试

接下来在发送处实现文本和图片消息的展示,点击发送按钮根据不同的类型创建不同的气泡消息

void ChatPage::on_send_btn_clicked()
{auto pTextEdit = ui->chatEdit;ChatRole role = ChatRole::Self;QString userName = QStringLiteral("恋恋风辰");QString userIcon = ":/res/head_1.jpg";const QVector<MsgInfo>& msgList = pTextEdit->getMsgList();for(int i=0; i<msgList.size(); ++i){QString type = msgList[i].msgFlag;ChatItemBase *pChatItem = new ChatItemBase(role);pChatItem->setUserName(userName);pChatItem->setUserIcon(QPixmap(userIcon));QWidget *pBubble = nullptr;if(type == "text"){pBubble = new TextBubble(role, msgList[i].content);}else if(type == "image"){pBubble = new PictureBubble(QPixmap(msgList[i].content) , role);}else if(type == "file"){}if(pBubble != nullptr){pChatItem->setWidget(pBubble);ui->chat_data_list->appendChatItem(pChatItem);}}
}

效果展示

https://cdn.llfc.club/1718499438435.jpg

源码和视频

https://www.bilibili.com/video/BV1Mz4218783/?vd_source=8be9e83424c2ed2c9b2a3ed1d01385e9

源码链接

https://gitee.com/secondtonone1/llfcchat

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/28344.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java小结

# Java的特点 Java是一门面向对象的编程语言。面向对象和面向过程的区别参考下一个问题。 Java具有平台独立性和移植性。 Java有一句口号&#xff1a;Write once, run anywhere&#xff0c;一次编写、到处运行。这也是Java的魅力所在。而实现这种特性的正是Java虚拟机JVM。已…

Hvv--知攻善防应急响应靶机--Linux1

HW–应急响应靶机–Linux1 所有靶机均来自 知攻善防实验室 靶机整理&#xff1a; 夸克网盘&#xff1a;https://pan.quark.cn/s/4b6dffd0c51a#/list/share百度云盘&#xff1a;https://pan.baidu.com/s/1NnrS5asrS1Pw6LUbexewuA?pwdtxmy 官方WP&#xff1a;https://mp.weixin.…

Linux,shell ,gun基本概念和关系

Linux 系统简单架构图 1、命令行界面&#xff08;CLI&#xff09;和图形用户界面 (GUI) 1、图形界面就是我们常用的windows系统这种&#xff0c;打开文件&#xff0c;双击一下。想选择哪个文件&#xff0c;就鼠标移动到哪里选择就行。 2、命令行界面就是下面这种只有黑乎乎的…

代码随想录二刷DAY1~3

Day1 704 二分查找&#xff0c;简单 我也有自己写题解的能力了&#xff0c;而且思维很清晰&#xff1a; 找什么就在if里写什么。 class Solution {public: int search(vector<int>& nums, int target) { int l0,rnums.size()-1; while(l<r){ …

算法体系-21 第二十一 暴力递归到动态规划(三)

一 最长回文子串 1.1 描述 给定一个字符串str&#xff0c;返回这个字符串的最长回文子序列长度 比如 &#xff1a; str “a12b3c43def2ghi1kpm” 最长回文子序列是“1234321”或者“123c321”&#xff0c;返回长度7 1.2 分析 1.2.1 先将原传逆序&#xff0c;求原串和反转后的…

[C++] vector list 等容器的迭代器失效问题

标题&#xff1a;[C] 容器的迭代器失效问题 水墨不写bug 正文开始&#xff1a; 什么是迭代器&#xff1f; 迭代器是STL提供的六大组件之一&#xff0c;它允许我们访问容器&#xff08;如vector、list、set等&#xff09;中的元素&#xff0c;同时提供一个遍历容器的方法。然而…

2024上半年软考---江苏考区最先公布成绩

经历了考试之后&#xff0c;最期待的就是考试成绩的公布了&#xff0c;最好的成绩是45、45、45.只要过了分数线就满足了。下面我们来看看各大考区的分数的公布时间。 提前说下江苏考区的时间比较早&#xff0c;我就是江苏考区的&#xff0c;希望本次可以顺利通过考试。 2024年…

零基础到高手蜕变:一步到位Jupyter Notebook安装全攻略

前言 对于数据分析、机器学习、科学研究等领域的工作者来说&#xff0c;Jupyter Notebook 已经成为了一种不可或缺的工具。它的交互式编程界面&#xff0c;使得数据分析过程更加直观和高效。但并非所有人都熟悉如何安装和配置Jupyter Notebook&#xff0c;特别是在不同的操作系…

在typora中利用正则表达式,批量处理图片

一&#xff0c;png格式 在 Typora 中批量将 HTML 图片标签转换为简化的 Markdown 图片链接&#xff0c;且忽略 alt 和 style 属性&#xff0c;可以按照以下步骤操作&#xff1a; 打开 Typora 并加载你的文档。按下 Ctrl H&#xff08;在 Windows/Linux 上&#xff09;或 Cmd…

Unity C#调用Android,IOS震动功能

最近在Unity上需要很原生移动端进行交互&#xff0c; 原理&#xff1a;新建一个android项目&#xff0c;把生成的app module给干掉&#xff0c;然后留下一个vibrationPlugin module&#xff0c;在这个module下写android震动代码&#xff0c;将这个android工程构建出来的 aar移…

2024数据库期末综合解析(部分题)

目录 第4关&#xff1a;数据记录修改 任务描述 补充 答案&#xff1a; 第6关&#xff1a;数据查询二 任务描述 补充 答案&#xff1a; 第4关&#xff1a;数据记录修改 任务描述 湖南人口hnpeople数据表如下所示 各字段含义如下 cs&#xff08;城市)、qx(区县)、rk(人口)、man(男…

115.网络游戏逆向分析与漏洞攻防-邮件系统数据分析-调试优化结构体类型数据的创建

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果 现在的代码都是依据数据包来写的&#xff0c;如果看不懂代码&#xff0c;就说明没看懂数据包…

理解DDD设计

DDD的理解 领域驱动设计&#xff08;Domain-Driven Design&#xff0c;DDD&#xff09;是一种软件开发方法论&#xff0c;强调将业务领域作为软件设计的核心&#xff0c;以便更好地满足业务需求。DDD认为&#xff0c;软件开发的核心是理解业务&#xff0c;而不是实现技术。在D…

​晶体管高频等效电路

目录 混合Π等效电路 Y参数等效电路 混合Π与Y参数等效电路的转换 混合Π等效电路 共射三极管的等效电路。 Y参数等效电路 混合Π与Y参数等效电路的转换

异或运算的原理以及应用

异或&#xff08;XOR&#xff09;是计算机科学和数字电路中常用的运算之一。异或运算符通常用符号“⊕”或“^”表示&#xff0c;它有着简单而独特的性质&#xff0c;使其在数据加密、错误检测与纠正等多个领域得到了广泛的应用。在网络上我们传输的每一比特数据都经过了异或运…

unity 简易异步socket

1.unity 同步socket 改异步 using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Net.Sockets; using UnityEngine.UI; using System.Threading; using System;public class Echo : MonoBehaviour {//定义套接字Socket socket;//UG…

【C#】使用JavaScriptSerializer序列化对象

在C#开发语言编程中&#xff0c;通常使用系统内置的JavaScriptSerializer类来序列化对象&#xff0c;以便将其转换为JSON格式的文本存储与后台服务通信, 在这里将为大家详细介绍一下这个过程。 文章目录 反序列化序列化忽略属性 假设处理的数据中有一个对象类, 如下 public cl…

Linux系统脚本开机自启动,开机自启动jar包vue前台等

脚本内容jiaobenname.sh #!/bin/bash # 设置环境变量 export JAVA_HOME/usr/local/java/jdk-17.0.10 export CLASSPATH.:$JAVA_HOME/lib/ export PATH.:$JAVA_HOME/bin:$PATHwhile true; doif ps aux | grep -v grep | grep "tomcat" > /dev/null; thenecho &quo…

ppt添加圆角矩形,并调整圆角弧度方法

一、背景 我们看的论文&#xff0c;许多好看的图都是用PPT做的&#xff0c;下面介绍用ppt添加圆角矩形&#xff0c;并调整圆角弧度方法。 二、ppt添加圆角矩形&#xff0c;并调整圆角弧度 添加矩形&#xff1a; 在顶部工具栏中&#xff0c;点击“插入”选项卡。 在“插图”…

索引-定义、创建(CREATE INDEX)、删除(DROP INDEX)

一、概述 1、索引是SQL语言定义的一种数据对象&#xff0c;是大多数DBMS为数据库中基本表创建的一种辅助存取结构&#xff0c;用于响应特定查询条件进行查询时的查询速度&#xff0c;DBMS根据查询条件从数据库文件中&#xff0c;选择出一条或者多条数据记录以供检索&#xff0…