Qt中的ItemDelegate总结
Qt中的ItemDelegate总结
本文是对这几年Model/View框架中使用Delegate的一个总结.
在Qt的Model/View框架中, 当我们需要在ItemView中对数据做编辑, 或者我们需要定制特殊的显示模式的时候, 标准的做法是使用ItemDelegate
, 当然, 还可以使用QTableWidgetItem::setItemWidget()
和QTreeWidgetItem::setitemWidget()
, 但是这两个函数的限制也很大, 基本上也只能用来显示静态内容.
Qt中的ItemView中可以通过定制Delegate来定制特定Item的编辑和显示功能. 做这件事情呢, 有几种方法:
使用工厂类QItemEditorFactor
- 利用
QItemEditorFactory
, 注册指定数据类型使用的editor. 这种做法可能是最简单的, 但是同时也是使用限制最多的一种. 因为从接口上看, 我们完全看不到任何精细的控制途径. 但是对于简单场景, 这个已经足够了.
QItemEditorFactory
是一个工厂类, 它负责管理QStyledItemDelegate
, 而这种管理是通过工厂类内部维护的一个QItemEditorCreatorBase
的集合来进行的, 而这些Creator类负责调用Delegate, 创建编辑器. 工厂类通过方法registerEditor()
简化了这个过程, 当然, 简化了就增加了限制, 就只能通过Item里面的数据的实际类型来识别和分发了.
在Qt的例子coloreditorfactory
里面演示了这种用法:
首先定义了一个Editor, ColorListEditor
.
1 | class ColorListEditor : public QComboBox |
这里面最重要的是Q_PROPERTY
这一行, 在其中, 最重要的是USER
. 在Qt的QItemEditorCreatorBase
的帮助信息里面是这样写的:
An editor should provide a user property for the data it edits. QItemDelagates can then access the property using Qt’s meta-object system to set and retrieve the editing data. A property is set as the user property with the USER keyword:
Q_PROPERTY(QColor color READ color WRITE setColor USER true)
同时, 它也指出:
If the editor does not provide a user property, it must return the name of the property from valuePropertyName()
; delegates will then use the name to access the property. If a user property exists, item delegates will not call valuePropertyName()
.
然后, 如果要使用, 只需要注册它:
1 | QItemEditorFactory *factory = new QItemEditorFactory; |
使用时, 我们需要给要它编辑的Item指定Data, 例如:
1 | QTableWidgetItem *colorItem = new QTableWidgetItem; |
这种做法最大的限制, 其实是, 这个defaultFactory()
是全局的. 也就是说, 我一旦修改了, 那么应用程序的所有的ListView都会受到影响. 这个几乎可以肯定不是我们所期望的. 另外一个根本无解的问题是, 我们完全无法对取值做控制. 比如, 如果我们想限制上面的Color的取值该怎么办? 只能派生定制新的Color Editor. 这就完全不可行了.
所以, 其实这个就是个玩具而已.
指定Item使用哪个Delegate
另一种做法是实现Delegate, 并指定ItemView使用Delegate. 这是标准用法.
QAbstractItemView
类提供了三个这种函数, 可以用来控制给所有Item/指定列/指定行使用Delegate.
- setItemDelegate()
- setItemDelegateForColumn()
- setItemDelegateForRow()
比如, 下面的代码:
1 | tableView.setModel(&model); |
至于实现自己的Delegate, 有两种选择. 按照手册说法, 当我们只需要呈现数据时, 使用QAbstractitemDelegate
作为派生类的基类. 如果需要读写数据时, 使用QStyledItemDelegate
更合适一些.
QAbstractItemDelegate
. 需要实现paint()
和sizeHint()
两个函数. 一般, 如果仅仅是为了呈现数据, 并不需要修改数据, 可以使用这个类作为派生的基类. 这两个函数中,paint()
实现的是绘制功能. 它的参数包括,QPainter
的指针, 一个QStyleOptionViewItem
的引用option
, 以及当前数据的索引的引用.QModelIndex &
. 绘图时则是标准的业务流程. 以painter->save()
开始, 以painter->restore()
结束.sizeHint()
则是返回要绘制的大小.
QStyledItemDelegate
. 适用于简单的基于Widget控件的Delegate, 此时不需要实现这两个函数. 一般情况下, 当我们需要实现Editor的时候, 基本上都是从QStyledItemDelegate
派生出自己的类, 并实现createEditor()
,setEditorData()
,setModelData()
三个函数.createEditor()
: 当View中的item被修改时, 框架会调用createEditor()
函数来创建一个编辑器, 它返回创建的Widget的指针, 并由框架管理其生命期.setEditorData()
: 用于从Model中获取要编辑的数据并在Widget中显示setModelData()
: 用于在完成编辑后从Delegate Widget中获取用户修改后的数据更新到Model中.updateEditorGeometry()
: 设置编辑器的尺寸. 当editor被创建, 其大小和尺寸被修改时. View提供的QStyleOptionViewItem
参数中提供了这个值. 我们一般只需要将其设置给这个widget旧可以了.
如果我们在Delegate使用的仅仅是使用单个的标准控件, 那么这么就够了. 如果我们需要做定制的行为效果, 则需要同样派生paint()
和sizeHint()
两个方法.
我们可以看到, 它们的基础都是Model, 即使是在Widget中使用, 背后也仍然是Model.
利用Model的最大的好处, 个人觉得, 是我们可以很方便地同时定义其他的一些属性. 例如, 比如, 我们使用QSpinBox
作为Delegate, 最简单的实现是这样的:
1 | QWidget *SpinBoxDelegate::createEditor(QWidget *parent, |
可是, 如果我们需要针对不同的Item定义不同的取值范围该怎么办?
使用Model模式, 我们最简单的做法就是定义两个其他Role的属性过来, 在这里获取并设置给这个SpinBox里面就可以了.
更细粒度的控制
前面说过, 一个ItemView类有三个设置Delegate的函数:
- setItemDelegate()
- setItemDelegateForColumn()
- setItemDelegateForRow()
分别用于指定所有Item, 某一行, 某一列Item所使用的Delegate.
那么, 如果是更特殊的情况, 比如我们需要让某一列的不同Item使用不同的Delegate, 该如何实现? 当然, 有一些线程的第三方控件可以做这个, 但是, 如果我们并不需要这么重度的东西, 或者说我们只是需要一个小编辑, 而不是完整的Property Sheet, 该如何?
我倒是真的没有找到类似的接口. 不过, 想到了一个变通的法子.
我写一个Delegate的Wraper, 它根据特定的位置来决定.
例如, PropEditDelegate
, 我们可以这样实现它的createEditor
函数:
1 | QWidget *PropEditDelegate::createEditor(QWidget *parent, |
在这里, canCovert()
的使用, 在本质上我们是使用QVariant
的类型系统来识别QVariant
里面存储的数据的类型. 除了canConvert<>()
之外, 我们还有其他的OVariant
的类型系统方法来使用:
typeId()
userType()
typeName()
metaType()
(Qt6)
在这种模式下, 我们根据特定的位置, 根据特定的数据类型做dispatch. 这或许会是一种可行的方法. 当然, 这种耦合是很紧密的了. 要从别的地方考虑进行解耦处理.