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();
}








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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

32单片机基础:TIM输入捕获

指定的电平跳变&#xff0c;就是上升沿或者下降沿&#xff0c;可以通过程序设置 PWMI模式&#xff0c;就是PWM的输入模式&#xff0c;是专门为测量PWM频率和占空比设计的&#xff0c; 可配合主从触发模式 这两个功能结合起来&#xff0c;测量频率占空比就是硬件全自动运行的…

电脑如何进行屏幕录制?轻松成为视频大咖!

随着电脑的普及以及互联网技术的发展&#xff0c;屏幕录制成为了人们在工作和生活中经常需要面对的问题。无论是录制游戏过程、软件操作教程&#xff0c;还是网络教学视频&#xff0c;都需要进行屏幕录制。可是电脑如何进行屏幕录制呢&#xff1f;本文将介绍两种常见的电脑屏幕…

蓝桥杯-大小写转换

转换方法 toLowerCase() String类的toLowerCase()方法可以将字符串中的所有字符全部转换成小写&#xff0c;而非字母的字符不受影响&#xff0c;语法格式如下&#xff1a; 字符串名.toLowerCase() //将字符串中的字母全部转成小写&#xff0c;非字母不受影响。 package chap…

【vue.js】文档解读【day 1】 | 模板语法1

如果阅读有疑问的话&#xff0c;欢迎评论或私信&#xff01;&#xff01; 本人会很热心的阐述自己的想法&#xff01;谢谢&#xff01;&#xff01;&#xff01; 文章目录 模板语法前言文本插值原始HTML属性Attribute绑定动态绑定多个值 模板语法 前言 Vue 使用一种基于 HTML…

MATLAB2020a安装编译器mingw-64(6.3.0)

MATLAB2020a指定安装mingw-64&#xff08;6.3.0&#xff09;版本编译器 记录一下几个要点 mingw-64&#xff08;6.3.0&#xff09; 找到对应的mingw-64安装包 设置mingw的bin文件路径到环境变量 变量名&#xff1a;MW_MINGW64_LOC MATLAB设置路径

微信小程序接入百度地图(微信小程序插件)使用文档

第一步配置域名 :在微信公众平台登录后配置服务域名称:https://apis.map.qq.com 第二步申请密钥 申请开发者密钥申请地址 第三步使用插件 选择添加插件 搜索腾讯位置服务地图选点 选择要授权的小程序 授权完毕会在这里显示插件信息 第四步查看使用文档 跳转至文…

【Linux C | 网络编程】广播概念、UDP实现广播的C语言例子

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

关于出国留学和考研比较----以本人双非跨考计算机为例

文章目录 中心论点国内就业现状勿让旧认知害了自己那出国留学真的一无是处了吗?1. 藤校仍旧是具有极高价值2. 时间成本低3. 研究生一定比单纯的本科找工作强!4. 很多人说出国读博好,可以无脑入,真是这样吗? 中心论点 如果在选择出国留学还是国内考研的最终核心诉求都是有更好…

LSTM长短期记忆网

笔记来源—— 【重温经典】大白话讲解LSTM长短期记忆网络 如何缓解梯度消失&#xff0c;手把手公式推导反向传播 LSTM网络结构 RNN结构 下面拉出一个单元结构进行讲解 &#xff1a;记忆细胞&#xff0c;t-1时刻的记忆细胞 :表示状态,t-1时刻的状态 正是这样经过了一个单元&a…

Oracle.xs.dll‘ for module DBD::Oracle: load_file:找不到指定的模块

安装Ora2pg时,碰到 异常现象 D:\ProgramFiles\ora2pg>ora2pg -t show_report --estimate_cost -c ora2pg_conf.dist install_driver(Oracle) failed: Cant load D:/ProgramFiles/strawberry/perl/site/lib/auto/DBD/Oracle/Oracle.xs.dll for module DBD::Oracle: load_fil…

Nginx使用—基础知识

Nginx简介 Nginx优点 高性能、高并发 支持很高的并发&#xff0c;在处理大量并发的情况下&#xff0c;比其他web服务器要高效 轻量且高扩展 功能模块少(源代码仅保留http与核心模块代码&#xff0c;其余不够核心代码会作为插件来安装) 代码模块化&#xff08;易读&#xff0…

win10虚拟机安装驱动教程

在虚拟机菜单栏中选择安装VMware Tools&#xff1a; 安装好后&#xff0c;在虚拟机中打开此电脑&#xff0c;双击DVD驱动器进行安装&#xff1a; 一直点击下一步&#xff1a; 安装完成&#xff1a; 此时重启虚拟机&#xff0c;发面小屏幕页面的虚拟机自动占满了全部屏幕&#x…

Docker常用基础指令

目录 1 前言 2 常用指令 2.1 获取帮助 2.2 拉取镜像到本地 2.3 对本地镜像进行打包 2.4 对本地镜像的删除 2.5 通过tar包加载本地镜像 2.6 查看所有镜像 2.7 创建新的容器 2.8 查看容器 2.9 停止容器运行 2.10 运行容器 2.11 删除容器 2.12 查看容器日志 2.13 进…

ImportError: Could not import docarray python package解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

springcloud和基础服务的搭建以及封装

代码仓库地址&#xff1a;https://github.com/zhaoyiwen-wuxian/springcloud-common page分页也进行了封装&#xff0c;只需要添加到pom中&#xff0c;将会自动进行分页&#xff0c;并且后端不需要写任何的分页数据。只需要前端自己传分页参数即可&#xff0c;并且里面封装了很…

Clickhouse: 数据基本知识

产品概述 ClickHouse是一个开源的列式数据库管理系统&#xff0c;专门用于在线分析处理&#xff08;OLAP&#xff09;场景。它具有高性能、高可靠性、高可扩展性和低成本等优点&#xff0c;被广泛应用于大数据领域。 以下是ClickHouse的主要特点&#xff1a; 高性能&#xff…

挑战杯 基于深度学习的人脸表情识别

文章目录 0 前言1 技术介绍1.1 技术概括1.2 目前表情识别实现技术 2 实现效果3 深度学习表情识别实现过程3.1 网络架构3.2 数据3.3 实现流程3.4 部分实现代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习的人脸表情识别 该项目较…

Unity角色动画变成半跪\半蹲\下沉 的问题

导入的人物动画发生如图形态 解决方法&#xff1a;找到动画模型&#xff0c;Rig - AnimationType 改为Humanoid &#xff0c;然后Apply一下

Vue+OpenLayers7入门到实战目录

前言 本篇作为《VueOpenLayers7入门到实战》所有文章的二合一汇总目录&#xff0c;方便查找。 本专栏源码是由OpenLayers7.x版本结合Vue框架编写。 本专栏从Vue搭建脚手架到如何引入OpenLayers7依赖的每一步详细新手教程&#xff0c;再到通过各种入门案例和综合性的实战案例&a…

【MySQL】视图、索引

目录 视图视图的用途优点视图的缺点创建视图查看视图修改视图删除视图注意事项 索引索引的原理索引的数据结构二分查找法Hash结构Hash冲突&#xff01;&#xff01;&#xff01; B树二叉查找树 存在问题改造二叉树——B树降低树的高度 B树特点案例继续优化的方向 改造B树——B树…