代码编织梦想

PyQt5中的抽象模型基类QAbstractItemModel与自定义模型

一、关于QAbstractItemModel类

  1. QAbstractItemModel类继承自QObject, 该类是Qt所有模型类的基类,用于管理模型/视图结构中的数据。Qt的所有模型都需要子类化该类。注意,该类是抽象类,我们不应该创建该类的对象。

  2. 该类的构造函数,原型为__init__(parent: QObject = None)

二、QAbstractItemModel 类中的纯虚函数及有效模型索引

2.1 有效模型索引的创建

模型索引是由 QModelIndex 类进行描述的,但该类只有一个默认构造函数,而使用默认构造函数创建的模型索引是无效模型索引,因此要创建一个有效的模型索引,需要使用工厂函数QAbstractItemModel::createIndex()来创建,在重新实现纯虚函数 index()和 parent()时,都有可能会调用该工厂函数来创建模型索引。

2.2 相关函数及其原型

下面为相关的函数及其原型(本文使用的是C++代码的书写格式):

  • virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex ()) const = 0; //纯虚函数
    返回由行 row、列 column、父索引 parent 指定的数据项的模型索引,当子类重新实现此函数时,模型索引需调用 createIndex()函数来创建。

  • virtual QModelIndex parent(const QModelIndex &index) const = 0; //纯虚函数
    返回索引 index 的父模型索引,若没有父模型索引,则返回无效的模型索引。重新实现该函数时需要小心调用 QModelIndex 中的成员函数(比如 QModelIndex::parent());因为自定义的模型索引只会调用自定义的实现,因此 QModelIndex::parent()会调用此处重新实现的该函数,从而导致无限递归。重新实现该函数时,通常也使用 createIndex()函数创建模型索引。

  • virtual int rowCount(const QModelIndex &parent = QModelIndex ()) const = 0; //纯虚函数
    virtual int columnCount(const QModelIndex &parent =QModelIndex ()) const = 0; //纯虚函数
    以上函数表示,返回父模型索引 parent 下的行/列数,在实现基于表格的模型时,若父模型索引有效,则以上函数都应返回 0。

  • virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0; //纯虚函数
    返回索引 index 所引用的项在给定角色 role 下存储的数据。注意:若没有需要返回的值,应返回无效的 QVariant,而不是返回 0。该函数用于向视图和委托提供项目数据,也就是说视图和委托是显示的该函数返回的值。

  • virtual bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole); //虚拟的
    设置索引 index 所引用数据项的值为 value,其角色为 role。若设置成功则返回 true,并且应发送 dataChanged()信号,否则返回 false。默认实现为返回 false,虽然此函数不是纯虚函数,但若模型是可编辑模型,则必须重新实现此函数,

  • QModelIndex createIndex(int row, int column, void *ptr = Q_NULLPTR); //受保护的
    QModelIndex createIndex(int row, int column, quintptr id); //受保护的

    • 以上函数表示使用内部指针 ptr 或内部 ID 为给定的行 row 和列 column 创建一个模型索引。
    • 当重新实现纯虚函数 index()时,需要调用该函数创建模型索引。
    • 内部指针可由 QModelIndex::internalPointer()函数获取,内部 ID 可由QModelIndex::internalId()函数获取。 内部指针其实质就是指指向模型实际所管理的数据,因此不一定需要使用 internalPointer()函数来获取。
    • quintptr 类型在 32 位指针的系统上是 quint32,在 64 位指针的系统上是 quint64,其最终结果都是使用 typedef 重命的 unsigned int 类型(只是系统不同长度不同)
  • bool hasIndex(int row, int column, const QModelIndex &parent =QModelIndex ()) const;
    若根据 row、 column、 parent 返回的模型索引是有效索引,则返回 true,否则返回 false。

  • virtual bool hasChildren(const QModelIndex &parent = QModelIndex ())const; //虚拟的
    若 parent 拥有任何子女,则返回 true,否则返回 false,注意,若设置标志为Qt::ItemNeverHasChildren,则使用此方法的行为是未定义的。在分层模型中,查找数据项的子项目数量是一项昂贵的操作,因此 rowCount()函数应在确有必要时进行调用,通过首先调用此函数判断数据项是否有子项,然后再决定是否调用 rowCount()函数是一种有效的方法。

三、插入和删除行/列

3.1 插入和删除行/列后的数据更新

要使模型能插入行/列和删除行/列,子类需要重新实现以下虚函数:

  • insertRows();
  • insertColumns();
  • removeRows();
  • removeColumns();

下面以 insertRows()虚函数为例,讲解其规则(其余函数,原理类同):

在将新行插入到任何基础数据结构之前,必须调用 beginInsertRows()函数(称其为 begin 函数),该函数会通知其他组件(比如视图或委托)行数将要发生变化,完成插入操作之后,还需要调用 endInsertRows()函数(称其为 end 函数)以通知其他组件,该模型的行数已经更改,若 insertRows()插入成功,则返回 true,否则返回 false。

更改模型结构的另一种方法:

通常使用 begin 和 end 函数就能够达到通知其他组件模型结构变化的目的,但对于结构比较复杂的模型,则这种方法可能会比较低效,比如若有一个有 300 百万行的模型,需要删除所有偶数行,这将有可能使用beginRemoveRows和endRemoveRows达到150万次之多,这显然是低效的。此时可使用以下步骤来更新模型结构

  • 发送 layoutAboutToBeChanged()信号
  • 更新模型结构的内部数据。
  • 使用 chnagePersistentIndexList()更新持久索引。
  • 发送 layoutChanged()信号。

以上步骤可用于更新任何结构的模型。

3.2 插入操作相关的函数

  • virtual bool insertColumns(int column,int count,const QModelIndex &parent=QModelIndex ()); //虚拟的
    virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex ()); //虚拟的

    • QAbstractItemModel 类对以上虚函数的默认实现什么也没有做,并返回 false,因此要想模型支持插入操作,需要重新实现以上虚函数。重新实现时需要调用相应的 begin 函数和 end 函数。

    • 以上函数表示在指定的列 column/行 row 之前插入 count 行/列,插入的新行/列将是 parent 模型索引所指数据项的子项,若插入成功则返回 true,否则返回 false。

  • bool insertRow(int row, const QModelIndex &parent = QModelIndex ());
    bool insertColumn(int column, const QModelIndex &parent = QModelIndex ());
    以上函数分别调用虚函数 insertRows()和 insertColumns()

  • void beginInsertColumns(const QModelIndex &parent, int first, int last); //受保护的
    void beginInsertRows(const QModelIndex &parent, int first, int last); //受保护的
    void endInsertColumns(); //受保护的
    void endInsertRows(); //受保护的

    • 以上函数分别是插入列和行时的 begin 和 end 函数,其中在数据被插入之前beginInsertColunms()函数会发送 columnsAboutToBeInserted()信号,beginInsertRows()函数会发送 rowsAboutToBeInserted()信号,视图或代理通过连接到以上信号,以更新其视图显示,若视图或代理未对以上信号做出处理,则不会正确显示插入的行(需验证信号问题)。
    • 参数 parent 表示被插入到新列/行的父索引, first 和 last 分别表示新列插入后的开始和结束列/行号,下面以 beginInsertCloumns()函数为例进行讲解,beginInsertRows()函数原理是相同的(原理见下图)。
      20221123004417

3.3 删除操作相关的函数

  • virtual bool removeColumns(int column,int count,const QModelIndex &parent=QModelIndex ()); //虚拟的
    virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex ()); //虚拟的

    • QAbstractItemModel 类对以上虚函数的默认实现什么也没有做,并返回 false,因此要想模型支持删除操作,需要重新实现以上虚函数。重新实现时需要调用相应的 begin 函数和 end 函数。
    • 以上函数表示删除模型索引parent所指数据项之下从列column/行row开始的cout行/列。若删除成功则返回 true,否则返回 false。
  • bool removeRow(int row, const QModelIndex &parent = QModelIndex ());
    bool removeColumn(int column, const QModelIndex &parent = QModelIndex ());
    以上函数分别调用虚函数 removeRows()和 removeColumns()

  • void beginRemoveColumns(const QModelIndex &parent, int first, int last); //受保护的
    void beginRemoveRows(const QModelIndex &parent, int first, int last); //受保护的
    void endRemoveColumns(); //受保护的
    void endRemoveRows(); //受保护的

    • 以上函数分别是删除列和行时的 begin 和 end 函数,其中在数据被删除之前beginRemoveColunms()函数会发送 columnsAboutToBeRemoved()信号,beginRemoveRows()函数会发送 rowsAboutToBeRemoved()信号,视图或代理通过连接到以上信号,以更新其视图显示,若视图或代理未对以上信号做出处理,则不会正确显示删除的行。
    • 参数 parent 表示被删除的列/行的父索引, first 和 last 分别表示被删除的数据项的开始和结束列/行号,下面以 beginRemovedCloumns()函数为例进行讲解,beginRemovedRows()函数原理是相同的(原理见下图)。
      20221123004752

3.4 移动操作相关的函数

  • virtual bool moveColumns(const QModelIndex &sourceParent, int sourceColumn, int count,const QModelIndex &destinationParent, int destinationChild); //虚拟的
    virtual bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count,const QModelIndex &destinationParent, int destinationChild); //虚拟的

    • QAbstractItemModel 类对以上虚函数的默认实现什么也没有做,并返回 false,因此要想模型支持移动操作,需要重新实现以上虚函数。重新实现时需要调用相应的 begin 函数和 end 函数。
    • 以上函数表示从源模型索引 sourceParent 所指数据项之下,从列 column/行 row 开始的 cout 行/列,移至目标模型索引 destinationParent 所指数据项之下的destinationChild 之前。若移动成功则返回 true,否则返回 false。
  • bool moveRow(const QModelIndex &sourceParent, int sourceRow,const QModelIndex &destinationParent, int destinationChild);
    bool moveColumn(const QModelIndex &sourceParent, int sourceColumn,const QModelIndex &destinationParent, int destinationChild);
    以上函数分别调用虚函数 moveRows()和 moveColumns()

  • bool beginMoveColumns(const QModelIndex &sourceParent, int sourceFirst, int sourceLast,const QModelIndex &destinationParent, int destinationChild); //受保护的
    bool beginMoveRows(const QModelIndex &sourceParent, int sourceFirst, int sourceLast,const QModelIndex &destinationParent, int destinationChild); //受保护的
    void endMoveColumns(); //受保护的
    void endMoveRows(); //受保护的

    • 以上函数分别是移动列和行时的 begin 和 end 函数, begin 函数简化了模型中实体的移动,负责移动模型中的持久索引,否则需要自已完成持久索引的移动。使用以上的 begin 和 end 函数是 changePersistentIndex()函数及发送 layoutChanged()和layoutAboutToBeChanged()信号的替代方法(验证)。
    • 参数 sourceParent 表示被移动的列/行的父索引, destinationPart 表示移动后所在新列/行的父索引,以上函数表示把从 sourceFirst 开始到 sourceLast 结束的数据项移至 destinationChild 表示的列/行之前。
    • 当在同一父索引中移动时,必须确保destinationChild位于(sourceFirst, sourceLast+1)的范围之外(原理见下图)。另外,不要将列/行移至自已的子项或其祖先,若发生以上两种情形之一,则以上函数应返回 false。
      20221123005246
    • 下面以 beginMoveCloumns()函数为例进行讲解, beginMoveRows()函数原理是相同的(原理见下图)。
      20221123005334

四、模型标头

相关函数如下:

  • virtual QVariant headerData(int section, Qt::Orientation orientation,int role =Qt::DisplayRole) const; //虚拟的

  • virtual bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole); //虚拟的

以上函数用于获取和设置位于方向 orientation 和位置 section 的标头数据,其数据角色为 role(见下图)。重新实现 setHeaderData 函数时,必须明确地发送 headerDataChanged()信号。

20221123005749

五、另一种设置数据项的函数

相关函数如下:

  • virtual bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles); //虚拟的
  • virtual QMap<int, QVariant> itemData(const QModelIndex &index) const; //虚拟的
  1. 以上函数主要用于获取和设置组成数据项的各数据元素的信息,
  2. 注意, QMap<int,QVariant>中的 int 指的是 Qt::ItemDataRole 枚举成员对应的 int值,比如 QMap<int, QVariant> mp; mp.insert(6,f); setItemData(index, mp);表示把数据项的字体角色(Qt::FontRole 对应的 int 值为 6)设置为字体 f。
  3. itemData 函数是模型/视图结构中唯一能获取项目角色的函数。

使用 setItemDate()函数设置数据项(效果见图中 EEE)简明示例:

QFont f;
f.setPixelSize(22);
QMap<int, QVariant> mp; //创建 QMap
mp.insert(0,"EEE"); //设置 Qt::DisplayRole 角色的数据
mp.insert(1,QIcon("F:/1i.png")); //设置 Qt::DecorationRole 角色的数据
mp.insert(3,"222"); //设置角色 Qt::ToolTipRole 的数据
mp.insert(6,f); //设置角色 Qt::Font 的数据
//以下数据项由以上 4 个数据元素组成。
pmodel->setItemData(pmodel->index(0,1,QModelIndex()),mp);

20221123010703

六、自定义模型

6.1 自定义模型相关的虚函数

自定义模型至少需要实现以下虚函数:

  • columnCout()
  • rowCount()
  • index()
  • parent()
  • data()

为了能添加自已的数据到模型中,通常还需要重新实现 setData()函数,而不重新实现setData()则无法向模型中添加数据。

6.2 自定义模型的基本原理和步骤

  1. 数据:实际数据可使用 QList、数组、整型、或单独的一个类来保存,数据可存放在模型中,也可存放在文件等其他地方。

  2. columnCout()、 rowCount()、 index()、 parent()这 4 个函数用于共同设计模型的结构,因为使用索引表示模型中的某个数据项的位置,因此设计模型索引的结构就是设计模型的结构

    • 行数和列数的设计:比如对于 3 行 4 列的表格结构 columnCout()应返回 4,rowCount()应返回 3;对于列表结构,则因为列表只需要 1 列,所以 columncout()应总是返回 1, rowCount()返回该列表的行数;对于树形结构模型,则更复杂,需要根据当前父节点的情况进行判断,以返回该父节点拥有的列数和行数。

    • parent()函数(父模型索引)的设计:因为表格结构中的所有单元格都属于同一个父索引之下,所以可把所有单元格都视为顶级节点,因此他们的父索引可以以无效模型索引作为父索引,因此 parent()可以返回一个无效模型索引;对于列表结构的模型,同样只需返回一个无效模型索引即可;对树形结构模型,此步骤比较复杂,可以通过获取当前节点的父节点及其行号和列号,然后使用 createIndex()创建该父节点的索引。

    • index()函数的设计:该函数用于为模型中的每个数据项创建索引,创建索引需要使用 createIndex()函数,对于表格结构,只需向 createIndex()函数传递当前数据项所在的行号、列号及使用的数据的指针即可;对于列表结构,则列号始终为 0,其余同表格结构;对于树形结构,需要向该函数传递当前数据项位于父索引中的行号、列号及使用的数据的指针。

  3. data()函数的返回值决定了视图上应显示的数据,也就是说在界面上用户看到的数据是由该函数返回的, 若返回不当的值,则数据无法正常显示在视图上,下面以使用内置的标准视图类为例来讲解怎样设计此函数。 data()函数会被视图类调用多次, 视图每次都会向 data 传递一个不同的 role(角色)参数值,然后视图根据 data 返回的值,设置该 role 的数据, 因此在设计 data 函数的返回值时,需要根据 role 的不同值返回不同的数据,以使视图正确的显示。

七、示例代码

自定义表格模型示例代码


运行效果如下:


小手一抖,点个赞再走哦~~~

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/hubing_hust/article/details/127992873

qt模型/视图原理(2):自定义模型(qabstractitemmodel)_hyongilfmmm的博客-爱代码爱编程_qt qabstractitemmodel

Qt模型/视图原理(2):自定义模型(QAbstractItemModel) 本文为原创文章,转载请注明出处,或注明转载自“黄邦勇帅(原名:黄勇) 本文出自本人原创著作《Qt5.10 GUI完全参考手册》网盘地址: ht

python gui pyqt5基础知识-爱代码爱编程

一、安装 pip install PyQt5 pip install pyinstaller # 打包程序为.exe文件 ''' 打包文件: 命令行命令: pyinstaller -F -w C:\Users\seed

关于python3中无法安装pyqt5和pyqt5-tools的解决办法_aiden_s.k.的博客-爱代码爱编程

版权声明:本文为博主原创文章,未经博主允许不得转载。如需转载请留言,并贴上原博文链接:https://blog.csdn.net/u011628215/article/details/97389973 前言:可能题目表达的不是很准确,我是在想要使用Python3做GUI界面的时候遇到的问题。本文对于如何使用PyQt5进行GUI的搭建不做介绍,仅为了解

实战PyQt5: 066-Model-View框架中的Delegate类-爱代码爱编程

委托类(Delegate)简介 不同于MVC模式,模型-视图设计不包括用于管理与用户交互的完全独立的组件。通常,视图负责将模型数据呈现给用户,并负责处理用户输入。为了让输入的方式具有一定的灵活性,这种交互由委托来完成。这些部件在视图中提供输入功能,同时负责在某些视图中渲染单个项。在QAbstractItemDelegate类中定义了用于控制委托的标准接口

实战PyQt5: 082-图形视图(Graphics-View)框架简介-爱代码爱编程

图形视图框架(Graphics View Framework)提供了一个用于管理大量定制2D图形图元(Item)并与之交互的表面(surface)。以及一个可用于可视化这些图元的视图(View)部件,它支持缩放和旋转。 该框架包含一个事件传播体系结构,该体系结构允许对场景(Scene)中的图元进行精确的交互。在其中的图元可以处理按键事件,鼠标按下,移动,

python gui 显示表格_python GUI库图形界面开发之PyQt5表格控件QTableView详细使用方法与实例...-爱代码爱编程

PyQt5表格控件QTableView简介 在通常情况下,一个应用需要和一批数据进行交互,然后以表格的形式输出这些信息,这时就需要用到QTableView类了,在QTableView中可以使用自定义的数据模型来显示内容,通过setModel来绑定数据源 QTableWidget继承自QTableView,主要区别是QTableView可以使用自定义

python的pyqt5中文文档_介绍 - PyQt5 中文教程 - 开发手册 - 文江博客-爱代码爱编程

本教程的目的是带领你入门PyQt5。教程内所有代码都在Linux上测试通过。PyQt4 教程是PyQt4的教程,PyQt4是一个Python(同时支持2和3)版的Qt库。 关于 PyQt5 PyQt5 是Digia的一套Qt5应用框架与python的结合,同时支持2.x和3.x。本教程使用的是3.x。Qt库由Riverbank Computing开

【Python】PyQt5.QtGui 模块常用函数和类(使用参考源码)-爱代码爱编程

Python PyQt5.QtGui 模块常用函数和类。 PyQt5 是一套Python绑定Digia QT5应用的框架。QtGui 是PyQt5下面的一个模块,QtGui模块涵盖多种基本图形功能的类; 包括但不限于:窗口集、事件处理、2D图形、基本的图像和界面 和字体文本。以下从289个开源Python项目中,按照使用频率进行了排序,并列出了使

opencv-python与pyqt5冲突?使二者共存的方法-爱代码爱编程

        很多博客上说安装opencv-python-headless就好了,但是,opencv-python-headless中不包含窗体支持,也就是说,这样的opencv是无法imshow和waitKey。         opencv-python和pyqt5冲突是因为在opencv-python4.2.0以上的版本,opencv-pytho