QT 模型视图编程概念

模型视图结构

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的时候安装的。


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值