QT学习笔记-Qt Model/View


前言

因工作需要,要实现一个小型的数据管理系统。本着想捡起来QT技能的想法,学习下QT模型视图的操作。


一、Qt模型视图结构简介

模型/视图结构,仍然分离了数据与呈现给用户的方式,但提供了基于相同原理的简单框架。这种分离使得它可以在几个不同的视图中显示相同的数据,并且实现新类型的视图,而无需改变底层的数据结构。为了灵活地处理用户输入,则引入了委托的概念。在此框架引入委托的优点是:它允许项目数据显示和自定义编辑。
在这里插入图片描述

  • 模型与数据源进行通信,在这个体系结构中为其它组件提供了一个接口。通信的性质依赖于数据源的类型以及模型的实现方式。
  • 视图从模型中得到模型索引,这些都引用到数据项。通过为模型提供模型索引,视图可以从数据源中检索数据项。
  • 在标准的视图里,委托呈现数据项目。当一个项目被编辑,委托与模型直接利用模型索引进行通信。
    模型/视图/委托通信
    模型的信号:通知视图关于数据源保持的数据发生的变化。
    视图的信号:提供了关于用户交互显示的项目信息。
    委托的信号:当编辑时告诉模型和视图编辑器的状态。

二、Qt模型

概念

所有的模型都基于QAbstractItemModel类。这个类定义了一个使用视图和委托来访问数据的接口。数据本身不是必须要存储在模型中,可以在一个数据结构或一个单独的类、文件、数据库、或其它一些应用组件。
QAbstractItemModel为数据提供了一个接口,它拥有足够的灵活性来处理表格、列表、树形式的数据视图。然而,实现新的列表和类似于表的数据结构模型时,QAbstractListModel和QAbstractTableModel类是更好的起点,因为它们提供了适当的常用功能的默认实现。这些类可以派生子类,用来提供支持特定种类的列表和表格的模型。
Qt提供了一些现成的模型,可以用来处理数据项:

  • QStringListModel:用于存储简单的QString的列表项。
  • QStandardItemModel:管理更复杂的树结构件,其中每一个项目可以包含任意数据。
  • QFileSystemModel:提供有关本地文件系统的文件和目录信息。
  • QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel:使用模型/视图约定来访问数据库。
    在模型/视图结构中,模型提供了视图与委托访问数据的标准接口。在Qt中,标准的接口由QAbstractItemModel类定义。无论数据项存储在什么底层的数据结构中,QAbstractItemModel的所有子类所代表的数据都被看作包含视图项的分层结构。视图使用这个约定来访问模型中的数据项,但并不限制将该信息传达给用户的方式。
    在这里插入图片描述

Model indexes

为确保数据的展示与获取是分离的,引入模型索引的概念。我们可以通过模型索引来获得每条信息。视图与委托使用这些索引来请求显示的数据项。
因此,只有模型需要知道如何获取数据,通过模型管理的数据的类型可以被相当普遍定义。模型索引包含一个指向创建它们的模型的指针,这样在处理多个模型时可以防止混乱。

 QAbstractItemModel *model = index.model();

模型索引提供临时参考信息,并且可以用于通过模型来检索或修改数据。由于模型可能重组其内部结构,模型的索引可能会变得无效,不宜存储。如果需要长期参考一条信息,必须创建一个持久性模型索引。这为模型保持最新信息提供了一个参考。临时模型索引由QModelIndex类提供,持久性模型索引由QPersistentModelIndex类提供。
取得对应于数据项的模型索引,模型中必须制定三个属性:一个行号、一个列号,以及父项的模型索引。

行列

在基础框架下,模型可以被当作一个简单的表格接入,其中数据项可以通过行列号定位。这并不意味着数据的底层结构也是序列结构;行列号的使用只是为了方便组件间的通信。我们可以通过行列号来检索模型对应数据项的信息。

父项

当在表格或列表视图中使用数据时,理想化方式是由模型提供类表接口给数据项;行号和列号准确地映射到视图对应的数据项。然而,如树视图一样的结构需要模型暴露一个更灵活的接口。因此,每个数据项也可以是另一个表的父项,大致相同的方式,在一个树视图中的顶级数据项可以包含另一个数据项列表。
当请求一个模型项的索引时,必须提供有关该数据项的父节点的一些信息。在模型外,访问一个数据项的唯一途径是通过一个模型索引,所以父节点索引也需要提供:

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

模型中的顶层节点通常将QModelIndex()指定为父节点。

数据项角色

模型中的数据项可以为其他组件演绎不同的角色,允许为不同的情况提供不同类型的数据。例如,Qt::DisplayRole可以在用于访问视图中被显示为文本的字符串。通常情况下,包含数据的项目用于若干不同的角色,且标准角色被Qt::ItemDataRole定义。
我们可以通过对应的模型索引向模型请求数据项数据,并通过指定一个角色来获取想要的数据类型:

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

在这里插入图片描述

三、Qt视图

概念

模型视图架构中,视图用来从模型中获取数据项,并展示给用户。视图所展示的结构不必与模型完全一致,甚至可以与底层的数据存储结构截然不同。
视图通常管理从模型中获取的数据的总体布局。它们可以自己渲染单个数据项,或者使用代理处理渲染和编辑功能。除了显示数据,视图还处理数据项之间的导航以及数据项选择的某些方面。这些视图还实现了基本的用户界面功能,例如上下文菜单和拖放。视图可以为数据项提供默认编辑功能,也可以与委托一起提供自定义编辑器。
视图可以在没有模型的情况下构建,但必须先提供模型,然后才能显示有用的信息。视图通过使用selections跟踪用户选择的数据项,这些选项可以为每个视图单独维护,或在多个视图之间共享。
某些视图(如QTableView和QTreeView)可以同时显示标题和数据项。这些也由视图类QHeaderView实现。标题通常访问与包含它们的视图相同的模型。它们使用QAbstracItemModel::headerData()函数从模型中检索数据,通常以标签的形式显示标题信息。可以从QHeaderView类中对新标题进行子类化,以便为视图提供更专门的标签。

使用已有视图

Qt提供了三个现成的视图类,它们以大多数用户熟悉的方式显示模型中的数据。QListView可以将模型中的项目显示为简单列表,或以经典图标视图的形式显示。QTreeView将模型中的项目显示为列表的层次结构,允许以紧凑的方式表示深度嵌套的结构。QTableView以表格的形式显示模型中的项,非常类似于电子表格应用程序的布局。
在这里插入图片描述
对于大多数应用程序来说,上面显示的标准视图的默认功能应该足够了。它们提供基本的编辑功能,并且可以定制以满足更专业的用户界面的需要。

使用模型

使用模型的方法很简单,构建一个模型,传入一些数据,再构建一个视图来显示模型内容。

view->setModel(model);

操作被选项

视图被选项的操作机制由QItemSelectionModel类提供。所有标准视图都有自己的默认选择模型,和交互方式。视图的选择模型可以通过selectionModel() 函数获取,也可通过setSelectionModel()函数对选择模型进行替换。

四、Qt委托

与MVC模式不同,模型视图设计并没有包含一个完整的分离组件来管理用户的交互。通常视图承担了向用户展示模型的数据,与处理用户输入的功能。为了在获取输入的方式上有一定的灵活性,交互可以由委托执行。这些组件提供输入功能,还负责在某些视图中呈现单个项数据项。用于控制委托的标准接口在QAbstractItemDelegate类中定义。
通过实现paint()sizeHint()函数,我们期望委托能够自己呈现其内容。然而,简单的基于widget的委托可以通过将QItemDelegate而不是QabstratemDelegate子类化实现,并利用这些函数的默认实现。
委托编辑器可以通过使用widget来管理编辑过程,也可以通过直接处理事件来实现。

使用已有委托

Qt提供的标准视图使用QItemDelegate的实例提供编辑功能。委托接口的默认实现为每个标准视图(QListView、QTableView和QTreeView)以通常的样式呈现数据项。
所有标准角色都由标准视图使用的默认委托处理。
视图使用的委托由itemDelegate()函数返回。setItemDelegate()函数允许为标准视图安装自定义委托,在为自定义视图设置委托时需要使用此函数。

一个简单的自定义委托

在这里插入图片描述
这个委托使用一个Qspinbox来提供编辑功能,主要用来使模型显示整数。
我们的委托继承自 QStyledItemDelegate,因为我们不需要自己实现显示函数,然而我们仍需要提供函数来管理编辑控件。

 class SpinBoxDelegate : public QStyledItemDelegate
 {
     Q_OBJECT

 public:
     SpinBoxDelegate(QObject *parent = 0);

     QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                           const QModelIndex &index) const override;

     void setEditorData(QWidget *editor, const QModelIndex &index) const override;
     void setModelData(QWidget *editor, QAbstractItemModel *model,
                       const QModelIndex &index) const override;

     void updateEditorGeometry(QWidget *editor,
         const QStyleOptionViewItem &option, const QModelIndex &index) const override;
 };

注意委托的构造函数中不构造编辑器,仅当需要时再构建编辑器。
提供编辑器

 QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
    const QStyleOptionViewItem &/* option */,
    const QModelIndex &/* index */) const
{
    QSpinBox *editor = new QSpinBox(parent);
    editor->setFrame(false);
    editor->setMinimum(0);
    editor->setMaximum(100);

    return editor;
}

注意我们不需要保留编辑器组件的指针,视图在不再使用时会自动回收。
我们给编辑器安装委托的默认事件过滤器来确保它能提供用户期望的标准编辑快捷方式。
视图通过调用我们稍后定义的函数来确保编辑器的数据和几何图形设置正确。我们可以根据视图提供的模型索引创建不同的编辑器。例如,如果我们有一列整数和一列字符串,我们可以返回QSpinBox或QLineEdit,这取决于正在编辑的列。
委托必须提供函数来将模型数据复制到编辑器中。在这个例子中,代码如下:

void SpinBoxDelegate::setEditorData(QWidget *editor,
                                    const QModelIndex &index) const
{
    int value = index.model()->data(index, Qt::EditRole).toInt();

    QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
    spinBox->setValue(value);
}

在本例中,我们知道编辑器是一个spinbox,但我们可以为模型中不同类型的数据提供不同的编辑器,在这种情况下,我们需要在访问其成员函数之前将编辑器转换为适当的类型。
向模型提交数据
当用户完成编辑时,视图将通过调用setModelData()函数使委托把编辑后的值存储到模型中。

void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
                                  const QModelIndex &index) const
{
   QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
   spinBox->interpretText();
   int value = spinBox->value();

   model->setData(index, value, Qt::EditRole);
}

由于视图在管理着编辑器不间,我们只需要使用编辑器提供的内容更新模型。在本例中,我们确保spin box是最新的,并使用指定的索引使用它包含的值更新模型。
标准的QItemDelegate类在完成编辑时通过发出closeEditor()信号通知视图。该视图确保编辑器小部件被关闭并销毁。在这个例子中,我们只提供简单的编辑功能,所以我们永远不需要发出这个信号。
所有对数据的操作都是通过QAbstracteModel提供的接口执行的。这使得委托基本上独立于它所处理的数据类型,但为了使用某些类型的编辑器小部件,必须做出一些假设。在本例中,我们假设模型始终包含整数值,但我们仍然可以将此委托用于不同类型的模型,因为QVariant为意外数据提供了合理的默认值。
更新编辑器几何图形
委托负责管理编辑器的几何图形。创建编辑器时以及更改视图中项目的大小或位置时,必须设置几何图形。幸运的是,视图在视图选项对象内提供了所有必要的几何图形信息。

 void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
   const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
   editor->setGeometry(option.rect);
}

编辑提示
编辑完成后,委托应该向其他部件提供有关编辑过程结果的提示,并提供有助于后续编辑操作的提示。这是通过发出带有适当提示的closeEditor()信号来实现的。这由默认的QItemDelegate事件过滤器负责,我们在构建spin box时将其安装在spin box上。

五、创建模型

模型/视图组件之间的功能分离允许创建可以利用现有视图的模型。这种方法允许我们使用标准的图形用户界面组件(如QListView、QTableView和QTreeView)呈现来自各种来源的数据。
QAbstracteModel类提供了一个足够灵活的接口,支持以层次结构排列信息的数据源,允许以某种方式插入、删除、修改或排序数据。它还支持拖放操作。
QAbstractListModelQAbstractTableModel类为更简单的非层次数据结构提供接口支持,并且更容易用作简单列表和表模型的起点。

设计模型

在为现有数据结构创建新模型时,重要的是考虑应该使用哪种类型的模型来提供接口到数据上。如果数据结构可以表示为项目列表或表格,则可以将QAbstractListModelQAbstractTableModel子类化,因为这些类为许多函数提供了合适的默认实现。然而,如果底层数据结构只能由层次树结构表示,则有必要将QAbstractItemModel子类化。
无论底层数据结构采用何种形式,通常最好在专用模型中补充标准QabstracteModel API,使其能够更自然地访问底层数据结构。这使得用数据填充模型变得更容易,但仍然允许其他通用模型/视图组件使用标准API与之交互。
在实现模型时,重要的是要记住,QAbstracteModel本身并不存储任何数据,它只提供了一个接口,视图使用该接口访问数据。
只读模型
对于最小的只读模型,只需要实现几个函数,因为大多数接口都有默认实现。
对于只读模型来说除了模型的构造函数,我们只需要实现以下函数:rowCount()返回模型中的行数,如有必要还需实现列数函数 columnCount()data()返回与指定模型索引对应的数据项。通常我们也实现headerData()函数,为树视图和表视图提供一些可在其标题中显示的内容。
如果模型是有层次结构的,我们还需要实现index()parent()函数。
可编辑模型
可编辑模型需要在只读模型的基础上,修改下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 Model::flags(const QModelIndex &index) const
 {
     if (!index.isValid())
         return Qt::ItemIsEnabled;

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

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

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

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

设置数据后,模型必须让视图知道某些数据已更改。这是通过发出dataChanged()信号来完成的。
行列操作

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

搜索
在项目视图中查找项目通常很有用,无论是作为开发人员还是作为向用户提供的服务。所有三个item view便利类都提供了一个通用的findItems()函数,使其尽可能一致和简单。
根据从Qt::MatchFlags中选择的值所指定的条件,通过它们包含的文本搜索项目。我们可以使用findItems()函数获得匹配项的列表:

     QTreeWidgetItem *item;
     QList<QTreeWidgetItem *> found = treeWidget->findItems(
         itemText, Qt::MatchWildcard);

     foreach (item, found) {
         treeWidget->setItemSelected(item, true);
         // Show the item->text(0) for each item.
     }

如果树中的项目包含搜索字符串中给定的文本,则上述代码会导致选择这些项目。这个模式也可以用在列表和表格中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值