Qt 使用QListView实现简约美观的聊天窗口

今天和大家分享一个使用QListView来展现聊天窗口的历史记录的例子, 因为聊天记录可能会有很多, 所以使用试图-模型的方式更加合理
这是最终效果:
在这里插入图片描述

ChatHistoryModel继承自QAbstractListModel ,
ChatHistoryViewDelegate继承自QStyledItemDelegate,
这个例子最关键的就是在QStyledItemDelegate的sizeHint函数中对每一条消息所需的高度进行计算,其他都很简单
一共五个文件,包含一个UI文件,可以直接编译运行

//Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "ChatView.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{Q_OBJECT
public:explicit Widget(QWidget *parent = nullptr);~Widget();
private:Ui::Widget *ui;ChatHistoryModel * mModel;ChatHistoryViewDelegate * mDelegate;public slots:void onAppendClicked();
private:void drawIcon();
};#endif // WIDGET_H
//Widget.cpp
#include "Widget.h"
#include "ui_Widget.h"
#include <QApplication>
#include <QPixmap>
#include <QPainter>
int main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;w.show();return a.exec();
}
Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);setWindowTitle(" ");drawIcon();mModel = new ChatHistoryModel;ui->listView->setModel(mModel);mDelegate = new ChatHistoryViewDelegate(ui->listView);ui->listView->setItemDelegate(mDelegate);connect(ui->lineEdit,&QLineEdit::returnPressed,this,&Widget::onAppendClicked);resize(600,400);
}
void Widget::onAppendClicked(){auto msg = ui->lineEdit->text().trimmed();if(ui->radioButtonRecv->isChecked()) msg = "r" + msg;else msg = "s" + msg;ui->lineEdit->clear();mModel->append(msg);ui->listView->scrollToBottom();
}
void Widget::drawIcon(){static const int LEN = 40;QPixmap pix(LEN,LEN);pix.fill(QColor("transparent"));QPainter painter(&pix);painter.setRenderHint(QPainter::Antialiasing);painter.setPen(Qt::NoPen);painter.setBrush(QBrush("lime"));QPainterPath pp;pp.addEllipse(QPointF(0,0),LEN/2,LEN/2);pp.addEllipse(QPointF(0,0),LEN/2-6,LEN/2-6);painter.translate(LEN/2,LEN/2);painter.drawPath(pp);setWindowIcon(QIcon(pix));
}Widget::~Widget()
{delete ui;
}
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"><class>Widget</class><widget class="QWidget" name="Widget"><property name="geometry"><rect><x>0</x><y>0</y><width>668</width><height>486</height></rect></property><property name="windowTitle"><string>Widget</string></property><layout class="QVBoxLayout" name="verticalLayout"><property name="spacing"><number>0</number></property><property name="leftMargin"><number>0</number></property><property name="topMargin"><number>0</number></property><property name="rightMargin"><number>0</number></property><property name="bottomMargin"><number>0</number></property><item><widget class="QListView" name="listView"><property name="styleSheet"><string notr="true">border:none;</string></property><property name="verticalScrollBarPolicy"><enum>Qt::ScrollBarAlwaysOff</enum></property><property name="horizontalScrollBarPolicy"><enum>Qt::ScrollBarAlwaysOff</enum></property></widget></item><item><layout class="QHBoxLayout" name="horizontalLayout_2"><property name="spacing"><number>0</number></property><item><widget class="QGroupBox" name="groupBox"><property name="styleSheet"><string notr="true">border:none;</string></property><property name="title"><string/></property><layout class="QHBoxLayout" name="horizontalLayout"><property name="spacing"><number>0</number></property><property name="leftMargin"><number>0</number></property><property name="topMargin"><number>0</number></property><property name="rightMargin"><number>20</number></property><property name="bottomMargin"><number>0</number></property><item><widget class="QRadioButton" name="radioButtonRecv"><property name="text"><string>接收</string></property></widget></item><item><widget class="QRadioButton" name="radioButtonSend"><property name="text"><string>发送</string></property><property name="checked"><bool>true</bool></property></widget></item></layout></widget></item><item><widget class="QLineEdit" name="lineEdit"><property name="minimumSize"><size><width>0</width><height>32</height></size></property><property name="styleSheet"><string notr="true">border:none;background:transparent</string></property><property name="text"><string>我觉得你好多了</string></property><property name="placeholderText"><string>输入信息内容</string></property></widget></item></layout></item></layout></widget><layoutdefault spacing="6" margin="11"/><resources/><connections/>
</ui>
//ChatView.h
#ifndef CHATVIEW_H
#define CHATVIEW_H
#include <QDebug>
#include <QAbstractListModel>
#include <QStyledItemDelegate>
#include <QListView>
class ChatHistoryModel:public QAbstractListModel
{Q_OBJECT
public:ChatHistoryModel();QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override ;Qt::ItemFlags flags(const QModelIndex &index) const;int rowCount(const QModelIndex &parent = QModelIndex()) const override ;void append(const QString str);
private:QStringList mMsgList;
};class ChatHistoryViewDelegate:public QStyledItemDelegate{Q_OBJECT
public:explicit ChatHistoryViewDelegate(QListView* parent);QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;void setFont(const QFont& font);void setTextLeftGap(int gap);
private:QFont mFont;QListView * mListView;int mTextGap;//文本左右边距,右边距和左边距是一样的};#endif // CHATVIEW_H
//ChatView.cpp
#include "ChatView.h"
#include <QMouseEvent>
#include <QListView>
#include <QEvent>
#include <QLineEdit>
#include <QPainter>ChatHistoryModel::ChatHistoryModel(){mMsgList<< "raaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" <<"s你在说什么?" <<"ra" <<"s你没事吧?" <<"r有事" <<"sDude,快去看医生吧"<<"r正在看"<< "s医生怎么说?" <<"r啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊";
}
void ChatHistoryModel::append(const QString str){if(str.length() < 2) return;beginInsertRows(QModelIndex(),rowCount(),rowCount());mMsgList.push_back(str);endInsertRows();
}
Qt::ItemFlags ChatHistoryModel::flags(const QModelIndex &index) const{return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
QVariant ChatHistoryModel::data(const QModelIndex &index, int role) const {if(!index.isValid() || index.row() <0 || index.row() >= mMsgList.size()){return QVariant();}if(role == Qt::DisplayRole){const auto& str = mMsgList[index.row()];if(str.length() > 1) return str.mid(1);else return "invalid message";}else if(role == Qt::UserRole){const auto& str = mMsgList[index.row()];if(str.length() > 0) return str[0];return 's';}return QVariant();
}int ChatHistoryModel::rowCount(const QModelIndex &parent  ) const   {Q_UNUSED(parent)return mMsgList.size();
}
ChatHistoryViewDelegate::ChatHistoryViewDelegate(QListView* parent):mListView(parent),mTextGap(16){Q_ASSERT(parent!=nullptr);mFont = QFont("Microsoft YaHei",12,2);
}
void ChatHistoryViewDelegate::setFont(const QFont& font){if(mFont != font){mFont = font;mListView->update();}
}
void ChatHistoryViewDelegate::setTextLeftGap(int gap){if(mTextGap/2 != gap){mTextGap = gap*2;mListView->update();}
}
QSize ChatHistoryViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {QString str = index.data(Qt::DisplayRole).toString();if(str.length() <= 0) return QSize(0,0);QFontMetrics fm(mFont);//在这里,我要给的宽度一定是可以绘画的总宽度,他要尽可能大,这样在paint中才有更多空间来进行间距调整,左右对齐的操作qreal w = mListView->width();if(w <= 0) w = 200; //这个分支只有刚创建实例的时候才会发生,而且很快会被覆盖掉const qreal txth = fm.height();const qreal vGap = 16;//上下两头的间距,这并不是精确的间距,因为在paint函数中,还要扣除一点点来显示不同行之间的间距qreal txtw = fm.horizontalAdvance(str);int times = txtw / (w*0.8-mTextGap) + 1;//总宽度的0.8是一条消息的最大长度,减去边距才是每行有效长度qreal h = txth * times + vGap;return QSize(w,h);
}void ChatHistoryViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {const QString str = index.data().toString();if(str.length() <= 0) return;painter->save();painter->setRenderHint(QPainter::Antialiasing);painter->setFont(mFont);QFontMetrics fm(mFont);qreal txtw = fm.horizontalAdvance(str);//总的文本有效长度qreal maxw = mListView->width() * 0.8 - mTextGap;//最大文本宽度if(txtw > maxw) txtw = maxw;    //如果有效长度比这个最大长度大,说明换行了QRect rct = option.rect;if(index.data(Qt::UserRole) == 's'){//发送的消息右对齐rct.setLeft(rct.width() - txtw-mTextGap);painter->setBrush(QBrush("lightblue"));}else{//接收的消息左对齐rct.setRight(txtw+mTextGap);painter->setBrush(QBrush("lightgreen"));}rct = rct.adjusted(0,2,0,0);//下面扣除2像素来分隔不同的行painter->setPen(Qt::NoPen);painter->drawRoundedRect(rct,8,8);rct = rct.adjusted(mTextGap/2,0,-mTextGap/2,0);//左右扣除2像素来表示水平文本边距,painter->setBrush(Qt::NoBrush);painter->setPen("black");painter->drawText(rct,Qt::AlignVCenter | Qt::AlignLeft  | Qt::TextWordWrap | Qt::TextWrapAnywhere,str);painter->restore();
}

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

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

相关文章

【Java JVM】对象回收判断

Java 对象回收判断是程序设计中至关重要的一环。在面向对象的编程中, 合理的对象回收策略直接影响着程序的性能和内存利用效率。 因此, 深入了解和准确判断 Java 对象的回收时机, 不仅可以优化程序的运行性能, 还能有效避免内存泄漏和资源浪费。 本文将简单的分析一下 JVM 中对…

JRT缓存协议测试

JRT由DolerGet提供可信的缓存数据获取&#xff0c;在OMR修改和删除数据后会更新缓存的数据&#xff0c;这样的话本Web下通过DolerGet取的数据是可信的。在多个Web之间要保证缓存数据的可信度&#xff0c;需要同步修改的数据到其他Web&#xff0c;为此仿照了缓存协议的效果&…

MySQL的三大范式

文章目录 简介第一范式第二范式第三范式&#xff1a; 简介 在MySQL的使用中&#xff0c; 要根据实际灵活设计表&#xff0c;一般来说我们通常遵循三大范式&#xff08;啥是范式&#xff1a;是一些约束、规范、规则&#xff0c; 来优化数据库表的设计和存储&#xff09;,三大范…

Stable Diffusion 模型分享:3D Animation Diffusion(3D动漫)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八 下载地址 模型介绍 3D Animation Diffusion 是 Lykon 大神的 3D 动漫模型。 作者述&#xff1a;在迪士尼、皮…

停止Tomcat服务的方式

运行脚本文件停止 运行Tomcat的bin目录中提供的停止服务的脚本文件 关闭命令 # sh方式 sh shutdown.sh# ./方式 ./shutdown.sh操作步骤 运行结束进程停止 查看Tomcat进程&#xff0c;获得进程id kill进程命令 # 执行命令结束进程 kill -9 65358 操作步骤 注意 kill命令是…

ffmpeg使用vaapi解码后的视频如何基于x11或EGL实现0-copy渲染?

技术背景 对于ffmpeg硬解码后渲染常见的做法是解码后通过av_hwframe_transfer_data方法将数据从GPU拷贝到CPU&#xff0c;然后做一些转换处理用opengl渲染&#xff0c;必然涉及到譬如类似glTexImage2D的函数将数据上传到GPU。而这样2次copy就会导致CPU的使用率变高&#xff0c…

智能物联时代下RFID技术在汽车零部件智能制造中的引领作用

RFID&#xff08;Radio Frequency Identification&#xff0c;射频识别&#xff09;技术在汽车零部件加工中有广泛的应用&#xff0c;其工作原理是通过无线电频率进行自动识别。在汽车零部件加工中&#xff0c;RFID技术可以发挥重要作用&#xff0c;提高生产效率、降低成本和减…

js形参传递特殊字符

在前端我们给其他页面传值或者传数据到后台的时候&#xff0c;字符串经常将一些特殊符号识别成字符集。这种情况下会将数据打断或者打乱&#xff0c;比如字符串里面包含*/&这些符号的时候就会错误。 我们可以通过将字符中的特殊字符替换成十六进制的字符&#xff0c;一些特…

【Linux从青铜到王者】进程信号

——————————————————————————————————————————— 信号入门 在了解信号之前有许多要理解的相关概念 我们可以先通过一个生活例子来初步认识一下信号 1.生活角度的信号 你在网上买了很多件商品&#xff0c;再等待不同商品快递的到来…

从事测绘地信,你需要这些插件、软件、小工具、图源...

特别声明&#xff0c;本篇是来自公众号GIS前沿的资源&#xff0c;看着比较好&#xff0c;特别给大家推荐。加粗样式 今天&#xff0c;我们又来汇总了一些工作中实用的插件、小工具、数据等等&#xff0c;小助手又来帮你提高工作效率了****。 因为小助手每年都会总结一次&…

15.Django总结

文章目录 1.Django创建项目的命令2.MVC,MVT的理解3.Django中间件的使用4.WSGI,uWSGI服务器 和 uwsgi协议5.nginx和uWISG 服务器之间如何配合工作的6.django开发中数据库做过什么优化7.Python中三大框架各自的应用场景8.django如何提升性能(高并发)9. 什么是restful api谈谈你的…

MySQL性能优化-数据库调优有哪些维度可以选择

数据库调优的目标 简单来说&#xff0c;数据库调优的目的就是要让数据库运行得更快&#xff0c;也就是说响应的时间更快&#xff0c;吞吐量更大。 不过随着用户量的不断增加&#xff0c;以及应用程序复杂度的提升&#xff0c;我们很难用“更快”去定义数据库调优的目标&#…

Stable Diffusion ———LDM、SD 1.0, 1.5, 2.0、SDXL、SDXL-Turbo等版本之间关系现原理详解

前言 2021年5月&#xff0c;OpenAI发表了《扩散模型超越GANs》的文章&#xff0c;标志着扩散模型&#xff08;Diffusion Models&#xff0c;DM&#xff09;在图像生成领域开始超越传统的GAN模型&#xff0c;进一步推动了DM的应用。 然而&#xff0c;早期的DM直接作用于像素空…

cmd模式下启动mysql

1.打开cmd输入services.msc&#xff0c;找到MYSQL&#xff0c;右击属性&#xff0c;找到可执行文件路径&#xff0c;加载到环境变量。 2.打开cmd&#xff0c;启动MYSQL&#xff1a;输入net start mysql; 3.登陆MYSQL&#xff0c;需要管理权限&#xff1b; 输入&#xff1a;my…

Docker容器化解决方案

什么是Docker&#xff1f; Docker是一个构建在LXC之上&#xff0c;基于进程容器的轻量级VM解决方案&#xff0c;实现了一种应用程序级别的资源隔离及配额。Docker起源于PaaS提供商dotCloud 基于go语言开发&#xff0c;遵从Apache2.0开源协议。 Docker 自开源后受到广泛的关注和…

数据链路层----滑动窗口协议的相关计算

目录 1.窗口大小的相关计算 •停等协议&#xff1a; •后退N帧协议&#xff1a; •选择重传协议&#xff1a; 2.信道利用率相关计算 •停等协议的信道利用率&#xff1a; •连续ARQ&#xff08;后退N帧协议&#xff0c;选择重传协议&#xff09;的信道利用率&#xff1a;…

工具函数模板题(蓝桥杯 C++ 代码 注解)

目录 一、Vector容器&#xff1a; 二、Queue队列 三、Map映射 四、题目&#xff08;快递分拣 vector&#xff09;&#xff1a; 代码&#xff1a; 五、题目&#xff08;CLZ银行问题 queue&#xff09;&#xff1a; 代码&#xff1a; 六、题目&#xff08;费里的语言 map&…

每日学习总结20240301

20240301 1. strchr VS strrchr strchr和strrchr是C语言标准库中的字符串处理函数&#xff0c;用于在字符串中查找特定字符的位置。 1.1 strchr函数 strchr函数用于在字符串中查找第一次出现指定字符的位置&#xff0c;并返回该位置的指针。函数原型如下&#xff1a; char…

你都了解2024程序员拿捏高薪的新方式吗?

2024年&#xff0c;程序员该如何拿高薪呢&#xff1f; 道理是这么讲&#xff0c;那我们到底应该如何去践行呢&#xff1f;以我自身的经验来看&#xff0c;网络接单无疑是我们程序员来钱最快的方式&#xff0c;既可以做到兼顾本职工作和快点搞钱&#xff0c;又可以充分利用好每一…

信息安全系列04-安全启动介绍

本文框架 1. 基本概念1.1 基本概念回顾1.2 数字签名及验签流程 2. 安全启动实施2.1 信任根选择2.1.1 使用HSM作为信任根2.1.2 使用最底层Bootloader作为信任根 2.2 校验方法确认2.2.1 基于非对称加密算法&#xff08;数字签名&#xff09;2.2.2 基于对称加密算法 2.3 安全启动方…