概念
与模型-视图-控制器模式不同,模型/视图设计没有包含一个完全独立的组件来管理与用户的交互。通常,视图负责向用户展示模型数据,并负责处理用户输入。为了在获取输入的方式上具有一定的灵活性,交互由委托执行。这些组件提供输入功能,还负责在某些视图中渲染单个项目。控制委托的标准接口定义在QAbstractItemDelegate类中。
委托希望能够通过实现paint()和sizeHint() 函数来渲染它们自己的内容。然而,简单的基于部件的委托可以继承QStyledItemDelegate而不是QAbstractItemDelegate,并利用这些函数的默认实现。
委托编辑器可以通过使用小部件来管理编辑过程,也可以通过直接处理事件来实现 。第一种方法将在本节后面介绍,它也会在Spin Box委托的例子中展示。
Pixelator的例子展示了如何创建一个自定义委托来为tableview执行特殊的渲染。
使用现有委托
Qt提供的标准视图使用QStyledItemDelegate实例来提供编辑功能。delegate接口的默认实现会以标准视图(QListView、QTableView和QTreeView)的通常风格渲染元素。
所有标准角色都由标准视图使用的默认委托处理。解释它们的方式在QStyledItemDelegate文档中有描述。
视图使用的委托由itemDelegate()函数返回。setItemDelegate()函数允许你为标准视图安装一个自定义委托,在为自定义视图设置委托时,必须使用这个函数。
一个简单的委托
这里实现的委托使用QSpinBox来提供编辑功能,主要用于显示整数的模型。虽然我们为此设置了一个自定义的基于整数的表模型,但我们可以轻松地使用QStandardItemModel,因为自定义委托控制数据输入。我们构建一个表视图来显示模型的内容,这将使用自定义委托进行编辑。
我们从QStyledItemDelegate继承委托的子类,因为我们不想编写自定义显示函数。但是,我们仍然必须提供管理编辑器小部件的函数:
class SpinBoxDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
SpinBoxDelegate(QObject *parent = nullptr);
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;
};
注意,构造委托时没有设置编辑器部件。我们只在需要时构建编辑器部件。
提供编辑器
在这个例子中,当表视图需要提供一个编辑器时,它要求委托提供一个适合于正在修改的项的编辑器部件。createEditor()函数提供了委托设置适当部件所需的一切:
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,这取决于正在编辑哪一列。
委托必须提供将模型数据复制到编辑器中的函数。在这个例子中,我们读取了存储在display角色中的数据,并相应地设置了spin box中的值。(设置编辑器的值)
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);
}
在这个例子中,我们知道编辑器小部件是一个spin box,但我们可以为模型中的不同类型的数据提供不同的编辑器,在这种情况下,我们需要在访问其成员函数之前将小部件转换为适当的类型。
向模型提交数据
当用户完成微调框中的值编辑后,视图会调用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);
}
由于视图为委托管理编辑器部件,我们只需要使用提供的编辑器内容更新模型。在本例中,我们确保微调框是最新的,并使用指定的索引用它包含的值更新模型。
标准的QStyledItemDelegate类通过发出closeEditor()信号来通知视图何时完成编辑。视图确保编辑器部件被关闭和销毁。在这个例子中,我们只提供了简单的编辑功能,所以我们永远不需要发射这个信号。
所有对数据的操作都通过QAbstractItemModel提供的接口来执行。这使得委托基本上独立于它所操作的数据类型,但是为了使用某些类型的编辑器部件,必须做一些假设。在这个例子中,我们假设模型总是包含整数值,但我们仍然可以将此委托用于不同类型的模型,因为QVariant为意外数据提供了合理的默认值。
更新编辑器的几何形状
委托的职责是管理编辑器的几何图形。几何形状必须在编辑器创建时设置,并且当项目的大小或在视图中的位置发生变化时设置。幸运的是,该视图在视图选项对象中提供了所有必要的几何信息。
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &/* index */) const
{
editor->setGeometry(option.rect);
}
在这个例子中,我们只使用项目矩形中的view选项提供的几何信息。呈现具有多个元素的项的委托不会直接使用项矩形。它将使编辑器相对于项目中的其他元素进行定位。
编辑提示
在编辑之后,委托应该向其他组件提供有关编辑过程结果的提示,并提供有助于任何后续编辑操作的提示。这是通过发送带有适当提示的closeEditor()信号来实现的。这是由默认的QStyledItemDelegate事件过滤器处理的,我们在构建spin box时安装了它。
可以调整spin box的行为,使其对用户更友好。在QStyledItemDelegate提供的默认事件过滤器中,如果用户点击Return来确认他们在微调框中的选择,则委托将值提交给模型并关闭微调框。我们可以通过在微调框上安装自己的事件过滤器来改变这种行为,并根据需要提供编辑提示;例如,我们可以发送带有EditNextItem提示的closeEditor(),以自动开始编辑视图中的下一项。
另一种不需要使用事件过滤器的方法是提供我们自己的编辑器部件,为了方便,可能会子类化QSpinBox。这种替代方法将使我们能够更多地控制编辑器部件的行为,但代价是编写额外的代码。如果需要自定义标准Qt编辑器部件的行为,在委托中安装事件过滤器通常更容易。
委托不必发出这些提示,但是那些不发出提示的委托与应用程序的集成程度较低,而且与那些发出提示以支持常见编辑操作的委托相比,它们的可用性较差。