21. [Python GUI] PyQt5中的模型与视图框架-抽象模型基类QAbstractItemModel与自定义模型

news2024/11/20 18:27:29

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 的不同值返回不同的数据,以使视图正确的显示。

七、示例代码

自定义表格模型示例代码


运行效果如下:


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

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

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

相关文章

数字孪生应用方向展示

昨晚&#xff0c;2022年卡塔尔世界杯正式打响&#xff01;伴随开幕式的进行&#xff0c;由中国铁建城建的卡塔尔世界杯主场馆卢赛尔体育场惊艳全球。事实上&#xff0c;在数字孪生技术的加持下&#xff0c;体育场馆建设也是重点技术应用方向之一&#xff0c;今天就为大家重点展…

java读取文件

先看项目截图 public class FileTest {public static void main(String[]args) throws IOException {String path Objects.requireNonNull(FileTest.class.getClassLoader().getResource("")).getPath();System.out.println(path);System.out.println("****…

微信“史诗级”更新,小而美终于回来啦~

最近微信安卓版又有了更新&#xff0c;版本号也来到了8.0.30。 此次更新又被业界称之为“史诗级”更新&#xff0c;主要原因是新版本微信安装包体积缩小了10M。 没错&#xff0c;你没有看错微信的安装包真的缩小了&#xff0c;而且整整缩小了10M&#xff01; 天呐&#xff0…

MyBatis从入门到精通真没那么难!跟着我带你深入实践Mybatis技术原理与实战!

什么是Mybatis mybatis 是一个优秀的基于java的持久层框架&#xff0c;它内部封装了jdbc&#xff0c;使开发者只需要关注sql语句本身&#xff0c;而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。 mybatis通过xml或注解的方式将要执行的各种 statemen…

跨平台应用开发进阶(四十五)uni-app集成企微客服实战

文章目录一、前言二、功能实现2.1 环境准备2.2 代码层面2.3 拓展工具三、拓展阅读一、前言 应用运营过程中&#xff0c;考虑接入企业微信客服功能&#xff0c;大致看了下官方接入文档&#xff0c;并不困难&#xff0c;引入代码量也不大。按照手册来操作即可。 二、功能实现 …

Go Module的基本使用

go module是类似于java中的maven,是包的管理工具&#xff0c;在没有这个go module之前&#xff0c;都是配置本地的GOPATH&#xff0c;创建的每个项目也都必须创建在这个GOPATH的src目录下&#xff0c;且项目的go文件不能重名 go module是在go1.1.1版本推出的 开启go module 在…

装配式施工在建筑装修中的应用研究

目 录 摘 要 I Abstract II 1引言 1 2装配式施工在建筑装修中的发展背景及现状 2 2.1装配式施工在建筑装修中的发展背景 2 2.2建筑装饰行业现状 2 3装配式施工在建筑装修中体系的主要特点 3 4装配式施工在建筑装修中体系的构成 4 4.1八大系统 4 4.1.1集成卫浴系统 4 4.1.2集成厨…

【App自动化测试】(二)Appium环境部署

目录1. Appium生态工具2. Appium环境安装部署2.1 Appium 环境依赖说明2.2 第一步&#xff1a;安装JDK2.3 第二步&#xff1a;安装SDK2.4 第三步&#xff1a;安装Appium2.5 第四步&#xff1a;安装appium python client2.6 第五步&#xff1a;安装appium-doctor检测appium的安装…

Metabase学习教程:视图-1

你应该用哪个图表&#xff1f; 您应该使用哪种类型的图表和图表来最好地传达来自数据的见解&#xff1f;这将有助于你选择正确的工作。 选择正确的图表可以归结为两个问题&#xff1a;数据是什么样子的&#xff0c;以及您试图传达什么&#xff1f; 让Metabase为您选择图表 …

Day01-网页结构分析

网页结构分析 一 前言 姓名&#xff1a;陈云 TEL&#xff1a;18571593511 企业用人的两个标准 1.能干活,见到需求能反应出粗线条实施计划,起手实施后能自主预判和解决坑点,直至完成. 2.对某些敏感点理解较准确,有一定潜质做个性化封装和技术选型工作,应对突发状况,避免生…

SpringBoot--通过JSON传递请求参数--方法/实例

原文网址&#xff1a;SpringBoot--通过JSON传递请求参数--方法/实例_IT利刃出鞘的博客-CSDN博客 简介 本文用示例介绍SpringMVC如何通过JSON格式传递入参。 JSON格式使用post方式来请求&#xff0c;即&#xff1a;对应的注解为&#xff1a;PostMapping。 PostMapping注解的方法…

Vue学习笔记

课程来源&#xff1a;https://www.bilibili.com/video/BV1Zy4y1K7SH?p4&vd_source6f37192b213c98639a87ec77b26d105d 学习计划&#xff1a;一天学10节&#xff0c;从第四节开始做笔记&#xff0c;预计16天完成&#xff08;完成日期2022年12月7日&#xff09; 目录&#…

【1】Anaconda基本命令以及相关工具:jupyter、numpy、Matplotilb

目录 一、Anaconda ◼ anaconda命令行操作 二、jupyter notebook 1 jupyter的基础使用 ◼ jupyter的启动 ◼ ipynb文件的新建、重命名、删除 ◼ 菜单说明 ◼ 运行cell单元块 ◼ 编写文档笔记 2 jupyter的高级使用&#xff1a;常用魔法命令 ◼ writefile pycat run …

【Golang】来用GoLand开发第一个Go程序

​ &#x1f4d3;推荐网站(不断完善中)&#xff1a;个人博客 ​ &#x1f4cc;个人主页&#xff1a;个人主页 ​ &#x1f449;相关专栏&#xff1a;CSDN专栏、个人专栏 ​ &#x1f3dd;立志赚钱&#xff0c;干活想躺&#xff0c;瞎分享的摸鱼工程师一枚 文章目录&#x1f34a…

【JAVA程序设计】基于JAVA的坦克大战小游戏--入门级小游戏

基于JAVA的坦克大战小游戏--入门级小游戏零、项目获取一、项目简介二、开发环境三、游戏玩法四、运行截图零、项目获取 获取方式&#xff08;点击下载&#xff09;&#xff1a;是云猿实战 项目经过多人测试运行&#xff0c;可以确保100%成功运行。 一、项目简介 本项目是基于…

基于Java+Spring+Strusts2+Hibernate 社区智慧养老服务平台 系统设计与实现

一.项目介绍 本系统分为前端 和 后端 两块&#xff0c; 前端包括&#xff1a;首页、健康新闻、疾病预防、养生之道、动态通知、登录和注册 各模块的文章点击查看&#xff0c;支持评论以及回复功能&#xff0c; 登录成功之后点击欢迎您:后面的账号即可进入后台管理 后端包括&…

老杨说运维 | 非常重要,事关转型

《荀子》有云&#xff1a;“水能载舟&#xff0c;亦能覆舟。”在公司日常运营过程中&#xff0c;数据指标就像是水&#xff0c;孕育着生命&#xff0c;承载着万物。科学的数据指标能指引公司在正确的道路上不断前进&#xff0c;使平淡无常的业务焕发新生&#xff0c;而不合理的…

Qt QUndoStack、QUndoCommand(实现撤回和回撤)

用到的类: 1 QUndoStack&#xff1a; 一个存放 QUndoCommand 命令的栈. 2 QUndoCommand&#xff1a;The QUndoCommand class is the base class of all commands stored on a QUndoStack. 3 QUndoView&#xff1a;The QUndoView class displays the contents of a QUndoStack.&…

C#上位机系列(3)—定时器和串口的介绍

本文是讲解C#.net平台的Winform框架下的第三个内容&#xff0c;手把手介绍上位机项目的创建方式以及一些写软件时常用的功能&#xff0c;讲解从零开始的每一个步骤。 本次介绍上位机中定时器的使用和串口的配置 定时器&#xff1a;每过一段设定好的时间进入执行函数&#xff…

SIMULIA现实仿真解决方案 SIMULIA仿真模拟应用程序

由 3DEXPERIENCE 平台提供技术支持&#xff0c;SIMULIA 可提供仿真模拟应用程序&#xff0c;这些应用程序使用户能够探究现实生活中产品、自然和生命的行为 SIMULIA 通过虚拟测试实现性能要求促进协作。其产品组合提供强大的工具&#xff0c;能够对采用V6设计的零件、组件和产…