• 关于qt模型视图 QStandardItemModel 的通俗讲解


    其实想实现树形,或者表格显示,有3种方式:

    1. 直接用qtreewidget
    2. 用模型/视图,用qt提供的标准QstandItemModel
    3. 用模型/视图,子类化QAbstractItemModel方式

    这里我们主要讲模型/视图的使用原理。各种方式的全面介绍,请看看我这边博客:qtreeview和qtreewidget的区别 使用总结 和选择_标biao的博客-CSDN博客

    注:node,item,QModelIndex其实是一个东西(直接强制转换),重新实现虚函数,就是为了把这个和行row,列column给绑定(或者叫映射,对应)起来。然后视图就知道怎么显示出这些实体了。

    为什么需要 模型-视图 框架

    模型(就是指的数据组成形式,链表,多叉树,矩阵结构等)和视图(就是实际显示出来的控件)分离,相等于就是前端和后端分离的架构了,这个能够减少代码耦合度,提高复用性。(大家想想网站开发,不就是这么个道理嘛)

    原因:模型是一种数据之间的组成架构,我们只需要把数据项itme之间的兄弟关系,父子关系,这些关系对应映射到我们的模型中去(实际上就是根据我们的数据项关系自己指定出各自的行列信息,这个是一次性的),然后视图读取这个模型,update()一下,就会自动更新视图了。

    所以,我们就只需要专注于我们的item的数据以及item关系的处理了(比如多叉树数据结构,增加一个孩子啥的操作,由于之前一次性映射好了模型的,所以视图那边会根据这个模型自动更新的),也就是后端这个业务代码了。

    模型结构

    为了能够让我们更好的设计模型,也就是数据之间的关系,qt提供了QStandardItem、 QStandardItemModel 类来描述这个模型:

    • QStandardItem:表示一个最基本的数据项item,可以包含显示的文本,图标,复选框。而且这个数据项可以可以指定背景色,字体。具有 使能,可编辑的,可选择的,可勾选的等状态。而且还能用于拖动和放下等操作的目标。还能存数据在里面(用item->setData(),直接把一个复合数据(比如QMap等)给存进去)。可以在Model中指定本item所处的行,列(所以这些数据项就能去组成丰富的组织结构,比如列表,树,表格)。
    • QStandardItemModel:表示这些数据项组成起来的索引总体(位置做标记),即对其关系进行管理。它是继承自QAbstractItemModel类,里面已经实现了很多必须重新实现的虚函数接口。

      如果我们有自己的结构体关系了,这个结构体节点来充当item,那我们需要子类化QAbstractItemModel,实现对应虚函数。此时每个项item(就是我们自己的结构体节点充当)就是对应这里面的 QModelIndex (这里每个QModelIndex其实就是 item的地址,如果item也是地址,那么这两直接强转就行),这个就是用来对应这个item的。

    自己子类化QAbstractItemModel的例子如下: 

    此时必须实现下面的虚函数,从而建立出所有的 QStandardItem 在 这个自定义的Model里面的组织结构关系

    1. QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
    2. QModelIndex parent(const QModelIndex &child) const override;
    3. int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    4. int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    5. QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    6. bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
    7. Qt::ItemFlags flags(const QModelIndex &index) const override;
    //视图会调用这个函数,说我现在给你parent地址,我要找它的第row子行,column子列,这个节点的首地址是谁,帮我找出来
    QModelIndex ProjectModel::index(int row, int column, const QModelIndex &parent) const
    

    这里再拿个函数 QModelIndex parent(const QModelIndex &child) 函数举例,就是要我们实现给出一个child项,返回出它的父节点项。(如果我们不实现这个,当然不行,因为Model也不知道这些项item的行列信息啊,所以视图调这些函数没法显示)。那我们当然就根据我们的自定义的数据结构关系(比如自定义的多叉树数据结构),找出它的父节点的地址(就是node),然后用这个地址调用QModelIndex  createIndex(row, column, node)函数去创建出一个QModelIndex 返回即可(这就是数据项在模型中的映射过程)。(注意这里可以看出,模型内部还是将node对应到一个行row,列column作为node(也就是item)定位的

    那么如果我们自己结构体,某些node里的数据发生了改变,想要告诉视图view更新一下,有两个函数可以调用:

    1. beginResetModel(),endResetModel(),这个函数会让整个视图都更新
    2. dataChanged(topLeft, bottomRight),这个函数会让视图的局部item显示进行更新,topLeft表示左上角的item的首地址(这就是QModelIndex),bottomRight表示右下角的。

    其实也不一定就需要子类化QAbstractItemModel这个类,大部分情况应用都比较简单,直接用实例化QStandardItemModel这个类,给它添加item,然后item再添加自己的item,就形成了多叉树,挺简单的。比如下面就是例子。(但是就没法实现自己对item之间的关系管理了)

    model->appendRow(fileItem);
    fileItem->appendRow(posItem);

    有了模型之后,我们只需要将这个模型设置到一个view上去即可,这时候,这个view就会自动根据这个model里面的行列信息(上面说了一个node的地址是和一个行,lie进行绑定的)找到node地址,进行对应的实体显示了。

    qt提供的视图有哪些

    由于数据之间的结构关系,其实就三种,列表,树,矩阵,所以qt提供的视图类也就3种,分别是listview,treeview(描述能力包含listview),tableview(很明显,这个的描述能力可以包含前两者)。当然我们还能自己定义一个视图出来。这些实际上都是继承自QWidget,所以它们都是可视化控件了,只是需要设置一个模型XXModel后(原理:这个视图显示就是读取这个model里面的行列信息进行对应显示了),就能显示出具体东西来了。

    函数分析

    这里再分析一个函数  int rowCount(const QModelIndex &parent = QModelIndex()) const override;

    这个是返回指定父节点的孩子数(每个孩子占一行,其中列为0),前提是给的参数parent是有效的。那这里为什么形参是

    parent = QModelIndex()呢,说明如果说我们没有指定形参,那么parent就是QModelIndex(),意思是创建出一个QModelIndex栈对象。从QModelIndex()类的构造函数说明来看,显然这个parent现在是invalid的。既然parent不可用,那么rowCount返回的是什么???

    我们的rowCount的说明没有指出来。但是在Model/View Programming部分的Model Classes中的Rows and columns里面有一句话是这样说的:Top level items in a model are always referenced by specifying QModelIndex() as their parent item. 意思是一个model中的顶级item也就是Top level item 的parent,一直都是通过QModelIndex()来指定的

    所以,知道一个model的所有行,你就必须使用QModelIndex()来作为其parent。如果你想知道一个model中的某个item下有多少行,你就需要将该item的地址(我们强转为modelindex)作为parent。

    所以直接调用 rowCount()函数,啥参数也不给,返回的就是这个模型的总行数。

    参考博客:

    C++ qt QModelIndex::QModelIndex()怎么解释?_百度知道

    为什么需要QStyledItemDelegate 

     item数据之间的组成结构,是由我们的模型model来实现的,而显示是由view来实现的(只提供了基本的编辑交互),那么视图view中与item的交互(编辑,点击,以及其它更灵活的操作)是由谁来实现呢???

    QStyledItemDelegate 这个代理类来完成,而且我们希望自己控制item的绘制效果(而QStyledItemDelegate没有对我们数据类型的绘制提供支持 ),那么就子类化 QStyledItemDelegate,并且重写 paint()函数即可。paint() 函数会被每一个item独立调用,而sizeHint()函数则可以定义每一个item 的大小。在重写 paint() 函数的时候,通常需要用 if 语句找到你需要进行渲染的数据类型并进行绘制,其他的数据类型需要调用父类的实现进行绘制。

    该函数原型如下:

     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)

    踩坑记录

    这个paint函数里,有个点需要特别注意:

    比如我们想控制文本字符串的绘制,我们需要绘制一段字符串,然后用QFontMetrics去测量一下这段字符串宽度(所以这里依赖与字体),然后设置x坐标,继续绘制接下来的字符串。这里一定要注意了,painter的字体和QFontMetrics的字体是否相同,这样绘制出来的文本才不会有莫名其妙的空格。需要我们去显式的设置字体,比如:

    QFontMetrics metrics = option.fontMetrics;
    painter->setFont(option.font);

    参考博客:

    QStyledItemDelegate类的使用_兔子先生_的博客-CSDN博客_qstyleditemdelegate的用法

  • 相关阅读:
    蓝牙耳机哪种通话效果最好?通话质量最好的蓝牙耳机盘点
    CAN通信
    【数据聚类】基于Baysian、KNN、3Layer Neural Network Classifier、KMeans多种算法实现数据聚类附matlab代码
    数据结构与算法(C语言版)P8---树、二叉树、森林
    2.4 Sample Moments and Hypothesis Tests
    网络安全CTF流量分析-入门3-Webshell连接流量分析
    如何隐藏Selenium特征实现自动化网页采集
    免费SSL证书与付费SSL证书的区别
    基于JAVA汉字学习网站计算机毕业设计源码+系统+mysql数据库+lw文档+部署
    软件复杂性的膨胀与测试
  • 原文地址:https://blog.csdn.net/kangkanglhb88008/article/details/127121776