Qt Model/View之Model

news2024/11/13 6:31:08


在检查如何处理选择之前,您可能会发现检查模型/视图框架中使用的概念很有用。

基本概念

在模型/视图架构中,模型提供了一个标准接口,用于视图和委托访问数据。在Qt中,标准接口由QAbstractItemModel类定义。无论数据项如何存储在任何底层数据结构中,QAbstractItemModel的所有子类都将数据表示为包含项目表的层次结构。视图使用这种约定来访问模型中的数据项,但它们向用户呈现这些信息的方式没有限制。

模型还通过信号和槽机制通知任何附加视图有关数据更改的信息。
本节描述了一些基本概念,这些概念对于其他组件通过模型类访问数据项的方式至关重要。后面的部分将讨论更高级的概念。

模型索引

为了确保数据的表示和访问方式是分开的,引入了模型索引的概念。可以通过模型获得的每条信息都由模型索引表示。视图和委托使用这些索引来请求要显示的数据项。

因此,只有模型需要知道如何获取数据,模型管理的数据类型可以定义得相当通用。模型索引包含一个指向创建它们的模型的指针,这可以防止在使用多个模型时产生混淆

QAbstractItemModel *model = index.model();

模型索引提供对信息片段的临时引用,可用于通过模型检索或修改数据。由于模型可能会不时地重新组织其内部结构,因此模型索引可能会变得无效,不应该被存储。如果需要对一个信息片段的长期引用,则必须创建一个持久的模型索引。这提供了对模型保持更新的信息的引用。临时模型索引由QModelIndex类提供持久模型索引由QPersistentModelIndex类提供

要获得对应于数据项的模型索引,必须为模型指定三个属性:行号、列号和父项的模型索引。下面将详细描述和解释这些属性。

行和列

在最基本的形式中,模型可以被访问为一个简单的表,其中项目按行号和列号定位。这并不意味着底层数据存储在数组结构中。使用行号和列号只是允许组件相互通信的约定。我们可以通过在模型中指定任意物品的行号和列号来获取它的信息,并得到表示该物品的索引:

QModelIndex index = model->index(row, column, ...);

为列表和表等简单的单层数据结构提供接口的模型不需要提供任何其他信息,但是,如上代码所示,在获取模型索引时,我们需要提供更多的信息。

行和列
该图显示了一个基本表模型的表示,其中每个项目通过一对行号和列号定位。通过将相关的行号和列号传递给模型,我们获得一个引用数据项的模型索引。

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());

模型中的顶级项总是通过指定QModelIndex()作为它们的父项来引用。这将在下一节中讨论。

项目(items)的父元素

当在表或列表视图中使用数据时,模型提供的类表接口是理想的。行和列的编号系统精确地映射到视图显示项目的方式。然而,像树视图这样的结构要求模型向其中的项公开更灵活的接口。因此,每个元素项也可以是另一个元素表的父元素,就像树视图中的顶层元素项可以包含另一个元素表一样。

在为模型元素请求索引时,必须提供元素父元素的一些信息。在模型外部,只有通过模型索引才能引用元素,因此还必须提供一个父模型索引:

QModelIndex index = model->index(row, column, parent);

父元素、行和列
该图显示了一个树模型的表示,其中每个项都由一个父项、一个行号和一个列号引用。

元素"A"和"C"在模型中被表示为顶层的兄弟元素:

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());

A项有几个子项。项目“B”的模型索引由以下代码获得:

QModelIndex indexB = model->index(1, 0, indexA);

项目角色

模型中的项可以为其他组件执行不同的角色,从而允许为不同的情况提供不同类型的数据。例如,Qt::DisplayRole用于访问可以在视图中显示为文本的字符串。通常,项包含许多不同角色的数据,标准角色由Qt::ItemDataRole定义。
我们可以向模型请求项目的数据,方法是将项目对应的模型索引传递给模型,并指定一个角色来获得我们想要的数据类型:

QVariant value = model->data(index, role);

项目角色
角色指示模型引用的数据类型。视图可以以不同的方式显示角色,因此为每个角色提供适当的信息很重要。

创建新模型部分更详细地介绍了角色的一些特定用途。

项目数据的最常见用途是由Qt::ItemDataRole中定义的标准角色覆盖的。通过为每个角色提供适当的项目数据,模型可以向视图和委托提供提示,说明项目应该如何呈现给用户。不同类型的视图可以根据需要自由地解释或忽略此信息。还可以为特定于应用程序的目的定义其他角色

总结

  • 模型索引以一种独立于任何底层数据结构的方式向视图和委托提供关于模型所提供的项目位置的信息。
  • 元素项通过行号和列号以及父元素项的model索引进行引用。
  • 模型索引是由模型根据其他组件(如视图和委托)的请求构建的。
  • 如果在使用index()方法请求索引时为父元素指定了有效的模型索引,则返回的索引指向模型中父元素下面的元素。获得的索引指向该项的一个子项。
  • 如果在使用index()方法请求索引时,为父元素指定了无效的模型索引,则返回的索引指向模型中的顶层元素。
  • 角色区分与项相关联的不同类型的数据。

使用模型索引

为了演示如何使用模型索引从模型中检索数据,我们设置了一个没有视图的QFileSystemModel,并在一个小部件中显示文件和目录的名称。虽然这不是使用模型的正常方式,但它展示了模型在处理模型索引时使用的约定。

QFileSystemModel的加载是异步的,以最小化系统资源使用。在处理这个模型时,我们必须考虑到这一点。

我们用以下方式构建文件系统模型:

    QFileSystemModel *model = new QFileSystemModel;
    connect(model, &QFileSystemModel::directoryLoaded, [model](const QString &directory) {
        QModelIndex parentIndex = model->index(directory);
        int numRows = model->rowCount(parentIndex);
    });
    model->setRootPath(QDir::currentPath);

在这种情况下,我们首先设置一个默认的QFileSystemModel。我们将它连接到一个lambda,使用该模型提供的index()的特定实现来获取父索引。在lambda表达式中,我们使用rowCount()函数计算模型的行数。最后,我们设置QFileSystemModel的根路径,让它开始加载数据并触发lambda表达式。

为简单起见,我们只对模型第一列中的项感兴趣。我们依次检查每一行,获取每行中第一个项目的模型索引,并读取存储在模型中该项目的数据。

    for (int row = 0; row < numRows; ++row) {
        QModelIndex index = model->index(row, 0, parentIndex);

为了获得模型索引,我们指定行号、列号(第一列为零),以及我们想要的所有项的父项的适当模型索引。使用模型的data()函数检索存储在每个条目中的文本。我们指定模型索引和DisplayRole以字符串的形式获取项目的数据。

    QString text = model->data(index, Qt::DisplayRole).toString();
        // Display the text in a widget.

    }

上面的例子演示了从模型中检索数据的基本原则:

  • 使用rowCount()和columnCount()可以得到模型的维度。这些函数通常需要指定一个父模型索引。
  • 模型索引用于访问模型中的项。指定项目需要行、列和父模型索引。
  • 要访问模型中的顶层元素,可以用QModelIndex()指定一个空的模型索引作为父索引。
  • 项目包含不同角色的数据。要获取特定角色的数据,必须向模型提供模型索引和角色。

创建新模型

模型/视图组件之间的功能分离,允许创建可以利用现有视图的模型。这种方法允许我们使用标准的图形用户界面组件(如QListView、QTableView和QTreeView)来表示来自各种来源的数据。

QAbstractItemModel类提供了一个足够灵活的接口来支持在层次结构中安排信息的数据源,允许数据被插入、删除、修改或以某种方式排序。它还支持拖放操作。

QAbstractListModel和qabstractttablemodel类提供了对更简单的非层次数据结构的接口支持,并且更容易作为简单列表和表模型的起点使用。

在本节中,我们创建一个简单的只读模型来探索模型/视图架构的基本原理。在本节的后面,我们将修改这个简单的模型,让用户可以修改物品。

有关更复杂模型的示例,请参见简单树模型示例。

QAbstractItemModel子类的需求在模型子类化参考文档中有更详细的描述。

设计模型

在为现有数据结构创建新模型时,重要的是要考虑使用哪种类型的模型来为数据提供接口。如果数据结构可以表示为一个列表或项目表,那么您可以子类化QAbstractListModel或qabstractttablemodel,因为这些类为许多函数提供了合适的默认实现。

然而,如果底层数据结构只能用层次树结构表示,则有必要将QAbstractItemModel子类化。在简单的树模型示例中采用了这种方法。

在本节中,我们基于字符串列表实现了一个简单的模型,因此QAbstractListModel提供了一个理想的基类来进行构建。

无论底层数据结构采用何种形式,在特殊模型中使用允许更自然地访问底层数据结构的API来补充标准QAbstractItemModel API通常是一个好主意。这使得用数据填充模型变得更容易,但仍然允许其他通用模型/视图组件使用标准API与之交互。下面描述的模型为此提供了一个自定义构造函数。

只读的示例模型

这里实现的模型是一个简单的、非层次的、只读的数据模型,基于标准的QStringListModel类。它有一个QStringList作为它的内部数据源,并且只实现了建立一个有效模型所需的功能。为了让实现更容易,我们创建了一个子类QAbstractListModel,因为它为列表模型定义了合理的默认行为,而且它公开了一个比qabstracttemmodel类更简单的接口。

当实现一个模型时,重要的是要记住,QAbstractItemModel本身不存储任何数据,它只是提供了一个接口,视图使用它来访问数据。对于最小只读模型,只需要实现一些函数,因为大多数接口都有默认实现。类声明如下:

class StringListModel : public QAbstractListModel
{
    Q_OBJECT

public:
    StringListModel(const QStringList &strings, QObject *parent = nullptr)
        : QAbstractListModel(parent), stringList(strings) {}

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role) const override;
    QVariant headerData(int section, Qt::Orientation orientation,
                        int role = Qt::DisplayRole) const override;

private:
    QStringList stringList;
};

除了模型的构造函数,我们只需要实现两个函数:rowCount()返回模型的行数,data()返回与指定模型索引对应的数据项。

表现良好的模型还实现了headerData()方法,为树视图和表视图提供在它们的标题中显示的内容。

请注意,这是一个非层次模型,因此我们不必担心父子关系。如果我们的模型是分层的,我们还必须实现index()和parent()函数。

字符串列表存储在内部私有成员变量stringList中。

模型的尺寸

我们希望模型中的行数与字符串列表中的字符串数相同。我们实现rowCount()函数时就考虑到了这一点:

int StringListModel::rowCount(const QModelIndex &parent) const
{
    return stringList.count();
}

由于模型是非层次化的,我们可以放心地忽略与父项对应的模型索引。默认情况下,从QAbstractListModel派生的模型只包含一列,因此我们不需要重新实现columnCount()函数。

模型头文件和数据

对于视图中的项目,我们希望返回字符串列表中的字符串。data()函数负责返回与index参数对应的数据项:

QVariant StringListModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    if (index.row() >= stringList.size())
        return QVariant();

    if (role == Qt::DisplayRole)
        return stringList.at(index.row());
    else
        return QVariant();
}

只有当提供的模型索引有效,行号在字符串列表的范围内,并且请求的角色是我们支持的角色时,我们才返回一个有效的QVariant。

有些视图,如QTreeView和QTableView,能够同时显示标题和项目数据。如果我们的模型显示在带有表头的视图中,我们希望表头显示行号和列号。我们可以通过继承headerData()函数来提供关于标题的信息:

QVariant StringListModel::headerData(int section, Qt::Orientation orientation,
                                     int role) const
{
    if (role != Qt::DisplayRole)
        return QVariant();

    if (orientation == Qt::Horizontal)
        return QStringLiteral("Column %1").arg(section);
    else
        return QStringLiteral("Row %1").arg(section);
}

同样,仅当角色是我们支持的角色时,才返回有效的QVariant。在决定返回的确切数据时,首部的方向也要考虑在内。

并不是所有的视图都显示带有项目数据的标题,那些这样做的视图可能被配置为隐藏它们。尽管如此,还是推荐你实现headerData()函数,以提供模型提供的数据的相关信息。

一个项目可以有多个角色,根据指定的角色提供不同的数据。在我们的模型中,项目只有一个角色,即DisplayRole,因此无论指定的角色是什么,我们都返回项目的数据。不过,我们可以在其他角色中重用为DisplayRole提供的数据,比如在工具提示中显示项目信息的ToolTipRole。

一个可编辑的模型

只读模型展示了如何向用户提供简单的选择,但对于许多应用程序来说,可编辑列表模型更有用。要修改只读模型,可以修改为只读模型实现的data()函数,以及两个额外的函数:flags()和setData()。在类定义中添加了下列函数声明:

    Qt::ItemFlags flags(const QModelIndex &index) const override;
    bool setData(const QModelIndex &index, const QVariant &value,
                 int role = Qt::EditRole) override;

使模型可编辑

委托在创建编辑器之前检查项是否可编辑。模型必须让委托知道它的项目是可编辑的。为此,我们为模型中的每个元素返回正确的标志;在这种情况下,我们启用了所有元素,并使它们既可选择又可编辑:

Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::ItemIsEnabled;

    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}

注意,我们不需要知道委托是如何执行实际的编辑过程的。我们只需要为委托提供一种方法来设置模型中的数据。这是通过setData()函数实现的:

bool StringListModel::setData(const QModelIndex &index,
                              const QVariant &value, int role)
{
    if (index.isValid() && role == Qt::EditRole) {

        stringList.replace(index.row(), value.toString());
        emit dataChanged(index, index, {role});
        return true;
    }
    return false;
}

插入和删除行

可以更改模型中的行数和列数。在字符串列表模型中,只改变行数是有意义的,所以我们只重新实现插入和删除行的函数。这些在类定义中声明:

    bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
    bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;

因为这个模型中的行对应于列表中的字符串,所以insertRows()函数会在指定位置之前插入一些空字符串。插入的字符串数等同于指定的行数。

父索引通常用于确定应该将行添加到模型中的何处。在这个例子中,我们只有一个字符串的顶级列表,所以我们只是在该列表中插入空字符串。

bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)
{
    beginInsertRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        stringList.insert(position, "");
    }

    endInsertRows();
    return true;
}

模型首先调用beginInsertRows()函数通知其他组件行数即将改变。该函数指定要插入的第一行和最后一行的行号,以及它们父项的model索引。修改字符串列表后,它调用endInsertRows()来完成操作,并通知其他组件模型的维度发生了变化。返回true表示成功。

从模型中删除行的函数也很容易编写。要从模型中删除的行由给定的位置和行数指定。为了简化实现,我们忽略了父索引,只从字符串列表中删除相应的项。

bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent)
{
    beginRemoveRows(QModelIndex(), position, position+rows-1);

    for (int row = 0; row < rows; ++row) {
        stringList.removeAt(position);
    }

    endRemoveRows();
    return true;
}

beginRemoveRows()函数总是在删除任何底层数据之前调用,并指定要删除的第一行和最后一行。这允许其他组件在数据不可用之前访问数据。删除行之后,模型发出endRemoveRows()来完成操作,并让其他组件知道模型的维度已经更改。

下一个步骤

我们可以使用QListView类以垂直列表的形式显示该模型或任何其他模型提供的数据。对于string list模型,这个视图还提供了一个默认编辑器,以便对项目进行操作。我们在视图类中检查标准视图类可用的可能性。

模型子类化参考文档更详细地讨论了QAbstractItemModel子类的需求,并提供了在不同类型的模型中启用各种特性必须实现的虚函数的指南。

Model/View Programming | Qt Widgets 5.15.17

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

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

相关文章

ListBox显示最新数据、左移和右移操作

1、程序 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using static Sys…

《餐饮世界》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问&#xff1a;《餐饮世界》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《餐饮世界》级别&#xff1f; 答&#xff1a;国家级。主管单位&#xff1a; 中国商业联合会 主办单位&am…

b√最大矩阵和

题目描述 给定一个二维整数矩阵&#xff0c;要在这个矩阵中选出一个子矩阵. 使得这个子矩阵内所有的数字和尽量大&#xff0c;我们把这个子矩阵称为和最大子矩阵 子矩阵的选取原则是原矩阵中一块相互连续的矩形区域。 输入描述 输入的第一行包含2个整数n,m(1< n,m< 10…

Mysql连接不上的问题?

Mysql服务器本地能访问&#xff0c;但是外部连接报错如下&#xff1a;显然我也知道这就是一个权限问题&#xff0c;但是在网上百度的方法要么就是不生效&#xff0c;要么就是执行命令报错&#xff0c;很抓狂&#xff5e;这里提供精准的解决方案&#xff1a;SELECT User, Host F…

EV代码签名证书签名指南,签名要求、签名步骤一览

作为软件开发者&#xff0c;在软件分发之前&#xff0c;为软件应用程序进行代码签名&#xff0c;可标识开发者身份&#xff0c;消除“未知发布者”警告&#xff0c;确保代码完整性&#xff0c;有利于应用程序安全分发&#xff0c;也可以让用户放心下载。而为软件应用程序进行代…

【C语言从不挂科到高绩点】17-C语言中的宏定义

Hello&#xff01;彦祖们&#xff0c;俺又回来了&#xff01;&#xff01;&#xff01;&#xff0c;继续给大家分享 《C语言从不挂科到高绩点》课程!! 本节将为大家讲解C语言中的函数&#xff1a; 本套课程将会从0基础讲解C语言核心技术&#xff0c;适合人群&#xff1a; 大学…

取消Cursor的注释斜体字风格

1. 打开settings.json 2. 添加如下代码 "editor.tokenColorCustomizations": {"textMateRules": [{"name": "Comment","scope": ["comment","comment.block","comment.block.documentation"…

Vert.x HttpClient调用后端服务时使用Idle Timeout和KeepAlive Timeout的行为分析

其实网上有大量讨论HTTP长连接的文章&#xff0c;而且Idle Timeout和KeepAlive Timeout都是HTTP协议上的事情&#xff0c;跟Vert.x本身没有太大关系&#xff0c;只不过最近在项目上遇到了一些问题&#xff0c;用到了Vert.x的HttpClient&#xff0c;就干脆总结一下&#xff0c;留…

从Apple Intelligence到IoT Intelligence,端侧生成式AI时代加速到来

9月10日凌晨1点&#xff0c;苹果新品发布会如期举行&#xff0c;全新iPhone16系列成为苹果生态中真正意义上的第一款原生AI手机&#xff0c;在第二代3nm工艺A18和A18 Pro芯片的加持下&#xff0c;iPhone16系列能够容纳并快速运行以Apple Intelligence为中心的生成式AI功能在手机…

铭顺元宇宙时代到来,数字人应用案例分享

近年来&#xff0c;随着技术的不断发展&#xff0c;数字人的功能和表现力也在不断提升&#xff0c;形形色色的虚拟数字人正代替真人&#xff0c;扮演着代言人、主播、客服和智能助理的角色&#xff0c;涉及文旅、电商、金融等多个行业。作为随着AI技术在数字人产业中的发展&…

远程桌面内网穿透是什么?有什么作用?

远程桌面内网穿透指的是通过特定技术手段&#xff0c;将处于内网中的电脑或服务器&#xff0c;通过外部网络&#xff08;互联网&#xff09;进行访问。内网穿透的主要作用是解决在内网环境下&#xff0c;远程设备与外部互联网之间的连接问题&#xff0c;允许用户从外部访问内网…

硬核,这款小而美的国产操作系统开源了!(带私活源码)

今天给大家介绍的是硬核的国产物联网操作系统 RT-Thread&#xff0c;内容很硬核&#xff0c;可以让大家捡起一些大学期间学到的知识&#xff0c;也能让自己对于操作系统有更多的理解。 项目介绍 RT-Thread 诞生于 2006 年&#xff0c;是一款以开源的物联网操作系统。主要采用…

07 vue3之组件及生命周期

组件基础 每一个.vue 文件呢都可以充当组件来使用 每一个组件都可以复用 组件的生命周期 简单来说就是一个组件从创建 到 销毁的 过程 成为生命周期 在我们使用Vue3 组合式API 是没有 beforeCreate 和 created 这两个生命周期的 onBeforeMount() 在组件DOM实际渲染安装之前…

一个未解决的漏洞:actuator字符绕过漏洞

最近遇到了安全部门派发的actuator泄漏漏洞&#xff0c;领导希望不暴露到外网上&#xff0c;对于内网需要认证才可以访问。 要想不暴露到外网上&#xff0c;就需要在网络层面做拦截&#xff0c;比如nginx和apisix上做代理配置。 一般的情况都可以应对&#xff0c;就是对于http…

CentOS镜像源更新

如果 CentOS 7.9 的官方镜像源已不维护&#xff0c;你可以使用以下方法更新&#xff1a; 切换到其他镜像源&#xff1a;使用 CentOS 镜像站点或第三方镜像源&#xff0c;如 EPEL&#xff08;Extra Packages for Enterprise Linux&#xff09;。修改 /etc/yum.repos.d/CentOS-Ba…

Web大学生网页作业成品——动漫火影忍者网页设计与实现(HTML+CSS+JS)(5个页面)

&#x1f389;&#x1f389;&#x1f389; 常见网页设计作业题材有**汽车、环保、明星、文化、国家、抗疫、景点、人物、体育、植物、公益、图书、节日、游戏、商城、旅游、家乡、学校、电影、动漫、非遗、动物、个人、企业、美食、婚纱、其他**等网页设计题目, 可满足大学生网…

终于有一本书把大模型背后的Transformer模型究竟是什么一次性说清楚了!

前言 ChatGPT红得发紫&#xff0c;强得让人类心悸。但在它的背后&#xff0c;还隐藏着一位真正的大佬。它的名字叫做——Transformer! 这本书全面介绍了最新的Transformer模型在自然语言处理中的应用方法和技巧&#xff0c;包括原理、实现方法和各种任务的应用&#xff0c;提供…

WebSocket和HTTP协议有什么区别

WebSocket 支持端对端通信可由client发起&#xff0c;也可由sever发起用于消息通知、直播间讨论区、聊天室、协同编辑 WebSocket连接过程 先发起一个HTTP请求成功之后在升级到WebSocket协议&#xff0c;再通讯 WebSocket和HTTP区别 WebSocket协议名是ws://&#xff0c;可双…

C语言存储类型 auto,register,static,extern

目录 1. auto 存储类型 1.1 自动变量特性 1.2 举例 2. register 存储类型 2.1 寄存器变量特性 2.2 举例 3. extern 存储类型 3.1 extern 存储类型特性 3.2 举例 3.2.1 extern全局变量 ​编辑 3.2.2 extern函数 4. static 存储类型 4.1 static 存储类型特性 4.2 举…

克雷格·费德里吉谈Apple Intelligence保密技术背后的挑战

苹果必须实现克雷格-费德里吉所说的突破&#xff0c;这样 Apple Intelligence公司才能在云中使用大型语言模型&#xff0c;同时还能保护用户隐私&#xff0c;苹果是这样做的。在"It’s Glowtime"活动中&#xff0c;苹果公司谈到了私有云计算作为保护用户隐私的方式。…