Qt Model/View

本文深入探讨Qt的模型视图架构,详细介绍了QAbstractItemModel及其子类的继承结构和功能,包括QStandardItemModel、QAbstractListModel、QAbstractTableModel和QAbstractProxyModel等。阐述了如何使用这些模型类来实现数据的存储、检索、编辑和展示。

Model/View

模型层继承结构

QAbstractItemModel的父类:QObject

  • QAbstractItemModel
    • QStandardItemModel
    • QAbstractListModel
    • QAbstractTableModel
    • QAbstrctProxyModel
    • QConcatenateTablesProxyModel
    • QDirModel
    • QFileSystemModel

QAbstractItemModel

**QAbstractItemModel:**定义标准接口,这些接口用于模型数据和其他model/view架构中的其他组件进行数据访问。

如果需要使用List或者Table类型的模型结构,建议继承QAbstractListModel或者QAbstractTableModel,而不推荐使用QAbstractItemModel。

对于模型结构中的item,每一个item都有其对应结构下的QModelIndex,可以通过index()函数来获取该item元素。

每一个index都有一个sibling() index(Qt6移除),每一个子item都有其parent() index。

每一个item都有其对应的role,比如:Qt::ItemDataRole。

可以调用setData()或者setItemData()来指定item的role,具体可查阅API文档。

可以调用flags()来查看item是否可以被selected、dragged或进行其他方式的操作。

可以调用hasChildren()函数来查询对应QModelIndex的item是否有子item。

可以调用rowCount(),columnCount()来查询每一层级上的行列个数,因为items可能以层级结构一层一层进行组织存放。可以调用insertRows()、insertColumns()、removeRows()、removeColumns()来插入或者移除元素。

可以调用match()函数来查找特定的数据。

可以调用sort()函数来对模型数据进行排序。

继承QAbstractItemModel时,至少需要实现index(),parent(),rowCount(),columnCount()和data()函数。

同时当rowCount()的实现比较expensive时,可以重新实现hasChildren()函数来提供特殊的模型的特殊存储方式。通过这种方式,可以约束视图展现数据的多少,达到数据的懒加载

可以实现setData来确保item可以编辑,同时重新实现flags函数来确保这个item的编辑状态(ItemIsEditable)是正常的。也可以实现headerData和setHeaderData函数来控制模型展现数据头的方式。

当重新实现setData()函数和setHeaderData()函数时,必须显示发送(emit) dataChanged()或者headerDataChanged()信号。

对于自定义模型,必须创建模型索引来提供数据访问,可以调用createIndex()函数来为item指定对应的QModelIndex。

模型底层数据结构的重定义:

  • insertRows():在插入新行到数据结构之前需要调用beginInsertRows(),在插入结束立马调用endInsertRows()
  • insertColumns():同上需要调用beginInsertColumns()和endInsertColumns()
  • removeRows():同上需要调用beginInsertRows()和endInsertRows()
  • removeColumns():同上需要调用beginRemoveColumns()和endRemoveColumns()

在自定义模型的底层数据存取形式时,调用beginXXX()和endXXX()函数的原因是保证数据持久化正常。

如果对于item本身来说,其下面还有层级结构的数据,Qt所提供的方式是会对item进行insert或者remove时,Qt会自动管理item下层的数据结构。

QStandardItemModel

Item的基类:QStandardItem

实现了QAbstractItemModel接口,对于QListView、QTableView、QTreeView或者自定义实现的这些View类(这些View内部都使用了QAbstractItemModel接口),他们可以直接使用QStandardItemModel或者其子类来作为自己的模型数据。为了性能和灵活性考虑,可以使用QAbstractItemModel来提供更加多样性的数据展现形式。

当想要一个list或者tree,可以创建一个空的QStandardItemModel,并使用appendRow()函数来增加数据到model中,然后调用item()函数来对item进行访问。如果想要model来以table的方式存储数据,可以将维度作为参数传导QStandardItemModel的构造函数中,然后调用setItem函数来讲item设置到特定的位置。也可以调用setRowCount或者setColumnCount函数来修改模型的维度。可以调用insertRows和insertColumn来插入item,调用removeRows和removeColumns来移除元素。可以调用setHorizontalHeaderLabels和setVerticalHeaderLabels来设置模型的头标签。可以调用findItems来查找元素,也可以调用sort函数来对模型数据进行排序。调用clear函数来移除model中的所有item。

QAbstractListModel

介绍

存储那种简单的没有继承序列item的结构,不能直接使用,必须继承以供使用。

由于QAbstractListModel相较于QAbstractItemModel有了更特定的接口函数,因此不适合在需要非List模型数据的view中使用,比如tree views。

继承

继承QAbstractListModel,必须实现rowCount()函数和data(),比较合理的实现是还是需要提供headerData()的实现。

如果对于可编辑的list model,必须提供setData的实现,同时实现flags(),如此便可以返回Qt::ItemIsEditable。

Note:QAbstractListModel提供了一个columnCount()函数的默认的实现,可以告知views:只有一个单列的items在模型中。

提供接口insertRows()和removeRows()来向模型中插入数据,使用前必须调用beginXXX()和endXXX()来确保数据的持久化存储正确。

QAbstractTableModel

介绍

类似于QAbstractTableModel,只是数据的存储维度不一样。

继承

类似需要的实现和QAbstractTableModel差不太多。

QAbstractProxyModel

介绍

定义了标准接口使得代理模型可以用来model/view组件进行正确的交互操作。不支持直接初始化。

Qt中已有的实现的所有的标准代理模型继承自QAbstractProxyModel类,如果需要创建新的代理模型类,则需要继承一个已存在的代理类并且提供所想需要表达的函数接口来提供想要的行为表示。

用代理模型来对model中的items进行过滤或者排序需要内部包含或者继承QSortFilterProxyModel类,内部包含或者继承属于两种使用方式,可以酌情使用。

为了继承QAbstractProxyModel,需要实现mapFromSource()和mapToSource()接口。接口mapSelectionFromSource()和mapSelectionToSource()函数只需要在需要提供不同于默认行为的行为时才进行相应的实现。

QDirModel

不再建议使用

QFileSystemModel

介绍

该类提供了接口函数来访问本地文件系统,提供函数来重命名和移除文件和目录,创建目录等。在简单的案例中,该类可以作为浏览器或者过滤器组件的一部分。

QFileSystemModel可以使用QAbstractItemModel中的接口函数进行访问(因为由上面的继承结构可以看出:QFileSystemModel是从QAbstractItemModel继承来的),同时也提供了另外特定的接口函数来访问目录模型。fileInfo(),isDir(),fileName()和filePath()提供底层文件和目录模型结构的信息。可以调用mkdir()、rmdir()函数来创建或者移除目录。

Example

// 创建模型,指定根目录
QFileSystemModel* model=new FileSystemModel;
model->setRootPath(QDir::currentPath());

// 将模型和当前创建的Tree视图进行绑定
QTreeView* tree=new QTreeView(splitter);
tree->setModel(model);

// 设置根索引
tree->setRootIndex(model->index(QDir::currentPath()));

QConcatenateTablesProxyModel

介绍

QConcatenateTablesProxyModel占据多个模型并连接这多个模型的rows。

换句话说,代理会拥有第一个原模型的所有rows,接下去是第二个模型的rows,依此类推。

如果各个model的列数不一样,那么代理会按照最小列数,对模型进行连接。(那么我想,这种模型数据结构在什么业务场景下会有效???)其他多余的列会在连接的时候被忽略。

model可以在运行时被增加或者删除,列的数量会自动适应。

QStyledItemDelegate

介绍

当在Qt的views中展现model中的数据时,Qt使用delegate来对items进行绘制。当一个item是edited,Qt会为这个item提供editor widget。当编辑事件发生时,这个widget会放在对应的view对象的top位置。QStyledItemDelegated是Qt所有views的默认的delegate,在这些views被创建时就自动绑定了QStyledItemDelegate对象。

Qt引入delegate机制的原因是:将item的作用(编辑、展示

或者其他)、view展示model中的item这种业务逻辑相拆分,从而降低Qt组件间的耦合度,同时也方便了自定义model/view中的组件们。

model中的item数据都有一个ItemDataRole的属性变量,用来表征item的行为属性。每一个item可以为每一个role存储一个QVariant(Qt的一种变量类型)变量值。QStyledItemDelegate实现了程序员所需的绝大多数公共的数据类型(eg. booleans,integers,strings…)用来对item进行display和editing。

view会根据model中的item的role来决定item在view中的展现方式。

以下列举常用的Role和其对应的可以接受的数据类型:

RoleAccepted Types
Qt::BackgroundRoleQBrush
Qt::BackgroundColorQColor(obsolete;use Qt::BackgroundRole instead)
Qt::CheckStateRoleQt::CheckState
Qt::DecorationRoleQIcon,QPixmap,QImage,QColor
Qt::DisplayRoleQString and types with a string representation
Qt::EditRoleSee QItemEditorFactory for details
Qt::FontRoleQFont
Qt::SizeHintRoleQSize
Qt::TextAlignmentRoleQt::Alignment
Qt::ForegroundRoleQBush
Qt::TextColorRoleQColor(obsolete; use Qt::ForegroundRole instead)

对于Qt::EditRole做一点补充:Editors可以用QItemEditorFactory来创建,QItemEditor提供了一个静态实例,该实例默认被install到所有item的delegate上。程序员可以使用setItemEditorFactory()来设置一个自定义的工厂或者使用QItemEditorFactory::setDefaultFactory()来设置一个新的默认工厂对象。

继承QStyledItemDelegate

当delegate不支持所需绘制的数据类型或者程序员项自定义item的绘制方式,则可以通过继承QStyledItemDelegate的方式,继承后需要实现paint()、sizeHint()函数。需要为每个item调用paint()函数以获得不同的展示方式。通过sizeHint()函数,可以指定每一个item的hint(其实就是QSize)。

在需要使用一个自定义的delegate,其中的元素的role又具有edit属性时,除了使用之前提到的使用QItemEditorFactory的方式之外,可以采取不使用的方式,但是必须重新实现以下四个方法:

  • createEditor(): 返回模型中用来更改数据的widget,可以重新实现来自定义编辑行为
  • setEditorData(): 提供带有数据的widget以便操作
  • updateEditorGeometry(): 确保对于特定的view,editor控件能够正确展示
  • setModelData(): 将更新过后的数据返回给模型

具体实例可以参照 Qt Star Example

QT Model/View 是用于在 Qt 应用程序中显示和编辑数据的框架,基于 MVC(Model-View-Controller)设计模式,其中 Model 表示数据,View 表示用户界面,Controller 表示控制器。该框架提供了灵活的方式来显示和编辑数据,可使用多种不同视图呈现相同数据,还能轻松添加自定义视图[^1]。 ### 原理介绍 在 Model/View 架构中,Model 负责存储和管理数据,View 负责将数据呈现给用户,两者通过信号和槽机制进行通信。当 Model 中的数据发生变化时,会发出信号通知 View 更新显示;当用户在 View 中进行操作时,View 会将操作信息传递给 Model 进行相应处理。 ### 标准模型 Qt 提供了两个标准的模型: - **QStandardItemModel**:多用途模型,可用于表示 list、table、tree views 所需的各种不同数据结构,且持有数据[^2]。 - **QFileSystemModel**:维护相关的目录内容信息,本身不持有数据,仅是对本地文件系统中的文件与目录的描述。它是现成的模型,容易进行配置以利用现有的数据[^2]。 ### 数据角色与编辑 对于布尔值,可使用 Qt::CheckStateRole 并设置 Qt::ItemIsUserCheckable 标志,然后使用复选框来编辑该值。model 中的底层数据对于所有角色都是相同的,这使得将 model 与标准组件集成更加容易[^3]。 ### 委托与编辑功能 Qt 提供的标准视图使用 QItemDelegate 的实例提供编辑功能。委托接口的默认实现为每个标准视图(QListViewQTableViewQTreeView)以通常的样式呈现数据项。所有标准角色都由标准视图使用的默认委托处理。视图使用的委托由 itemDelegate() 函数返回,setItemDelegate() 函数允许为标准视图安装自定义委托,在为自定义视图设置委托时也需要使用此函数[^4]。 ### 示例代码 以下是一个简单的自定义委托示例,用于在 QTableView 中使用自旋框编辑数据: ```cpp #include <QAbstractItemModel> #include <QSpinBox> #include <QWidget> #include <QItemDelegate> class SpinBoxDelegate : public QItemDelegate { Q_OBJECT public: SpinBoxDelegate(QObject *parent = nullptr) : QItemDelegate(parent) {} QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override { QSpinBox *editor = new QSpinBox(parent); editor->setRange(0, 100); return editor; } void setEditorData(QWidget *editor, const QModelIndex &index) const override { int value = index.model()->data(index, Qt::EditRole).toInt(); QSpinBox *spinBox = static_cast<QSpinBox*>(editor); spinBox->setValue(value); } void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override { QSpinBox *spinBox = static_cast<QSpinBox*>(editor); spinBox->interpretText(); int value = spinBox->value(); model->setData(index, value, Qt::EditRole); } void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override { editor->setGeometry(option.rect); } }; ``` ### 使用指南 1. **选择合适的模型**:根据数据的类型和需求,选择合适的标准模型(如 QStandardItemModel 或 QFileSystemModel),或者创建自定义模型。 2. **创建视图**:使用 Qt 提供的标准视图(如 QListViewQTableViewQTreeView),或者创建自定义视图。 3. **设置模型与视图**:将模型设置给视图,使用视图的 setModel() 函数。 4. **处理编辑**:如果需要自定义编辑功能,可以使用委托。对于标准视图,可以使用默认委托,也可以使用 setItemDelegate() 函数安装自定义委托。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值