前面咱们介绍了模型的概念。下面则是另一个基本元素:视图。在 model/view 架构中,视图是数据从模型到最终用户的途径。数据经过视图向用户进行显示。此时,这种显示方式没必要须同模型的存储结构相一致。实际上,不少状况下,数据的显示同底层数据的存储是彻底不一样的。架构
咱们使用QAbstractItemModel
提供标准的模型接口,使用 QAbstractItemView
提供标准的视图接口,而结合这二者,就能够将数据同表现层分离,在视图中利用前面所说的模型索引。视图管理来自模型的数据的布局:既能够直接渲染数据自己,也能够经过委托渲染和编辑数据。编辑器
视图不只仅用于展现数据,还用于在数据项之间的导航以及数据项的选择。另外,视图也须要支持不少基本的用户界面的特性,例如右键菜单以及拖放。视图能够提供数据编辑功能,也能够将这种编辑功能交由某个委托完成。视图能够脱离模型建立,可是在其进行显示以前,必须存在一个模型。也就是说,视图的显示是彻底基于模型的,这是不能脱离模型存在的。对于用户的选择,多个视图能够相互独立,也能够进行共享。函数
某些视图,例如QTableView
和QTreeView
,不只显示数据,还会显示列头或者表头。这些是由QHeaderView
视图类提供的。在《QFileSystemModel》一章的最后,咱们曾经提到过这个问题。表头一般访问视图所包含的同一模型。它们使用QAbstractItemModel::headerData()
函数从模型中获取数据,而后将其以标签 label 的形式显示出来。咱们能够经过继承QHeaderView
类,实现某些更特殊的功能。布局
正如前面的章节介绍的,咱们一般会为视图提供一个模型。拿前面咱们曾经见过的一个例子来看:性能
QStringList data; data << "0" << "1" << "2"; model = new QStringListModel(this); model->setStringList(data); listView = new QListView(this); listView->setModel(model); QPushButton *btnShow = new QPushButton(tr("Show Model"), this); connect(btnShow, SIGNAL(clicked()), this, SLOT(showModel())); QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addWidget(btnShow); QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(listView); layout->addLayout(buttonLayout); setLayout(layout);
运行一下程序,这个界面十分简单:this
跟咱们前面的演示几乎如出一辙。如今咱们有一个问题:若是咱们双击某一行,列表会容许咱们进行编辑。可是,咱们没办法控制用户只能输入数字——固然,咱们能够在提交数据时进行检测,这也是一种办法,不过,更友好的方法是,根本不容许用户输入非法字符。为了达到这一目的,咱们使用了委托。下面,咱们增长一个委托:code
class SpinBoxDelegate : public QStyledItemDelegate { Q_OBJECT public: SpinBoxDelegate(QObject *parent = 0) : QStyledItemDelegate(parent) {} QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; void setEditorData(QWidget *editor, const QModelIndex &index) const; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const; };
正如前面所说,委托就是供视图实现某种高级的编辑功能。不一样于经典的 Model-View-Controller(MVC)模式,model/view 没有将用户交互部分彻底分离。通常地,视图将数据向用户进行展现而且处理通用的输入。可是,对于某些特殊要求(好比这里的要求必须输入数字),则交予委托完成。这些组件提供输入功能,同时也能渲染某些特殊数据项。委托的接口由QAbstractItemDelegate
定义。在这个类中,委托经过paint()
和sizeHint()
两个函数渲染用户内容(也就是说,你必须本身将渲染器绘制出来)。为使用方便,从 4.4 开始,Qt 提供了另外的基于组件的子类:QItemDelegate
和QStyledItemDelegate
。默认的委托是QStyledItemDelegate
。两者的区别在于绘制和向视图提供编辑器的方式。QStyledItemDelegate
使用当前样式绘制,而且可以使用 Qt Style Sheet(咱们会在后面的章节对 QSS 进行介绍),所以咱们推荐在自定义委托时,使用QStyledItemDelegate
做为基类。不过,除非自定义委托须要本身进行绘制,不然,两者的代码实际上是同样的。继承
继承QStyledItemDelegate
须要实现如下几个函数:索引
createEditor()
:返回一个组件。该组件会被做为用户编辑数据时所使用的编辑器,从模型中接受数据,返回用户修改的数据。setEditorData()
:提供上述组件在显示时所须要的默认值。updateEditorGeometry()
:确保上述组件做为编辑器时可以完整地显示出来。setModelData()
:返回给模型用户修改过的数据。下面依次看看各函数的实现:接口
QWidget *SpinBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, const QModelIndex & /* index */) const { QSpinBox *editor = new QSpinBox(parent); editor->setMinimum(0); editor->setMaximum(100); return editor; }
在createEditor()
函数中,parent 参数会做为新的编辑器的父组件。
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); }
setEditorData()
函数从模型中获取须要编辑的数据(具备Qt::EditRole
角色)。因为咱们知道它就是一个整型,所以能够放心地调用toInt()
函数。editor 就是所生成的编辑器实例,咱们将其强制转换成QSpinBox
实例,设置其数据做为默认值。
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); }
在用户编辑完数据后,委托会调用setModelData()
函数将新的数据保存到模型中。所以,在这里咱们首先获取QSpinBox
实例,获得用户输入值,而后设置到模型相应的位置。标准的QStyledItemDelegate
类会在完成编辑时发出closeEditor()
信号,视图会保证编辑器已经关闭,可是并不会销毁,所以须要另外对内存进行管理。因为咱们的处理很简单,无需发出closeEditor()
信号,可是在复杂的实现中,记得能够在这里发出这个信号。针对数据的任何操做都必须提交给QAbstractItemModel
,这使得委托独立于特定的视图。固然,在真实应用中,咱们须要检测用户的输入是否合法,是否可以存入模型。
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { editor->setGeometry(option.rect); }
最后,因为咱们的编辑器只有一个数字输入框,因此只是简单将这个输入框的大小设置为单元格的大小(由option.rect
提供)。若是是复杂的编辑器,咱们须要根据单元格参数(由option
提供)、数据(由index
提供)结合编辑器(由editor
提供)计算编辑器的显示位置和大小。
如今,咱们的委托已经编写完毕。接下来须要将这个委托设置为QListView
所使用的委托:
listView->setItemDelegate(new SpinBoxDelegate(listView));
值得注意的是,new 操做符并不会真的建立编辑器实例。相反,只有在真正须要时,Qt 才会生成一个编辑器实例。这保证了程序运行时的性能。
而后咱们运行下程序: