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

本文介绍了一个使用QListView和自定义模型及视图Delegate实现的聊天历史记录展示例子,重点在于QStyledItemDelegate中的sizeHint函数,用于动态计算消息行高度,适合大量聊天记录的场景。
摘要由CSDN通过智能技术生成

今天和大家分享一个使用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();
}








  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
借助于Qt的Model/View架构和提供的一些样例代码,我们可以很方便地用QListView实现一个简单的QQ列表。 首先,我们需要创建一个QListView对象和一个QStandardItemModel对象。这个QStandardItemModel是Qt中提供的标准的Item Model,用来提供给View提供数据。 ```C++ #include <QListView> #include <QStandardItemModel> QListView *listView = new QListView(this); QStandardItemModel *model = new QStandardItemModel(this); ``` 然后,我们可以为这个QStandardItemModel添加QQ列表中的好友信息,通过QStandardItemModel的`setItem`函数设置每个item的数据模型。 ```C++ QStandardItem *item1 = new QStandardItem("Tom"); item1->setIcon(QIcon(":/images/avatar1.png")); QStandardItem *item2 = new QStandardItem("Jack"); item2->setIcon(QIcon(":/images/avatar2.png")); QStandardItem *item3 = new QStandardItem("Lucy"); item3->setIcon(QIcon(":/images/avatar3.png")); model->appendRow(item1); model->appendRow(item2); model->appendRow(item3); ``` 接着,我们将QStandardItemModel设置为QListView的model,并设置一些样式和属性。 ```C++ listView->setModel(model); listView->setEditTriggers(QAbstractItemView::NoEditTriggers); listView->setDragDropMode(QAbstractItemView::NoDragDrop); listView->setSelectionMode(QAbstractItemView::SingleSelection); listView->setSelectionBehavior(QAbstractItemView::SelectRows); listView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); listView->setStyleSheet("border: 0; padding-left: 10px; background-color:white;"); listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); listView->setUniformItemSizes(true); listView->setIconSize(QSize(36, 36)); ``` 最后,我们将这个QListView加入到窗口中进行显示。 ```C++ QVBoxLayout *layout = new QVBoxLayout(); layout->addWidget(listView); setLayout(layout); ``` 完整代码如下: ```C++ #include <QListView> #include <QStandardItemModel> #include <QVBoxLayout> class MainWindow : public QMainWindow { public: MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) { QListView *listView = new QListView(this); QStandardItemModel *model = new QStandardItemModel(this); QStandardItem *item1 = new QStandardItem("Tom"); item1->setIcon(QIcon(":/images/avatar1.png")); QStandardItem *item2 = new QStandardItem("Jack"); item2->setIcon(QIcon(":/images/avatar2.png")); QStandardItem *item3 = new QStandardItem("Lucy"); item3->setIcon(QIcon(":/images/avatar3.png")); model->appendRow(item1); model->appendRow(item2); model->appendRow(item3); listView->setModel(model); listView->setEditTriggers(QAbstractItemView::NoEditTriggers); listView->setDragDropMode(QAbstractItemView::NoDragDrop); listView->setSelectionMode(QAbstractItemView::SingleSelection); listView->setSelectionBehavior(QAbstractItemView::SelectRows); listView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); listView->setStyleSheet("border: 0; padding-left: 10px; background-color:white;"); listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); listView->setUniformItemSizes(true); listView->setIconSize(QSize(36, 36)); QVBoxLayout *layout = new QVBoxLayout(); layout->addWidget(listView); setLayout(layout); } }; ``` 这样就可以通过QListView实现一个QQ列表啦!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值