Qt ModelView模式
Posted 965452300
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt ModelView模式相关的知识,希望对你有一定的参考价值。
Qt Model/View模式
Model/View 结构
Qt 4推出了一组新的item view类,它们使用model/view结构来管理数据与表示层的关系。
Model-View-Controller(MVC), 是从Smalltalk发展而来的一种设计模式,常被用于构建用户界面。经典设计模式的著作中有这样的描述:
MVC 由三种对象组成。Model是应用程序对象,View是它的屏幕表示,Controller定义了用户界面如何对用户输入进行响应。在MVC之前,用户界面设计倾向于三者揉合在一起,MVC对它们进行了解耦,提高了灵活性与重用性。
model与数据源通讯,并提供接口给结构中的别的组件使用。通讯的性质依赖于数据源的种类与model实现的方式。
view从model获取model indexes,后者是数据项的引用。
delegate会对数据项进行渲染,当某个数据项被选中时,delegate通过model indexes与model直接进行交流。
models,views,delegates之间通过信号,槽机制来进行通讯:
从model发出的信号通知view数据源中的数据发生了改变。
从view发出的信号提供了有关被显示的数据项与用户交互的信息。
从delegate发生的信号被用于在编辑时通知model和view关于当前编辑器的状态信息。
Models
model为view和delegates使用数据提供了标准接口。
所有的item models都基于QAbstractItemModel类,这个类定义了用于views和delegates访问数据的接口。
数据本身不必存储在model,数据可被置于一个数据结构或另外的类,文件,数据库,或别的程序组件中。
不管数据在底层以何种数据结构存储,QAabstractItemModel的子类会以层次结构的形式来表示数据,结构中包含了数据项表。我们按这种约定来访问model中的数据项,但这个约定不会对如何显示这些数据有任何限制。数据发生改变时,model通过信号槽机制来通知关联的views。
Qt提供的一些现成的Models用于处理数据项的:
QStringListModel 用于存储简单的QString列表。
QStandardItemModel 管理复杂的树型结构数据项,每项都可以包含任意数据。
QDirModel 提供本地文件系统中的文件与目录信息。
QSqlQueryModel, QSqlTableModel,QSqlRelationTableModel用来访问数据库。
假如这些标准Model不满足你的需要,你应该子类化QAbstractItemModel,QAbstractListModel或是QAbstractTableModel来定制。
Model Indexes
为了使数据存储与数据访问分开,引入了model index的概念。通过model index,可以引用model中的数据项,Views和delegates都使用indexes来访问数据项,然后再显示出来。
model indexes提供了对一项数据信息的临时引用,通过它可以访问或是修改model中的数据。model有时会重新组织内部的数据结构,这时model indexes便会失效,因此不应该保存临时的model indexes
数据信息的长期引用,可使用persistent model index,这个会自动保持更新。
行与列
通过指定model中的行列数来获取任一项数据,可以得到与数据项一一对应的那个index。
QModelIndex index = model->index(row, column, ...);
通过Index.data即可获取对应的数据。也可通过model->data(index, Qt::DisplayRole).toString();获取。
一个model的顶级项(根项),由QModelIndex()取得。
父项
在model外部,引用一个数据项的唯一方法就是通过model index,因此需要在求取model index时指定父项的信息。
QModelIndex index = model->index(row, column, parent);
上图中,A项和C项作为model中顶层的兄弟项:
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
A有许多孩子,它的一个孩子B用以下代码获取:
QModelIndex indexB = model->index(1, 0, indexA);
项角色
model中的项可以作为各种角色来使用,这允许为不同的环境提供不同的数据。
每个数据项都可以为许多不同的角色提供数据,标准的角色在Qt::ItemDataRole中定义。我们可以通过指定model index与角色来获取我们需要的数据:
QVariant value = model->data(index, role);
概念总结:
1、Model indexes为views与delegages提供model中数据项定位的信息,它与底层的数据结构无关。
2、通过指定行,列数,父项的model index来引用数据项。
3、依照别的组件的要求,model indexes被model构建。
4、使用index()时,如果指定了有效的父项的model index,那么返回得到的model index对应于父项的某个孩子。
5、使用index()时,如果指定了无效的父项的model index,那么返回得到的model index对应于顶层项的某个孩子。
6、角色对一个数据项包含的不同类型的数据给出了区分。
使用Model Indexes
QDirModel *model = new QDirModel;
QModelIndex parentIndex = model->index(QDir::currentPath());
int numRows = model->rowCount(parentIndex);
for (int row = 0; row < numRows; ++row)
{
QModelIndex index = model->index(row, 0, parentIndex);
tring text = model->data(index, Qt::DisplayRole).toString();
// Display the text in a widget.
}
以上的例子说明了从model中获取数据的基本原则:
1、model的行列数可以从rowCount()与columnCount()中得出。这些函数通常都需要一个表示父项的model index。
2、model indexes用来从model中访问数据项,数据项用行,列,父项model index定位。
3、 为了访问model顶层项,可以使用QModelIndex()指定。
4、数据项为不同的角色提供不同的数据。为了获取数据,除了model index之外,还要指定角色。
Views
view从model中获得数据项然后显示给用户。view负责管理从model中读取的数据的外观布局。
不同的view都完整实现了各自的功能:QListView把数据显示为一个列表,QTableView把Model 中的数据以table的形式表现,QTreeView 用具有层次结构的列表来显示model中的数据。这些类都基于QAbstractItemView抽象基类,尽管这些类都是现成的,完整的进行了实现,但它们都可以用于子类化以便满足定制需求。
它们自己可以去渲染每个数据项,也可以利用delegate来既处理渲染又进行编辑。
一个view创建时必不需要model,但在它能显示一些真正有用的信息之前,必须提供一个model。
选择项
view通过使用selections来跟踪用户选择的数据项。每个view可以维护单独使用的selections,也可以在多个views之间共享。
标题
从 model中获取数据的函数是QabstractItemModel::headerDate()。可派生QHeaderView来定制自己得标题。
实操
void MainWindow::stepUI()
{
QSplitter *oSplitter = new QSplitter(this);
QDirModel *oModel = new QDirModel();
QTreeView *oTreeView = new QTreeView(oSplitter);
//配置一个view去显示model中的数据,只需要简单地调用setModel(),并把目录model作为参数传递
oTreeView->setModel(oModel);
//setRootIndex()告诉views显示哪个目录的信息,这需要提供一个model index,然后用这个
//model index去model中去获取数据
//index()这个函数是QDirModel特有的,通过把一个目录做为参数,得到了需要的model index
//其他的代码只是窗口show出来,进入程序的事件循环就好了
oTreeView->setRootIndex(oModel->index(QDir::currentPath()));
oSplitter->setWindowTitle("show DIr");
QTableView *oTableView = new QTableView(oSplitter);
oTableView->setModel(oModel);
oTableView->setRootIndex(oModel->index(QDir::currentPath()));
this->setCentralWidget(oSplitter);
}
几个要点:
1、 同一个model可以绑定多个view
2、 多个view之间共享选择:oTableView ->setSelectionModel(oTreeView ->selectionModel());
Delegates
既提供输入功能又负责渲染view中的每个数据项。
QAbstractItemDelegate 是model/view架构中的用于delegate的抽象基类。缺省的delegate实现在QItemDelegate类中提供。它可以用于Qt标准views的缺省 delegate.
控制delegates的标准接口在QAbstractItemDelegate类中定义。Delegates通过实现paint()和sizeHint()以达到渲染内容的目的。
QT提供的delegate
简单的基于widget的delegates,可以从QItemDelegate子类化,而不是QAbstractItemDelegate,这样可以使用它提供的上述函数的缺省实现。delegate可以使用widget来处理编辑过程,也可以直接对事件进行处理。
Qt提供的标准views都使用QItemDelegate的实例来提供编辑功能。
通过view获取delegate:可以用itemDelegate()函数取得,而setItemDelegate() 函数可以设置一个定制delegate。
实操:使用QSpinBox提供编辑功能
class SpinBoxDelegate : public QItemDelegate
{
Q_OBJECT
public:
SpinBoxDelegate(QObject *parent = 0);
// 获取编辑方式的窗体
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 Q_DECL_OVERRIDE;
// 更新编辑器几何布局
void updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
};
① delegate创建时不需要创建widget,只需要在真正使用的时候才需要创建,在createEditor中真正的调用。
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;
}
此处我们不需要释放editor,因为view用完以后会释放。
② 将model中的数据同步到新创建的编辑器里面
void SpinBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
int value = index.model()->data(index, Qt::DisplayRole).toInt();
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
spinBox->setValue(value);
}
③ 当编辑完成,向model中提交数据
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);
}
标准的QItemDelegate类当它完成编辑时会发射closeEditor()信号来通知view。view保证编辑器widget关闭与销毁。
④ 更新编辑器的位置,在编辑器创建或view尺寸或者位置变化的时候进行调用。
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry(option.rect);
}
⑤ 编辑完成提示
编辑完成以后会发送信号closeEditor来通知其他组件完成编辑和数据提交。我们也可以通过绑定信号closeEditor来进行其他操作。
排序
在model/view架构中,有两种方法进行排序:
1、 对model进行排序
① 编程实现:重新实现QAbstractItemModel::sort()函数
交互式实现:用户点击表头排序,把QHeaderView::sectionClicked()信号与QTableView::sortByColum()槽或QTreeView::sortByColumn()槽进行联结就好了。
QTableView中的sortByColumn方法,他有两个方法可调用,一个是槽函数:
void QTableView::sortByColumn(int column)
{
Q_D(QTableView);
if (column == -1)
return;
d->model->sort(column, d->horizontalHeader->sortIndicatorOrder());
}
另一个是公共函数:最终还是调用的槽函数。
void QTableView::sortByColumn(int column, Qt::SortOrder order)
{
Q_D(QTableView);
d->horizontalHeader->setSortIndicator(column, order);
sortByColumn(column);
}
其实最终还是调用的model中的排序。
2、 通过代理model在view表示数据之前对model数据结构进行转换。
QT提供的常用类
QListWidget,QTreeWidget,QTableWidget这些类qt建议不要去进行派生子类。
以上是关于Qt ModelView模式的主要内容,如果未能解决你的问题,请参考以下文章
26.Qt Quick QML-RotationAnimationPathAnimationSmoothedAnimationBehaviorPauseAnimationSequential(代码片段