Qt扫盲-Qt Model/View 理论总结 [下篇]

Qt Model/View 理论总结 [下篇]

  • 一、处理I tem view 中的选择
    • 1. 概念
      • 1. 当前项目和已选项目
    • 2. 使用选择 model
      • 1. 选择项目
      • 2. 读取选区状态
      • 3. 更新选区
      • 4. 选择 model 中的所有项
  • 二、创建新 model
    • 1. 设计一个 model
    • 2. 只读示例 model
      • 1. model 的尺寸
      • 2. model 头和数据
    • 3. 可编辑的 model
      • 1. 使 model 可编辑
      • 2. 插入和删除行
      • 3. 下一个步骤
  • 三、基于 item 的 view 便捷类
    • 1. QListWidget
    • 2. QTreeWidget控件
    • 3. QTableWidget
    • 4. 共同的特征
      • 1. 隐藏的item
      • 2. 选择
      • 3. 搜索
  • 四、使用拖放项目 view
    • 1. 使用方便 view
      • 1. 使用 model / view 类
      • 2. 支持对项目进行拖放
      • 3. 对导出数据进行编码
      • 4. 将删除的数据插入到 model 中
      • 5. 解码导入数据
  • 五、代理 model
    • 1. 使用代理 model
    • 2. 自定义代理 model
      • 1. 自定义过滤 model
      • 2. 自定义排序 model
      • 3. model 子类化参考
  • 六、 model 子类化参考
    • 1. 项目数据处理
    • 2. 只读访问
      • 1. 可编辑项
      • 2. 可调整大小的 model
      • 3. model 数据的延迟填充
    • 3. 导航和 model 索引创建
      • 1. Parents and children
    • 4. 拖放支持和MIME类型处理
      • 1. MIME数据
      • 2. 接受丢弃的数据
      • 3. 方便的 view
    • 5. 针对大量数据的性能优化
  • 七、 model / view 类

一、处理I tem view 中的选择

1. 概念

item view 类中使用的选择 model 提供了基于 model / view 体系结构的选择的一般描述。尽管操作选择的标准类对所提供的项目 view 来说已经足够了,但是选择 model 允许您创建专门的选择 model ,以满足您自己的项目 model 和 view 的需求。

关于 view 中所选项的信息存储在 QItemSelectionModel 类的一个实例中。它为单个 model 中的项维护 model 索引,并且独立于任何 view 。由于一个 model 可以有多个 view ,因此可以在 view 之间共享选择,从而允许应用程序以一致的方式显示多个 view 。

选择由选择范围组成。这些方法通过仅记录每个选定项范围的起始和结束 model 索引来有效地维护关于大量选择项的信息。通过使用多个选择范围来描述选择,构建非连续的选择项集;

选择应用于选择 model 持有的 model 索引集合。最近选择的应用项称为当前选择。这种选择的效果即使在应用后也可以通过使用某些类型的选择命令进行修改。本节稍后将讨论这些内容。

1. 当前项目和已选项目

在 view 中,总是有一个当前项和一个选定项——两个独立的状态。一个项目可以是当前项目,同时选择。 view 负责确保始终有一个当前项,例如,键盘导航需要一个当前项。
下表突出显示了当前项和选定项之间的差异。

当前的项目 选定的项目
当前项只能有一个。 可以有多个选择项。
当前项目将随着按键导航或鼠标按钮点击而改变。 当用户与项目交互时,项目的选择状态是被设置还是未设置,这取决于几种预定义的模式——例如,单选择、多选择等。
如果按下编辑键F2或双击项目,当前项目将被编辑(前提是启用编辑)。 当前元素可以和锚点一起使用,以指定选择或取消选择的范围(或两者的结合)。
当前项由焦点矩形表示。 选中的项目用选择矩形表示。

在操作选择时,通常可以将QItemSelectionModel视为一个项目 model 中所有项目的选择状态的记录。一旦建立了选择 model ,就可以选择、取消选择元素,或者切换它们的选择状态,而不需要知道哪些元素已经被选择了。所有选中项的索引可以随时被获取,并且可以通过信号和槽机制通知其他组件选择 model 的变化。

2. 使用选择 model

标准 view 类提供了默认的选择 model ,可以在大多数应用程序中使用。属于一个 view 的选择 model 可以使用 view 的selectionModel()函数获得,并通过setSelectionModel()在多个 view 之间共享,因此通常不需要构造新的选择 model 。
通过指定一个 model 和一对QItemSelection的 model 索引来创建一个选择。这使用索引来引用给定 model 中的项,并将它们解释为选定项块中的左上角和右下角项。要将选择应用于 model 中的项目,需要将选择提交到选择 model ;这可以通过多种方式实现,每种方式对选择 model 中已经存在的选择有不同的影响。

1. 选择项目

为了演示selection的一些主要特性,我们构建一个自定义表 model 的实例,其中共有32个元素,然后打开一个表 view ,查看其中的数据:

TableModel *model = new TableModel(8, 4, &app);

QTableView *table = new QTableView(0);
table->setModel(model);

QItemSelectionModel *selectionModel = table->selectionModel();

表 view 的默认选择 model 会被检索出来以备以后使用。我们不修改 model 中的任何项,而是选择一些将显示在表格左上角的项。为此,我们需要获取与待选择区域的左上角和右下角元素对应的 model 索引:

QModelIndex topLeft;
QModelIndex bottomRight;

topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(5, 2, QModelIndex());

要在 model 中选择这些项,并在tableview中查看相应的更改,需要构建一个selection对象,然后将其应用到selection model 中:

QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);

使用由选择标志组合定义的命令,将选择应用于选择 model 。在这种情况下,使用的标志会导致记录在selection对象中的项被包含在选择 model 中,而不管它们之前的状态如何。结果选择显示在 view 中。
Qt扫盲-Qt Model/View 理论总结 [下篇]_第1张图片

通过选择标志定义的各种操作,可以修改对元素项的选择。这些操作产生的选择可能具有复杂的结构,但选择 model 有效地表示了这些选择。在学习如何更新选区时,我们会介绍如何使用不同的选择标志来操作所选元素。

2. 读取选区状态

可以使用selectedindex()函数读取存储在selection model 中的 model 索引。这将返回一个未排序的 model 索引列表,只要我们知道它们用于哪个 model ,就可以对其进行迭代:

const QModelIndexList indexes = selectionModel->selectedIndexes();

for (const QModelIndex &index : indexes) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
model->setData(index, text);
}

上面的代码使用基于范围的for循环来迭代和修改选择 model 返回的索引对应的项。

选择 model 发出信号来指示选择中的变化。它们通知其他组件关于整个选择和item model 中当前聚焦项的更改。我们可以将selectionChanged()信号连接到一个插槽,并在选择发生变化时检查 model 中被选中或取消选中的项目。插槽用两个QItemSelection对象调用:一个包含一个索引列表,对应于新选择的项;另一个包含对应于新取消选择项的索引。

在下面的代码中,我们提供了一个槽,它接收selectionChanged()信号,用一个字符串填充选定的项目,并清除取消选择的项目的内容。

void MainWindow::updateSelection(const QItemSelection &selected,
const QItemSelection &deselected)
{
QModelIndexList items = selected.indexes();

for (const QModelIndex &index : qAsConst(items)) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
model->setData(index, text);
}

items = deselected.indexes();

for (const QModelIndex &index : qAsConst(items)) {
model->setData(index, QString());
}

我们可以通过将currentChanged()信号连接到一个插槽来跟踪当前聚焦的元素,该插槽通过两个 model 索引调用。它们分别对应于前一个获得焦点的项和当前获得焦点的项。

在下面的代码中,我们提供了一个接收currentChanged()信号的槽,并使用提供的信息来更新QMainWindow的状态栏:

void MainWindow::changeCurrent(const QModelIndex &current,
const QModelIndex &previous)
{
statusBar()->showMessage(
tr("Moved from (%1,%2) to (%3,%4)")
.arg(previous.row()).arg(previous.column())
.arg(current.row()).arg(current.column()));
}

通过这些信号可以直接监控用户所做的选择,但我们也可以直接更新选择 model 。

3. 更新选区

选择命令由选择标志的组合提供,由QItemSelectionModel::SelectionFlag定义。每个选择标志都告诉选择 model ,当调用任何一个select()函数时,如何更新选中项的内部记录。最常用的标志是Select标志,它指示选择 model 将指定的项记录为已选择。切换标志使选择 model 反转指定项的状态,即选择给定的任何未选择项,以及取消选择任何当前选中的项。Deselect标志用来取消选择所有指定的项目。

选择 model 中的单个项目是通过创建一个项目选择并将其应用于选择 model 来更新的。在下面的代码中,我们将第二次选择项应用到上面显示的表 model ,使用Toggle命令翻转给定项的选择状态。

 QItemSelection toggleSelection;

topLeft = model->index(2, 1, QModelIndex());
bottomRight = model->index(7, 3, QModelIndex());
toggleSelection.select(topLeft, bottomRight);

selectionModel->select(toggleSelection, QItemSelectionModel::Toggle);

操作的结果显示在table view中,提供了一种方便的可视化方式:
Qt扫盲-Qt Model/View 理论总结 [下篇]_第2张图片
默认情况下,选择命令只对 model 索引指定的单个项进行操作。但是,用于描述选择命令的标志可以与其他标志结合使用,以改变整个行和列。例如,如果调用select()时只有一个索引,但使用的命令是select和Rows的组合,那么包含引用的项的整行都会被选中。下列代码演示了如何使用Rows和Columns标志:

QItemSelection columnSelection;

topLeft = model->index(0, 1, QModelIndex());
bottomRight = model->index(0, 2, QModelIndex());

columnSelection.select(topLeft, bottomRight);

selectionModel->select(columnSelection,
QItemSelectionModel::Select | QItemSelectionModel::Columns);

QItemSelection rowSelection;

topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(1, 0, QModelIndex());

rowSelection.select(topLeft, bottomRight);

selectionModel->select(rowSelection,
QItemSelectionModel::Select | QItemSelectionModel::Rows);

虽然只向选择 model 提供了4个索引,但使用行和列选择标志意味着选择了两列和两行。下图显示了这两种选择的结果:
Qt扫盲-Qt Model/View 理论总结 [下篇]_第3张图片

在示例 model 上执行的命令都涉及对 model 中选择的项目进行累积。也可以清除选区,或者用一个新的选区替换当前选区。

要将当前选区替换为新的选区,需要将其他选区标志与当前标志合并。使用此标志的命令指示选择 model 用调用select()时指定的索引替换当前的 model 索引集合。要在开始添加新选择之前清除所有选择,请将其他选择标志与clear标志结合起来。这具有重置选择 model 的 model 索引集合的效果。

4. 选择 model 中的所有项

为了选择 model 中的所有项,需要为 model 的每一层创建一个覆盖该层中所有项的选择。为此,可以使用给定的父索引检索左上角和右下角元素对应的索引:

QModelIndex topLeft = model->index(0, 0, parent);
QModelIndex bottomRight = model->index(model->rowCount(parent)-1,
model->columnCount(parent)-1, parent);

利用这些指标和 model 进行选择。然后在selection model 中选择相应的项:

QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);

这需要对 model 中的所有级别执行。对于顶级元素,我们将按照通常的方式定义父索引:

QModelIndex parent = QModelIndex();

在层次 model 中,hasChildren()函数用于确定某一项是否为另一层项的父元素。

二、创建新 model

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

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

QAbstractListModel和QAbstractTableModel类为更简单的非分层数据结构提供接口支持,并且更容易用作简单列表和表 model 的起点。

在本节中,我们将创建一个简单的只读 model 来探索 model / view 体系结构的基本原则。在本节的后面部分,我们将调整这个简单 model ,以便用户可以修改项。

1. 设计一个 model

在为现有数据结构创建新 model 时,重要的是要考虑应该使用哪种类型的 model 来提供到数据的接口。如果数据结构可以表示为项的列表或表,则可以创建QAbstractListModel或QAbstractTableModel的子类,因为这些类为许多函数提供了合适的默认实现。

但是,如果底层数据结构只能用层次树结构表示,则有必要子类化QAbstractItemModel。简单树 model 示例采用了这种方法。

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

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

2. 只读示例 model

这里实现的 model 是一个基于标准QStringListModel类的简单的、非分层的只读数据 model 。它有一个QStringList作为它的内部数据源,并且只实现创建一个功能 model 所需要的东西。为了使实现更容易,我们继承了QAbstractListModel的子类,因为它为列表 model 定义了合理的默认行为,并且它公开了一个比QAbstractItemModel类更简单的接口。

在实现 model 时,重要的是要记住,QAbstractItemModel本身并不存储任何数据,它只是提供 view 用来访问数据的接口。对于最小的只读 model ,只需要实现几个函数,因为大多数接口都有默认实现。类声明如下:

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;
};

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

行为良好的 model 还实现了headerData(),以便为树 view 和表 view 提供在其头文件中显示的内容。

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

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

1. model 的尺寸

我们希望 model 中的行数与字符串列表中的字符串数相同。我们实现rowCount()函数是这样的:

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

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

2. model 头和数据

对于 view 中的项,我们希望返回字符串列表中的字符串。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();
}

只有当提供的 model 索引是有效的,行号在字符串列表中的条目范围内,并且所请求的角色是我们支持的角色时,我们才返回有效的QVariant。
一些 view ,比如QTreeView和QTableView,能够在显示条目数据的同时显示标题。如果我们的 model 显示在带有标题的 view 中,我们希望标题显示行号和列号。我们可以通过子类化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。在决定要返回的确切数据时,也要考虑标题的方向。

并非所有 view 都显示带有项目数据的标题,而那些显示标题的 view 可能被配置为隐藏它们。尽管如此,还是建议您实现headerData()函数来提供关于 model 提供的数据的相关信息。

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

3. 可编辑的 model

只读 model 显示了如何将简单的选择呈现给用户,但对于许多应用程序来说,可编辑列表 model 更有用。我们可以修改只读 model ,通过修改我们为只读实现的data()函数,以及实现两个额外的函数:flags()和setData(),使项目可编辑。下面的函数声明被添加到类定义中:

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

1. 使 model 可编辑

委托在创建编辑器之前检查项是否可编辑。 model 必须让委托知道它的项是可编辑的。我们通过为 model 中的每个项目返回正确的标志来做到这一点;在这种情况下,我们启用所有项,并使它们既可选择又可编辑:

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

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

注意,我们不需要知道委托是如何执行实际的编辑过程的。我们只需要为委托提供一种方法来设置 model 中的数据。这是通过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;
}

在此 model 中,字符串列表中与 model 索引对应的项被提供的值所替换。但是,在修改字符串列表之前,必须确保索引是有效的,项的类型是正确的,并且支持角色。按照惯例,我们坚持角色是EditRole,因为这是标准项委托使用的角色。

然而,对于布尔值,您可以使用Qt::CheckStateRole并设置Qt::ItemIsUserCheckable标志;然后使用一个复选框来编辑该值。该 model 中的底层数据对于所有角色都是相同的,因此该细节使得将 model 与标准组件集成更加容易。

设置数据后, model 必须让 view 知道某些数据已经更改。这是通过发出datachchanged()信号来完成的。由于只有一项数据发生了变化,因此信号中指定的项范围仅限于一个 model 索引。

此外,data()函数需要更改以添加Qt::EditRole测试:

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 || role == Qt::EditRole)
return stringList.at(index.row());
else
return QVariant();
}

2. 插入和删除行

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

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

由于此 model 中的行对应于列表中的字符串,insertRows()函数在指定位置之前将一些空字符串插入到字符串列表中。插入的字符串数等于指定的行数。

父索引通常用于确定应该将行添加到 model 中的什么位置。在这个例子中,我们只有一个字符串的顶级列表,所以我们只需要将空字符串插入到列表中。

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;
}

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

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

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

3. 下一个步骤

我们可以使用QListView类以垂直列表的形式显示 model 的项目,从而显示由该 model 或任何其他 model 提供的数据。对于字符串列表 model ,该 view 还提供了一个默认编辑器,以便可以对项目进行操作。我们在 view 类中检查标准 view 类提供的可能性。

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

三、基于 item 的 view 便捷类

基于项的控件的名称反映了它们的用途:QListWidget提供了一个项列表,QTreeWidget显示了一个多级树结构,QTableWidget提供了一个单元格项表。每个类继承QAbstractItemView类的行为,该类实现了项目选择和标题管理的通用行为。

1. QListWidget

单个级别的项目列表通常使用一个QListWidget和一些QListWidgetItems来显示。列表控件的构造方式与其他控件相同:

QListWidget *listWidget = new QListWidget(this);

列表项可以在构建后直接添加到列表控件中:

new QListWidgetItem(tr("Sycamore"), listWidget);
new QListWidgetItem(tr("Chestnut"), listWidget);
new QListWidgetItem(tr("Mahogany"), listWidget);

它们也可以在没有父列表部件的情况下构建,并在以后的某个时间添加到列表中:

QListWidgetItem *newItem = new QListWidgetItem;
newItem->setText(itemText);
listWidget->insertItem(row, newItem);

列表中的每个项目都可以显示一个文本标签和一个图标。可以更改用于呈现文本的颜色和字体,以提供项目的自定义外观。工具提示、状态提示和“这是什么?”帮助都很容易配置,以确保列表被正确地集成到应用程序中。

 newItem->setToolTip(toolTipText);
newItem->setStatusTip(toolTipText);
newItem->setWhatsThis(whatsThisText);

默认情况下,列表中的项按其创建的顺序显示。项目列表可以根据Qt::SortOrder中给出的标准进行排序,以产生按字母顺序正反排序的项目列表:

listWidget->sortItems(Qt::AscendingOrder);
listWidget->sortItems(Qt::DescendingOrder);

2. QTreeWidget控件

QTreeWidget和QTreeWidgetItem类提供了条目的树或层次列表。树控件中的每个项都可以有自己的子项,并且可以显示许多列的信息。创建树窗口控件就像任何其他窗口控件:

 QTreeWidget *treeWidget = new QTreeWidget(this);

在将项添加到树控件之前,必须设置列的数量。例如,我们可以定义两个列,并创建一个标题来在每个列的顶部提供标签:

treeWidget->setColumnCount(2);
QStringList headers;
headers << tr("Subject") << tr("Default");
treeWidget->setHeaderLabels(headers);

为每个部分设置标签的最简单方法是提供一个字符串列表。对于更复杂的头,您可以构造一个树项目,按照您的意愿装饰它,并将其用作树控件的头。

树形控件中的顶级项是用树形控件作为它们的父控件构造的。它们可以以任意顺序插入,或者您可以通过在构造每个项时指定前一个项来确保它们以特定顺序列出:

QTreeWidgetItem *cities = new QTreeWidgetItem(treeWidget);
cities->setText(0, tr("Cities"));
QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
osloItem->setText(0, tr("Oslo"));
osloItem->setText(1, tr("Yes"));

QTreeWidgetItem *planets = new QTreeWidgetItem(treeWidget, cities);

树控件处理顶级项与处理树中更深层次的其他项略有不同。可以通过调用树控件的takeTopLevelItem()函数从树的顶层删除项,但是通过调用其父项的takeChild()函数从低层删除项。使用insertTopLevelItem()函数将项插入到树的顶层。在树的较低级别,使用父项的insertChild()函数。

在树的顶层和低层之间移动项目很容易。我们只需要检查项是否为顶级项,该信息由每个项的parent()函数提供。例如,我们可以删除树控件中的当前项,而不管其位置如何:

     QTreeWidgetItem *parent = currentItem->parent();
      int index;

      if (parent) {
          index = parent->indexOfChild(treeWidget->currentItem());
          delete parent->takeChild(index);
      } else {
          index = treeWidget->indexOfTopLevelItem(treeWidget->currentItem());
          delete treeWidget->takeTopLevelItem(index);
      }

在树控件的其他地方插入项目遵循相同的模式:

      QTreeWidgetItem *parent = currentItem->parent();
      QTreeWidgetItem *newItem;
      if (parent)
          newItem = new QTreeWidgetItem(parent, treeWidget->currentItem());
      else
          newItem = new QTreeWidgetItem(treeWidget, treeWidget->currentItem());

3. QTableWidget

类似于在电子表格应用程序中发现的项目表是用QTableWidget和QTableWidgetItem构造的。它们提供了一个滚动表控件,其中包含要在其中使用的标题和项。

可以创建具有一定数量的行和列的表,也可以根据需要将这些表添加到不确定大小的表中。

     QTableWidget *tableWidget;
      tableWidget = new QTableWidget(12, 3, this);

项在表外构造,然后在所需位置添加到表中:

   QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(
          pow(row, column+1)));
      tableWidget->setItem(row, column, newItem);

水平和垂直标头可以通过在表外构造项并使用它们作为标头来添加到表中:

      QTableWidgetItem *valuesHeaderItem = new QTableWidgetItem(tr("Values"));
      tableWidget->setHorizontalHeaderItem(0, valuesHeaderItem);

注意,表中的行和列都是从0开始的。

4. 共同的特征

每个便利类都有许多基于项的公共特性,这些特性可以通过每个类中的相同接口获得。我们将在以下几节中介绍这些功能,并提供不同控件的一些示例。查看每个控件的 model / view 类列表,了解所使用的每个函数的更多细节。

1. 隐藏的item

有时,在项目 view 控件中隐藏项目而不是删除项目是很有用的。所有上述控件的项都可以隐藏,稍后再显示。您可以通过调用isItemHidden()函数来确定是否隐藏项,并且可以使用setItemHidden()来隐藏项。
由于此操作是基于项的,因此相同的函数可用于所有三个便利类。

2. 选择

选择项目的方式由控件的选择模式控制(QAbstractItemView::SelectionMode)。此属性控制用户是否可以选择一个或多个项目,以及在多项目选择中,选择是否必须是一个连续的项目范围。选择模式对上述所有控件都以相同的方式工作。

单项选择:当用户需要从控件中选择单个项目时,默认的SingleSelection模式是最合适的。在这种模式下,当前项和所选项是相同的。
在这里插入图片描述

多项目选择:在此模式下,用户可以在不更改现有选择的情况下切换控件中任何项目的选择状态,这与可以独立切换非排他性复选框的方式非常相似。
Qt扫盲-Qt Model/View 理论总结 [下篇]_第4张图片

扩展选择:通常需要选择许多相邻项的控件,例如电子表格中的控件,需要使用ExtendedSelection模式。在这种模式下,可以用鼠标和键盘选择控件中连续范围的项。如果使用了修饰键,还可以创建复杂的选择,包括与控件中其他选定项不相邻的许多项。
Qt扫盲-Qt Model/View 理论总结 [下篇]_第5张图片

如果用户在没有使用修改键的情况下选择了一个项目,则清除现有的选择。

使用selectedItems()函数读取控件中选定的项,并提供可迭代的相关项列表。例如,我们可以用下面的代码找到选中项目列表中所有数值的总和:

      const QList<QTableWidgetItem *> selected = tableWidget->selectedItems();
      int number = 0;
      double total = 0;

      for (QTableWidgetItem *item : selected) {
          bool ok;
          double value = item->text().toDouble(&ok);

          if (ok && !item->text().isEmpty()) {
              total += value;
              number++;
          }
      }

请注意,对于单一选择模式,当前项将在选择中。在多重选择和扩展选择模式中,当前项目可能不在选择范围内,这取决于用户形成选择的方式。

3. 搜索

能够在项目 view 控件中查找项目通常很有用,无论是作为开发人员还是作为服务提供给用户。所有三个项目 view 便利类都提供了一个通用的findItems()函数,以使其尽可能一致和简单。

根据Qt::MatchFlags中的一组值所指定的标准,搜索条目所包含的文本。我们可以使用findItems()函数获取匹配项的列表:

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

      for (QTreeWidgetItem *item : found) {
          item->setSelected(true);
          // Show the item->text(0) for each item.
      }

上面的代码使树控件中的项在包含搜索字符串中给出的文本时被选中。此模式也可用于列表和表控件。

四、使用拖放项目 view

Qt的拖放基础结构完全由 model / view 框架支持。列表、表和树中的项可以在 view 中拖动,数据可以作为mime编码的数据导入和导出。

标准 view 自动支持内部拖放,可以移动项目以改变它们显示的顺序。默认情况下,这些 view 不支持拖放操作,因为它们是为最简单、最常见的用途配置的。为了允许拖拽项目,需要启用 view 的某些属性,并且项目本身也必须允许拖拽。
只允许从 view 导出项,不允许将数据放入其中的 model 的需求比完全启用的拖放 model 的需求要少。

1. 使用方便 view

默认情况下,QListWidget、QTableWidget和QTreeWidget所使用的每种类型的项都配置为使用一组不同的标志。例如,每个QListWidgetItem或QTreeWidgetItem最初都是启用的、可检查的、可选择的,并且可以用作拖放操作的源;每个QTableWidgetItem也可以被编辑并用作拖放操作的目标。

尽管所有的标准项都为拖放设置了一个或两个标志,但你通常需要在 view 本身设置各种属性,以利用内置的拖放支持:

  • 要启用项目拖动,将 view 的dragEnabled属性设置为true。
  • 要允许用户在 view 中删除内部或外部项,请将 view 的viewport()的acceptDrops属性设置为true。
  • 要向用户显示当前被拖放的项目在被拖放时的位置,可以设置 view 的showDropIndicator属性。这为用户提供了关于 view 中项目位置的持续更新信息。

例如,我们可以使用以下代码行在列表控件中启用拖放功能:

  QListWidget *listWidget = new QListWidget(this);
  listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
  listWidget->setDragEnabled(true);
  listWidget->viewport()->setAcceptDrops(true);
  listWidget->setDropIndicatorShown(true);

结果是一个列表控件,它允许在 view 内复制项目,甚至允许用户在包含相同类型数据的 view 之间拖动项目。在这两种情况下,项都是复制而不是移动的。

为了使用户能够在 view 中移动项目,我们必须设置列表控件的dragDropMode:

listWidget->setDragDropMode(QAbstractItemView::InternalMove);

1. 使用 model / view 类

设置用于拖放的 view 遵循与便利 view 相同的模式。例如,QListView可以用与QListWidget相同的方式进行设置:

  QListView *listView = new QListView(this);
  listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
  listView->setDragEnabled(true);
  listView->setAcceptDrops(true);
  listView->setDropIndicatorShown(true);

由于对 view 显示的数据的访问是由 model 控制的,因此所使用的 model 还必须提供对拖放操作的支持。 model 支持的操作可以通过重新实现QAbstractItemModel:: supporteddroptions()函数来指定。例如,使用以下代码启用复制和移动操作:

  Qt::DropActions DragDropListModel::supportedDropActions() const
  {
      return Qt::CopyAction | Qt::MoveAction;
  }

尽管可以给出Qt:: droptions中的任何值组合,但是需要编写 model 来支持它们。例如,为了允许Qt::MoveAction与列表 model 一起正确使用,该 model 必须提供QAbstractItemModel::removeRows()的实现,要么直接实现,要么从基类继承实现。

2. 支持对项目进行拖放

通过重新实现QAbstractItemModel::flags()函数来提供合适的标志, model 向 view 指示哪些项可以拖拽,哪些项可以接受拖拽。

例如,提供基于QAbstractListModel的简单列表的 model 可以通过确保返回的标志包含Qt::ItemIsDragEnabled和Qt::ItemIsDropEnabled值来启用对每个项的拖放操作:

  Qt::ItemFlags DragDropListModel::flags(const QModelIndex &index) const
  {
      Qt::ItemFlags defaultFlags = QStringListModel::flags(index);

      if (index.isValid())
          return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
      else
          return Qt::ItemIsDropEnabled | defaultFlags;
  }

注意,可以将项目拖放到 model 的顶层,但是只能对有效的项目进行拖拽。

在上面的代码中,由于 model 派生自QStringListModel,我们通过调用flags()函数的实现来获得一组默认的标志。

3. 对导出数据进行编码

当数据项通过拖放操作从 model 中导出时,它们被编码为对应于一个或多个MIME类型的适当格式。 model 通过重新实现QAbstractItemModel::mimeTypes()函数来声明它们可以用来提供项目的MIME类型,返回一个标准MIME类型列表。
例如,一个只提供纯文本的 model 将提供以下实现:

  QStringList DragDropListModel::mimeTypes() const
  {
      QStringList types;
      types << "application/vnd.text.list";
      return types;
  }

model 还必须提供代码,以广告格式对数据进行编码。这是通过重新实现QAbstractItemModel::mimeData()函数来提供一个QMimeData对象来实现的,就像在任何其他拖放操作中一样。
下面的代码显示了如何将对应于给定索引列表的每一项数据编码为纯文本并存储在QMimeData对象中。

  QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes) const
  {
      QMimeData *mimeData = new QMimeData;
      QByteArray encodedData;

      QDataStream stream(&encodedData, QIODevice::WriteOnly);

      for (const QModelIndex &index : indexes) {
          if (index.isValid()) {
              QString text = data(index, Qt::DisplayRole).toString();
              stream << text;
          }
      }

      mimeData->setData("application/vnd.text.list", encodedData);
      return mimeData;
  }

由于向函数提供了一个 model 索引列表,因此这种方法足够通用,可用于分层 model 和非分层 model 。
请注意,自定义数据类型必须声明为元对象,并且必须为它们实现流操作符。有关详细信息,请参见QMetaObject类描述。

4. 将删除的数据插入到 model 中

任何给定 model 处理丢失数据的方式取决于它的类型(列表、表或树)和它的内容可能呈现给用户的方式。通常,用于容纳丢失数据的方法应该是最适合 model 底层数据存储的方法。
不同类型的 model 倾向于以不同的方式处理丢失的数据。列表和表 model 仅提供存储数据项的平面结构。因此,当数据在 view 中的现有项上被删除时,它们可能会插入新的行(和列),或者它们可能会使用提供的一些数据覆盖 model 中的项内容。树 model 通常能够将包含新数据的子项添加到其底层数据存储中,因此就用户而言,其行为更具可预测性。
被删除的数据由一个 model 的QAbstractItemModel::dropMimeData()的重新实现来处理。例如,处理简单字符串列表的 model 可以提供一种实现,该实现可以将放到现有项上的数据与放到 model 顶层的数据(即,放到无效项上的数据)分开处理。
通过重新实现QAbstractItemModel::canDropMimeData(), model 可以禁止删除某些项,或者依赖于删除的数据。
model 首先必须确保操作应该被执行,提供的数据是可以使用的格式,并且其在 model 中的目的地是有效的:

  bool DragDropListModel::canDropMimeData(const QMimeData *data,
      Qt::DropAction action, int row, int column, const QModelIndex &parent)
  {
      Q_UNUSED(action);
      Q_UNUSED(row);
      Q_UNUSED(parent);

      if (!data->hasFormat("application/vnd.text.list"))
          return false;

      if (column > 0)
          return false;

      return true;
  }
  bool DragDropListModel::dropMimeData(const QMimeData *data,
      Qt::DropAction action, int row, int column, const QModelIndex &parent)
  {
      if (!canDropMimeData(data, action, row, column, parent))
          return false;

      if (action == Qt::IgnoreAction)
          return true;

如果提供的数据不是纯文本,或者为删除指定的列号无效,则简单的单列字符串列表 model 可以指示失败。
要插入到 model 中的数据的处理方式不同,这取决于它是否被放置到现有项上。在这个简单的示例中,我们希望允许在现有项之间、列表中第一项之前和最后一项之后放置item。

当删除发生时,与父项对应的 model 索引要么是有效的,表明删除发生在一个项上,要么是无效的,表明删除发生在与 model 顶层对应的 view 中的某个地方。

   int beginRow;

      if (row != -1)
          beginRow = row;

我们首先检查提供的行号,看看是否可以使用它将项目插入到 model 中,而不管父索引是否有效。

else if (parent.isValid())
          beginRow = parent.row();

如果父 model 索引有效,则删除发生在项上。在这个简单的列表 model 中,我们找出项目的行号,并使用该值将被删除的项目插入到 model 的顶层。

     else
          beginRow = rowCount(QModelIndex());

当 view 中的其他位置发生拖放,并且行号不可用时,我们将项附加到 model 的顶层。
在分层 model 中,当一个项目发生拖放时,最好将新项目作为该项目的子项目插入 model 中。在这里显示的简单示例中, model 只有一个级别,因此这种方法不合适。

5. 解码导入数据

dropMimeData()的每个实现还必须解码数据并将其插入 model 的底层数据结构中。
对于一个简单的字符串列表 model ,编码的项可以被解码并流化为QStringList:

      QByteArray encodedData = data->data("application/vnd.text.list");
      QDataStream stream(&encodedData, QIODevice::ReadOnly);
      QStringList newItems;
      int rows = 0;

      while (!stream.atEnd()) {
          QString text;
          stream >> text;
          newItems << text;
          ++rows;
      }

然后可以将字符串插入到底层数据存储中。为了保持一致性,这可以通过 model 自己的接口来完成:

      insertRows(beginRow, rows, QModelIndex());
      for (const QString &text : qAsConst(newItems)) {
          QModelIndex idx = index(beginRow, 0, QModelIndex());
          setData(idx, text);
          beginRow++;
      }

      return true;
  }

注意, model 通常需要提供QAbstractItemModel::insertRows()和QAbstractItemModel::setData()函数的实现。

五、代理 model

在 model / view 框架中,单个 model 提供的数据项可以由任意数量的 view 共享,并且每个 view 都可能以完全不同的方式表示相同的信息。自定义 view 和委托是为相同数据提供完全不同的表示的有效方法。然而,应用程序通常需要为相同数据的处理版本提供常规 view ,例如对项目列表进行不同排序的 view 。

虽然将排序和过滤操作作为 view 的内部函数执行似乎是合适的,但这种方法不允许多个 view 共享这种可能代价高昂的操作的结果。另一种方法(涉及在 model 本身中排序)会导致类似的问题,即每个 view 必须显示根据最近的处理操作组织的数据项。

为了解决这个问题, model / view 框架使用代理 model 来管理各个 model 和 view 之间提供的信息。从 view 的角度来看,代理 model 是行为与普通 model 类似的组件,并代表该 view 访问源 model 中的数据。 model / view 框架使用的信号和槽确保每个 view 都得到适当的更新,无论在其自身和源 model 之间放置了多少代理 model 。

1. 使用代理 model

代理 model 可以插入到现有 model 和任意数量的 view 之间。Qt提供了一个标准的代理 model ,QSortFilterProxyModel,它通常被实例化并直接使用,但也可以被子类化以提供自定义的过滤和排序行为。QSortFilterProxyModel类可以通过以下方式使用:

      QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(parent);
      filterModel->setSourceModel(stringListModel);

      QListView *filteredView = new QListView;
      filteredView->setModel(filterModel);

由于代理 model 继承自QAbstractItemModel,它们可以连接到任何类型的 view ,并且可以在 view 之间共享。它们还可以用于处理从管道安排中的其他代理 model 获得的信息。
QSortFilterProxyModel类被设计为实例化并直接在应用程序中使用。可以通过继承此类并实现所需的比较操作来创建更专门化的代理 model 。

2. 自定义代理 model

通常,代理 model 中使用的处理类型涉及将每个数据项从源 model 中的原始位置映射到代理 model 中的不同位置。在某些 model 中,有些项目可能在代理 model 中没有相应的位置;这些 model 是过滤代理 model 。 view 使用代理 model 提供的 model 索引访问项目,这些索引不包含关于源 model 或该 model 中原始项目的位置的信息。

QSortFilterProxyModel允许在将源 model 中的数据提供给 view 之前对其进行过滤,并且还允许将源 model 的内容作为预先排序的数据提供给 view 。

1. 自定义过滤 model

QSortFilterProxyModel类提供了一个相当通用的过滤 model ,可以在各种常见情况下使用。对于高级用户,可以对QSortFilterProxyModel进行子类化,从而提供一种允许实现自定义过滤器的机制。
QSortFilterProxyModel的子类可以重新实现两个虚函数,当从代理 model 请求或使用 model 索引时调用:

  • filterAcceptsColumn()用于从源 model 的一部分过滤特定的列。
  • filterAcceptsRow()用于从源 model 的一部分过滤特定的行。

QSortFilterProxyModel中上述函数的默认实现返回true,以确保所有项都传递给 view ;这些函数的重新实现应该返回false以过滤掉单独的行和列。

2. 自定义排序 model

QSortFilterProxyModel实例使用std::stable_sort()函数来设置源 model 中的项与代理 model 中的项之间的映射,从而允许在不修改源 model 结构的情况下向 view 公开已排序的项层次结构。要提供自定义排序行为,需要重新实现lessThan()函数来执行自定义比较。

3. model 子类化参考

model 子类需要提供在QAbstractItemModel基类中定义的许多虚函数的实现。需要实现的这些函数的数量取决于 model 的类型——它是为 view 提供一个简单的列表、一个表还是一个复杂的项目层次结构。从QAbstractListModel和QAbstractTableModel继承的 model 可以利用这些类提供的函数的默认实现。以树状结构公开数据项的 model 必须为qab中的许多虚拟函数提供实现

六、 model 子类化参考

model 子类需要提供在QAbstractItemModel基类中定义的许多虚函数的实现。需要实现的这些函数的数量取决于 model 的类型——它是为 view 提供一个简单的列表、一个表还是一个复杂的项目层次结构。从QAbstractListModel和QAbstractTableModel继承的 model 可以利用这些类提供的函数的默认实现。在树状结构中公开数据项的 model 必须为QAbstractItemModel中的许多虚拟函数提供实现。

需要在 model 子类中实现的函数可以分为三组:

  • 项目数据处理:所有 model 都需要实现一些功能,使 view 和委托能够查询 model 的维度、检查项目和检索数据。
  • 导航和索引创建:分层 model 需要提供函数, view 可以调用这些函数来导航它们公开的树状结构,并获取项目的 model 索引。
  • 拖放支持和MIME类型处理: model 继承了控制内部和外部拖放操作执行方式的函数。这些函数允许用其他组件和应用程序可以理解的MIME类型描述数据项。

1. 项目数据处理

model 可以提供对其提供的数据的不同级别的访问:它们可以是简单的只读组件,一些 model 可能支持调整大小操作,而其他 model 可能允许对项目进行编辑。

2. 只读访问

为了提供对 model 提供的数据的只读访问,必须在 model 的子类中实现以下函数:

函数名 功能作用
flags() 由其他组件使用,以获取 model 提供的每个项的信息。在许多 model 中,标志的组合应该包括Qt::ItemIsEnabled和Qt::ItemIsSelectable。
data() 用于向 view 和委托提供项数据。一般来说, model 只需要为Qt::DisplayRole和任何特定于应用程序的用户角色提供数据,但为Qt::ToolTipRole, Qt::AccessibleTextRole和Qt::AccessibleDescriptionRole提供数据也是很好的实践。请参阅Qt::ItemDataRole枚举文档,了解与每个角色相关的类型的信息。
headerData () 为 view 提供要在其标题中显示的信息。只有可以显示标题信息的 view 才能检索到这些信息。
rowCount () 提供 model 公开的数据行数。

上面的这四个函数必须在所有类型的 model 中实现,包括列表 model (QAbstractListModel子类)和表 model (QAbstractTableModel子类)。

另外,以下函数必须在QAbstractTableModel和QAbstractItemModel的直接子类中实现:

函数名 功能作用
columnCount () 提供 model 公开的数据列数。列表 model 不提供这个函数,因为它已经在QAbstractListModel中实现了。

1. 可编辑项

可编辑 model 允许修改数据项,还可以提供允许插入和删除行和列的函数。要启用编辑功能,必须正确实现以下功能:

函数名 功能作用
flags() 必须为每个项目返回适当的标志组合。特别地,这个函数返回的值除了应用于只读 model 中的项的值之外,还必须包括Qt::ItemIsEditable。
setData () 用于修改与指定 model 索引相关联的数据项。为了能够接受用户界面元素提供的用户输入,该函数必须处理与Qt::EditRole相关的数据。实现也可以接受与Qt::ItemDataRole指定的许多不同类型的角色相关联的数据。在更改数据项之后, model 必须发出datachchanged()信号来通知其他组件更改。
setHeaderData () 用于修改水平和垂直标题信息。在更改数据项之后, model 必须发出headerdatachchanged()信号来通知其他组件更改。

2. 可调整大小的 model

所有类型的 model 都可以支持插入和删除行。表 model 和层次 model 也可以支持插入和删除列。在 model 维度发生更改之前和之后通知其他组件非常重要。因此,可以实现以下函数来允许调整 model 的大小,但实现必须确保调用适当的函数来通知附加的 view 和委托:

函数名 功能作用
insertRows() 用于向所有类型的 model 添加新的数据行和项。实现必须在将新行插入任何底层数据结构之前调用beginInsertRows(),并在之后立即调用endInsertRows()。
removeRows() 用于从所有类型的 model 中删除行和它们包含的数据项。实现必须在从任何底层数据结构中删除行之前调用beginRemoveRows(),然后立即调用endRemoveRows()。
insertColumns() 用于向表 model 和分层 model 中添加新的列和数据项。实现必须在将新列插入任何底层数据结构之前调用beginInsertColumns(),并在之后立即调用endInsertColumns()。
removeColumns() 用于从表 model 和分层 model 中删除列及其包含的数据项。实现必须在从任何底层数据结构中删除列之前调用beginRemoveColumns(),然后立即调用endRemoveColumns()。

通常,如果操作成功,这些函数应该返回true。然而,在某些情况下,手术可能只是部分成功;例如,如果可以插入的行数少于指定的行数。在这种情况下, model 应该返回false,以表明无法启用任何附加组件来处理这种情况。

在调整大小API的实现中调用的函数发出的信号使附加的组件有机会在任何数据变得不可用之前采取行动。使用begin和end函数封装插入和删除操作还使 model 能够正确地管理持久 model 索引。

通常,begin和end函数能够将 model 底层结构的更改通知其他组件。对于 model 结构的更复杂的更改,可能涉及内部重组、数据排序或任何其他结构更改,有必要执行以下顺序:

  • 发出layoutAboutToBeChanged()信号
  • 更新表示 model 结构的内部数据。
  • 使用changePersistentIndexList()更新持久索引
  • 发出layoutChanged()信号。

此序列可用于任何结构更新,以代替更高级和更方便的保护方法。例如,如果一个有200万行的 model 需要删除所有奇数行,那就是100万个不连贯的范围,每个范围有1个元素。使用beginRemoveRows和endRemoveRows 100万次是可能的,但这显然是低效的。相反,这可以表示为一次更新所有必要的持久索引的单个布局更改。

3. model 数据的延迟填充

model 数据的延迟填充有效地允许对 model 信息的请求被延迟,直到 view 实际需要它。

一些 model 需要从远程数据源获取数据,或者必须执行耗时的操作来获取有关数据组织方式的信息。由于 view 通常会请求尽可能多的信息以准确地显示 model 数据,因此限制返回给 view 的信息量以减少不必要的后续数据请求是很有用的。

在分层 model 中,查找给定项的子节点数量是一项代价高昂的操作,因此确保只在必要时调用 model 的rowCount()实现非常有用。在这种情况下,可以重新实现hasChildren()函数,为 view 提供一种廉价的方法来检查子元素的存在,并且在QTreeView的情况下,为它们的父元素绘制适当的装饰。

无论hasChildren()的重新实现返回true还是false, view 都可能不需要调用rowCount()来确定存在多少个子节点。例如,如果父项没有展开以显示它们,QTreeView不需要知道有多少个子项。

如果知道许多项将有子项,则重新实现hasChildren()以无条件返回true有时是一种有用的方法。这确保了在尽可能快地初始填充 model 数据的同时,每个项目都可以在以后为孩子检查。唯一的缺点是,没有子项的项目可能在某些 view 中显示错误,直到用户尝试查看不存在的子项。

3. 导航和 model 索引创建

分层 model 需要提供函数, view 可以调用这些函数来导航它们公开的树状结构,并获取项目的 model 索引。

1. Parents and children

由于暴露给 view 的结构是由底层数据结构决定的,因此每个 model 子类都可以通过提供以下函数的实现来创建自己的 model 索引:

函数名 功能作用
index () 给定父项的 model 索引,该函数允许 view 和委托访问该项的子项。如果找不到有效的子项——对应于指定的行、列和父 model 索引,则该函数必须返回QModelIndex(),这是一个无效的 model 索引。
parent() 提供与任何给定子项的父项对应的 model 索引。如果指定的 model 索引对应于 model 中的顶级项,或者 model 中没有有效的父项,则该函数必须返回一个无效的 model 索引,该索引是用空的QModelIndex()构造函数创建的。

上述两个函数都使用createIndex()工厂函数生成索引,供其他组件使用。 model 通常会为这个函数提供一些唯一的标识符,以确保 model 索引稍后可以与其相应的项重新关联。

4. 拖放支持和MIME类型处理

model / view 类支持拖放操作,为许多应用程序提供了足够的默认行为。然而,在拖放操作期间,也可以自定义项目的编码方式,无论它们是默认复制还是移动,以及如何将它们插入到现有 model 中。
此外,便利 view 类实现的专门化行为应该与现有开发人员所期望的密切相关。方便性 view 部分提供了这种行为的概述。

1. MIME数据

默认情况下,内置 model 和 view 使用内部MIME类型(application/x-qabstractitemmodeldatalist)来传递关于 model 索引的信息。这为项目列表指定数据,其中包含每个项目的行号和列号,以及每个项目支持的角色的相关信息。
使用此MIME类型编码的数据可以通过调用QAbstractItemModel::mimeData()来获得,该方法带有一个包含要序列化的项的QModelIndexList。
当在自定义 model 中实现拖放支持时,可以通过重新实现以下功能以特殊格式导出数据项:

  • mimeData ()
    这个函数可以被重新实现,以返回其他格式的数据,而不是默认的application/x-qabstractitemmodeldatalist内部MIME类型。
    子类可以从基类获得默认的QMimeData对象,并以其他格式向其添加数据。

对于许多 model ,以MIME类型(如text/plain和image/png)表示的通用格式提供项目内容是很有用的。注意,可以使用QMimeData::setImageData()、QMimeData::setColorData()和QMimeData::setHtml()函数轻松地将图像、颜色和HTML文档添加到QMimeData对象中。

2. 接受丢弃的数据

当在 view 上执行拖放操作时,将查询底层 model ,以确定它支持哪种类型的操作以及它可以接受的MIME类型。该信息由QAbstractItemModel:: supporteddroptions()和QAbstractItemModel::mimeTypes()函数提供。不覆盖QAbstractItemModel提供的实现的 model 支持复制操作和项目的默认内部MIME类型。

当序列化的项目数据被拖放到 view 中时,数据将使用QAbstractItemModel::dropMimeData()的实现插入到当前 model 中。此函数的默认实现永远不会覆盖 model 中的任何数据;相反,它尝试将数据项插入为某项的兄弟项或该项的子项。

为了利用QAbstractItemModel对内置MIME类型的默认实现,新 model 必须提供以下功能的重新实现:

函数名 功能作用
insertrow () insertColumns () 这些函数使 model 能够使用QAbstractItemModel::dropMimeData()提供的现有实现自动插入新数据。
setData () 允许用项填充新的行和列。
setItemData () 此函数为填充新项提供了更有效的支持。

为了接受其他形式的数据,这些函数必须重新实现:

函数名 功能作用
supportedDropActions () 用于返回拖放操作的组合,指示 model 接受的拖放操作的类型。
mimetype () 用于返回可由 model 解码和处理的MIME类型列表。通常,支持输入 model 的MIME类型与为外部组件使用的数据编码时可以使用的MIME类型相同。
dropMimeData () 对通过拖放操作传输的数据执行实际解码,确定将在 model 中设置数据的位置,并在必要的地方插入新的行和列。如何在子类中实现此函数取决于每个 model 公开的数据的需求。

如果dropMimeData()函数的实现通过插入或删除行或列来更改 model 的维度,或者如果修改了数据项,则必须注意确保发出所有相关信号。简单地调用子类中其他函数的重新实现,例如setData()、insertRows()和insertColumns(),可以确保 model 的行为一致。
为了确保拖动操作正常工作,重新实现以下从 model 中删除数据的函数是很重要的:
removeRows ()
removeRow ()
removeColumns ()
removeColumn ()
有关使用项目 view 拖放的详细信息,请参阅使用项目 view 拖放。

3. 方便的 view

方便的 view (QListWidget、QTableWidget和QTreeWidget)覆盖了默认的拖放功能,提供了不那么灵活但更自然的行为,适合于许多应用程序。例如,由于更常见的是将数据放入QTableWidget中的单元格中,用正在传输的数据替换现有的内容,底层 model 将设置目标项的数据,而不是向 model 中插入新的行和列。有关在方便 view 中拖放的详细信息,请参见在项 view 中使用拖放。

5. 针对大量数据的性能优化

canFetchMore()函数检查父节点是否有更多可用数据,并相应地返回true或false。fetchMore()函数根据指定的父节点获取数据。这两个函数可以组合在一起,例如,在涉及增量数据的数据库查询中填充QAbstractItemModel。我们重新实现canFetchMore()来指示是否有更多的数据需要获取,并根据需要将fetchMore()填充到 model 中。
另一个例子是动态填充的树 model ,当树 model 中的分支被扩展时,我们重新实现fetchMore()。
如果fetchMore()的重新实现将行添加到 model 中,则需要调用beginInsertRows()和endInsertRows()。此外,canFetchMore()和fetchMore()都必须重新实现,因为它们的默认实现返回false并且不做任何事情。

七、 model / view 类

这些类使用 model / view 设计模式,在该模式中,底层数据(在 model 中)与用户(在 view 中)呈现和操作数据的方式保持分离。

类名 功能作用
QAbstractItemDelegate 用于显示和编辑 model 中的数据项
QAbstractItemModel 项目 model 类的抽象接口
QAbstractItemView 项 view 类的基本功能
QAbstractListModel 抽象 model ,可以子类化为一维列表 model
QAbstractProxyModel 可以执行排序、过滤或其他数据处理任务的代理项 model 的基类
QAbstractTableModel 抽象 model ,可以被子类化为创建表 model
QColumnView 列 view 的 model / view 实现
QConcatenateTablesProxyModel 代理多个源 model ,连接它们的行
QDataWidgetMapper 数据 model 的一部分到控件之间的映射
QFileSystemModel 本地文件系统的数据 model
QHeaderView 项 view 的标题行或标题列
QIdentityProxyModel 代理未修改的源 model
QItemDelegate 显示和编辑来自 model 的数据项的工具
QItemEditorCreator 使创建项目编辑器创建基而不子类化QItemEditorCreatorBase成为可能
QItemEditorCreatorBase 在实现新的项编辑器创建器时必须子类化的抽象基类
QItemEditorFactory 用于编辑 view 和委托中的项数据的控件
QItemSelection 管理关于 model 中选定项的信息
QItemSelectionModel 跟踪 view 的选定项
QItemSelectionRange 管理关于 model 中选定项范围的信息
QListView 列表或图标 view 到 model 上
QListWidget 基于项目的列表控件
QListWidgetItem 项与QListWidget项 view 类一起使用
QModelIndex 用于定位数据 model 中的数据
QPersistentModelIndex 用于定位数据 model 中的数据
QSortFilterProxyModel 持排序和过滤在另一个 model 和 view 之间传递的数据
QStandardItem 项与QStandardItemModel类一起使用
QStandardItemEditorCreator 注册控件的可能性,而不必子类化QItemEditorCreatorBase
QStandardItemModel 用于存储自定义数据的通用 model
QStringListModel 为 view 提供字符串的 model
QStyledItemDelegate 显示和编辑来自 model 的数据项的工具
QTableView 表 view 的默认 model / view 实现
QTableWidget 带有默认 model 的基于项的表 view
QTableWidgetItem 项用于QTableWidget类
QTableWidgetSelectionRange 在不使用 model 索引和选择 model 的情况下与 model 中的选择进行交互的方法
QTreeView 树 view 的默认 model / view 实现
QTreeWidget 使用预定义树 model 的树 view
QTreeWidgetItem 项,以便与QTreeWidget便利类一起使用
QTreeWidgetItemIterator 在QTreeWidget实例中迭代项的方法

你可能感兴趣的:(#,▶,Qt扫盲,model,model/view,view,qt5)