模型视图结构
Model-View-Controller (MVC) 是一种设计模式。它们通常由三个对象构成,Model是应用模型,View是屏幕显示,Controller定义了用户界面和用户输入的交互。如果把View和Controller结合起来,就成为了Model/View(模型视图)模式,这种模式仍然是把数据的存储和显示分隔起来的。
l 模型与数据源通信,为这个架构中的其它组件提供了接口。通信的本质依赖于数据源的类型和Model的实现;
l 视图从Model获取Model index,它们是数据项的引用。通过提供Model indexes,视图可以从数据源获取数据项;
l 在标准的视图中,delegate负责渲染数据项。当正在编辑某一项的时候,delegate通过Model index与模型直接通信。
Model,View和Delegate相互之间的通信是通过信号和槽的机制:
l 从模型发出的信号会通知视图数据源的改变;
l 从视图发出的信号提供了用户与显示的数据项交互的信息;
l 从delegate发出的信息用于在编辑时告诉模型和视图编辑的状态。
Models
所有的数据项模型都是基于QAbstractItemModel类的。它定义了视图和delegates获取数据的接口。数据本身不需要存储在模型中,它可以存储在单独的数据结构中或通过单独的类、文件、数据库等其他组件来提供。
如果实现类似于列表或表格的数据结构,可以子类化QAbstractListModel 和QAbstractTableModel,它们提供了通用方法的默认实现。
Views
QListView,QTableView和QTreeView提供了不同的实现,它们都基于QAbstractItemView。尽管这些类都是可以直接使用的实现,也仍然可以子类化进行定制。
Delegates
QAbstractItemDelegate是delegates在模型视图框架中的抽象基类。从QT4.4开始,默认的实现由QStyledItemDelegate实现,它作为标准视图的默认delegate。QStyledItemDelegate 和QItemDelegate可为数据项提供独立的绘制和编辑模式,它们的不同在于QStyledItemDelegate使用的是当前的Style进行绘制。当要配合QT Style sheets的话建议从QStyledItemDelegate继承。
Sorting
有两种方法可以达到排序的目的,选择哪种方法取决于当前使用的model。如果模型是可排序的,比如,重新实现了QAbstractItemModel::sort()方法,QTableView andQTreeView都提供了API允许编程排序。另外,还可以允许用户交互排序(比如当用户点击表头排序),通过连接QHeaderView::sortIndicatorChanged() 的信号分别到QTableView::sortByColumn() slot 或the QTreeView::sortByColumn()。
Model classes
基本概念
Model提供了标准的接口,views和delegates可以用它来访问数据,QAbstractItemModel类定义了这些接口。不管数据以何种数据结构存储,QAbstractItemModel的子类利用分层结构来表示数据。视图利用这种结构来访问模型中的数据,但是给用户显示信息的方法是不受限制的。
Model indexes
为了保证数据的显示和访问是隔离的,引入了Modelindex的概念。每一条可以从Model获得的信息都通过Model index来表示。视图和delegates使用索引请求显示数据。这样,就只有Model需要知道如何获取数据。ModelIndex包含了创建它的模型的指针,这样在使用多个模型的时候就不会搞混。
QAbstractItemModel *model = index.model();
为了获取数据的Model index,要设置Model的三个属性:rownumber,column number,和父item的Model index。
Rows and columns
我们可以通过指定行号和列号来访问item的信息,在访问的时候要获得代表item的index:
QModelIndex index = model->index(row, column, ...);
对于简单的单层数据结构,如列表和表格等,模型提供了接口访问,但是像上面的代码那样,我们还需要一个model index。
上图是一个基本的表格模型,里面的item通过行号和列号来定位。我们通过下面的方法来获取item的index:
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB =model->index(1, 1, QModelIndex());
QModelIndex indexC =model->index(2, 1, QModelIndex());
模型中的顶层item,总是通过指定QModelIndex()作为父item index来访问。
Parents of items
访问表格结构的数据可以通过行号和列号机制,但是树形结构的数据就有所不同。树形结构中的每个item可能都是另一个表格的父item,当获取Index的时候需要提供父index的信息。
QModelIndex index = model->index(row, column, parent);
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC =model->index(2, 1, QModelIndex());
QModelIndex indexB = model->index(1, 0, indexA);
Item roles
模型中的item可以扮演多种角色的组件,允许提供不同形式的数据。比如:Qt::DisplayRole用于访问视图文字的字符串。
通过制定index和role来获得我们需要的数据类型:
QVariant value = model->data(index, role);
Role制定了要引用的数据类型是哪种。视图可以以不同的方式显示roles,所以对每个role要提供恰当的信息。
大部分的标准角色都定义在Qt::ItemDataRole。只要制定了恰当的role,模型就可以提示view和delegate来如何显示item。
小结
l Model index提供Model中item的位置信息给view和delegate,信息是与存储的数据结构无关的;
l Item通过行号、列号和父Item的Model index来引用;
l 根据Views和delegates的需求,Models负责构建Modelindexes;
l 当使用index()方法的时候,如果父item是有效的Modelindex,那么将会返回模型下的子itemindex;
l 当使用index()方法的时候,如果父item是无效的Modelindex,那么将会返回一个顶级item index;
l Role区分开不同数据类型的item。
Using model indexes
构建一个文件系统模型:
QFileSystemModel *model = newQFileSystemModel;
QModelIndex parentIndex =model->index(QDir::currentPath());
int numRows =model->rowCount(parentIndex);
通过模型的index()方法获取了父index,并利用rowCount方法获取了行数
for (int row = 0; row < numRows; ++row) {
QModelIndex index =model->index(row, 0, parentIndex);
QString text = model->data(index, Qt::DisplayRole).toString();
// Display the text in a widget.
}
从模型获取数据的基本原则:
l 模型的大小可以用rowCount()和columnCount()方法来获得,只需要指定父modelindex;
l Model index用于访问模型中的item,需要指定row,column和父model index;
l 获取模型中的顶层item,用QModelIndex()指定一个空的modelindex为父index;
l Item包含了有不同role的数据,要获取data的一种role,需要向模型提供model index和role。
View classes
概念
在模型/视图结构里面,视图从模型获取数据项并显示给用户。数据的显示形式并不一定要像模型提供的数据显示形式,并且有可能与用于存储数据项的底层数据结构完全不同。
内容和显示的分离是利用QAbstractItemModel和QAbstractItemView提供的标准接口来完成的,并利用model index这种通用形式来表示数据项。视图管理从模型获得数据的总体布局。它们可以渲染单个的数据项,或利用delegates来处理渲染和编辑特性。
除了显示数据,视图还负责数据项目的漫游和选择。视图也实现了基本的用户交互接口,如右键菜单,拖拽等。视图提供了默认的编辑特性,也可以利用delegate提供可定制的编辑。
视图在构建的时候可以不需要模型,但需要显示信息的时候必须提供模型。在视图中的选择可以是单独的,也可以在多重视图中共享。
一些视图,如QTableView 和 QTreeView,像显示数据一样显示表头,同样通过一个视图类来实现——QHeaderView。表头和视图通常访问同一个模型,通过QAbstractItemModel::headerData()函数获取数据,并通过标签的形式来显示数据信息。
Delegate classes
概念
不像MVC模式,模型/视图模式并没有单独的组件来管理与用户的交互。视图负责了数据的显示,并负责处理用户的输入。为了实现更灵活的输入方式,通过delegate来实现交互。这些组件提供了输入能力,并负责在视图中渲染单独的项目。控制delegate的标准接口定义在QAbstractItemDelegate类中。
Delegate通过实现paint() 方法 sizeHint()来渲染内容。但是,简单组件的delegates可以通过子类化QItemDelegate而不是QAbstractItemDelegate,使用其中一些默认的函数实现。
Delegates的编辑器可以通过两种方式来实现,一种是使用控件来处理编辑过程,一种是通过直接处理事件。
使用现有的delegate
QT的标准视图提供利用QItemDelegate的实例来提供编辑功能,对象QListView,QTableView, 和 QTreeView等视图都提供了默认的delegate实现。
视图使用的delegate通过itemDelegate()方法返回,setItemDelegate()函数允许插入自定义的delegate,当对自定义的视图设置delegate的时候需要使用到该方法。
A simple delegate
下面的delegate使用QSpinBox实现了编辑功能:
因为不希望重写显示函数,所以从QItemDelegate类子类化,但是仍然需要提供方法来管理编辑控件:
class SpinBoxDelegate : public QItemDelegate
{
Q_OBJECT
public:
SpinBoxDelegate(QObject*parent = 0);
QWidget*createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
voidsetEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget*editor, QAbstractItemModel *model,
constQModelIndex &index) const;
voidupdateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem&option, const QModelIndex &index) const;
};
在这里当delegate构建的时候并没有设置编辑控件,可以在需要编辑控件的时候再构造。
提供editor
在这个例子当中,当表格视图需要提供编辑器的时候,会调用delegate提供的编辑器。createEditor()函数负责提供delegate的编辑器:
QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem&/* option */,
const QModelIndex &/* index */) const
{
QSpinBox *editor = newQSpinBox(parent);
editor->setMinimum(0);
editor->setMaximum(100);
return editor;
}
在这里,我们并没有保存编辑器的指针,是因为当不需要的时候视图将会负责销毁编辑器。
根据视图提供的Modelindex可以提供不同的编辑控件,比如,有一列整数和一列字符串,我们可以根据正在编辑的列来返回一个QSpinBox或QLineEdit。
Delegate必须提供一个函数拷贝模型数据到编辑器中,比如从display role中读取数据,再根据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()函数请求delegate存储编辑值。
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);
}
由于视图为delegate管理编辑控件,我们只需将编辑控件中的内容更新到模型中。当通过发射closeEditor()信号完成编辑时,标准的QItemDelegate类会通知视图,视图确保编辑控件的关闭和销毁。在这个例子中,只提供给了简单的编辑功能,所以不需要发送这个信号。
所有对数据的操作都通过QAbstractItemModel提供的接口来执行,这种结构使得delegate完全独立于它操纵的数据类型,但是仍然需要设定使用某种类型的编辑控件。在这个例子中,我们认为model总是包含的整数类型数据,但是我们仍然可以把delegate用于不同种类的模型,这是因为QVariant为不能预期的数据提供了默认可靠的类型。
Updating the editor's geometry
Delegate还负责管理编辑器的几何位置,当编辑控件创建时、视图中项目的尺寸或位置变化时,必须设置几何位置,视图通过一个view opiton对象提供了必要的几何信息。
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem&option, const QModelIndex &/* index */) const
{
editor->setGeometry(option.rect);
}
在这种情况下,我们使用item矩形的viewoption提供的几何信息。
Editing hints
编辑完之后,delegate需要提示其它组件编辑处理的结果,通过closeEditor()信号来发送提示,这由默认的QItemDelegate时间过滤器来处理,是在构建spin box的时候安装的。