QtModelView模式学习笔记.docx
《QtModelView模式学习笔记.docx》由会员分享,可在线阅读,更多相关《QtModelView模式学习笔记.docx(29页珍藏版)》请在冰豆网上搜索。
![QtModelView模式学习笔记.docx](https://file1.bdocx.com/fileroot1/2023-2/5/cc19a9c1-7e1c-4d34-b0ab-ba31ea9fbb41/cc19a9c1-7e1c-4d34-b0ab-ba31ea9fbb411.gif)
QtModelView模式学习笔记
Ref:
一般来说,Model里面并没有真正存储数据(数据少的话也可以直接存储在Model里),它的数据是从真正的“肉(raw)”里取得,如一个diskfile,或database的queryresultset等等。
那么这个model究竟是干什么用的呢?
说白了吧,它就是负责将“肉”数据获取并提供给view,然后将view所做的对“肉”数据的修改更新至真正的“肉”中。
所以,读写文件、操作数据库、网络通讯等一系列与数据打交道的工作就在model中做了。
有的时候“肉”可能真的很肥,所以model还有一项重要的工作就是把这些“肉”编号。
这样就出现了ModelIndex这个非常重要的类。
一般来说,它使用一个2维的编号(row/colum)来对“肉”编号。
但对于tree这种有层次结构的数据来说,又加上一个parentindex作为第3个编号。
即一个父亲下面的叶子也是从0,0开始编号,获取modelindex的时候用递归来实现。
OK,现在model已经有了一堆编好号码的“肉”了,谁来买啊?
“肉”便宜了。
。
。
。
。
。
View适时出现,注意,很多view可以同时来买同一块“肉”。
(汗,不开玩笑了,这篇blog快水了)。
当view需要显示某些数据时,它们通过modelindex从model中获取数据(调用model的data函数,当model的data变化时,它也会自动发dataChangedsignal给所有的view以便它们更新)。
当然,在view中也可以调用model的setData函数来设定某个modelindex所对应的数据。
这里要说明一下model中的数据,用QVarient来承载,可以是所有Qt支持的类型,比较贴心的是,数据可以分成多个角色(role),例如Qt:
:
DisplayRole专用于显示,Qt:
:
BackgroundRole用于显示背景色等等。
所以在model中,你不光可以对“肉”进行编号,还可以对“肉”进行“深加工”,使它们更“好看”或是更“美味”。
View组织这些数据并显示,但却没有做真正的显示工作,真正的工作留给了delegate。
Delegate就是MVC中的C。
view让它显示时它就在paint函数中显示。
当然,你可以重载这个函数并实现你自己的显示。
你还可以给一个view设定rowdelegate和columdelegate专用于row和colum。
当用户触发了view的edittrigger时(如双击鼠标或回车),view开始inplaceedit(beginEditing)。
Delegate会在合适的地方创建一个合适的widget(如lineedit或combobox等)处理用户的输入,用户输入完成以后delegate获取用户的输入并返回。
这些输入可以通过调用model的setData函数保存到真正的“肉”中。
所以Delegate其实就是负责最终显示数据和处理用户交互的。
既然由用户交互,最重要的肯定是用户的选择了。
说一下selectionmodel。
View将用户选择的itemindex全部存入selectionmodel中,显示的时候根据selectionmodel的内容显示。
另外,多个view可以共享同一个selectionmodel,这样,当你选中其中的一个时,另一个view中的相应item也会被选中。
QtModel/View
介绍
Qt4推出了一组新的itemview类,它们使用model/view结构来管理数据与表示层的关系。
这种结构带来的功能上的分离给了开发人员更大的弹性来定制数据项的表示,它也提供一个标准的model接口,使得更多的数据源可以被这些itemview使用。
这里对model/view的结构进行了描述,结构中的每个组件都进行了解释,给出了一些例子说明了提供的这些类如何使用。
Model/View结构
Model-View-Controller(MVC),是从Smalltalk发展而来的一种设计模式,常被用于构建用户界面。
经典设计模式的著作中有这样的描述:
MVC由三种对象组成。
Model是应用程序对象,View是它的屏幕表示,Controller定义了用户界面如何对用户输入进行响应。
在MVC之前,用户界面设计倾向于三者揉合在一起,MVC对它们进行了解耦,提高了灵活性与重用性。
假如把view与controller结合在一起,结果就是model/view结构。
这个结构依然是把数据存储与数据表示进行了分离,它与MVC都基于同样的思想,但它更简单一些。
这种分离使得在几个不同的view上显示同一个数据成为可能,也可以重新实现新的view,而不必改变底层的数据结构。
为了更灵活的对用户输入进行处理,引入了delegate这个概念。
它的好处是,数据项的渲染与编程可以进行定制。
如上图所示,model与数据源通讯,并提供接口给结构中的别的组件使用。
通讯的性质依赖于数据源的种类
与model实现的方式。
view从model获取modelindexes,后者是数据项的引用。
通过把modelindexes提供给model,view可以从数据源中获取数据。
在标准的views中,delegate会对数据项进行渲染,当某个数据项被选中时,delegate通过modelindexes与model直接进行交流。
总的来说,model/view相关类可以被分成上面所提到的三组:
models,views,delegates。
这些组件通过抽象类来定义,它们提供了共同的接口,在某些情况下,还提供了缺省的实现。
抽象类意味着需要子类化以提供完整的其他组件希望的功能。
这也允许实现定制的组件。
models,views,delegates之间通过信号,槽机制来进行通讯:
从model发出的信号通知view数据源中的数据发生了改变。
从view发出的信号提供了有关被显示的数据项与用户交互的信息。
从delegate发生的信号被用于在编辑时通知model和view关于当前编辑器的状态信息。
Models
所有的itemmodels都基于QAbstractItemModel类,这个类定义了用于views和delegates访问数据的接口。
数据本身不必存储在model,数据可被置于一个数据结构或另外的类,文件,数据库,或别的程序组件中。
关于model的基本概念在ModelClasses部分中描述。
QAbstractItemModel提供给数据一个接口,它非常灵活,基本满足views的需要,无论数据用以下任何样的形式
表现,如tables,lists,trees。
然而,当你重新实现一个model时,如果它基于table或list形式的数据结构,最好从QAbstractListModel,QAbstractTableModel开始做起,因为它们提供了适当的常规功能的缺省实现。
这些类可以被子类化以支持特殊的定制需求。
子类化model的过程在CreateNewModel部分讨论
QT提供了一些现成的models用于处理数据项:
QStringListModel用于存储简单的QString列表。
QStandardItemModel管理复杂的树型结构数据项,每项都可以包含任意数据。
QDirModel提供本地文件系统中的文件与目录信息。
QSqlQueryModel,QSqlTableModel,QSqlRelationTableModel用来访问数据库。
假如这些标准Model不满足你的需要,你应该子类化QAbstractItemModel,QAbstractListModel或是
QAbstractTableModel来定制。
Views
不同的view都完整实现了各自的功能:
QListView把数据显示为一个列表,QTableView把Model中的数据以table的形式表现,QTreeView用具有层次结构的列表来显示model中的数据。
这些类都基于QAbstractItemView抽象基类,尽管这些类都是现成的,完整的进行了实现,但它们都可以用于子类化以便满足定制需求。
Delegates
QAbstractItemDelegate是model/view架构中的用于delegate的抽象基类。
缺省的delegate实现在QItemDelegate类中提供。
它可以用于Qt标准views的缺省delegate.
排序
在model/view架构中,有两种方法进行排序,选择哪种方法依赖于你的底层Model。
假如你的model是可排序的,也就是它重新实现了QAbstractItemModel:
:
sort()函数,QTableView与QTreeView都提供了API,允许你以编程的方式对Model数据进行排序。
另外,你也可以进行交互方式下的排序(例如,允许用户通过点击view表头的方式对数据进行排序),可以这样做:
把QHeaderView:
:
sectionClicked()信号与QTableView:
:
sortByColum()槽或QTreeView:
:
sortByColumn()槽进行联结就好了。
另一种方法是,假如你的model没有提供需要的接口或是你想用listview表示数据,可以用一个代理
model在用view表示数据之前对你的model数据结构进行转换。
便利类
许多便利类都源于标准的view类,它们方便了那些使用Qt中基于项的view与table类,它们不应该被子类化,
它们只是为Qt3的等价类提供一个熟悉的接口。
这些类有QListWidget,QTreeWidget,QTableWidget,它们提供了如Qt3中的QListBox,QlistView,QTable相似的行为。
这些类比View类缺少灵活性,不能用于任意的models,推介使用model/view的方法处理数据。
介绍
Qt提供了两个标准的models:
QStandardItemModel和QDirModel。
QStandardItemModel是一个多用途的model,可用于表示list,table,treeviews所需要的各种不同的数据结构。
这个model也持有数据。
QDirModel维护相关的目录内容的信息,它本身不持有数据,仅是对本地文件系统中的文件与目录的描述。
QDirModel是一个现成的model,很容易进行配置以用于现存的数据,使用这个model,可以很好地展示如何给一个现成的view设定model,研究如何用modelindexes来操纵数据。
model与views的搭配使用
QListView与QTreeView很适合与QDirModel搭配。
下面的例子在treeview与listview显示了相同的信息,QDirModel提供了目录内容数据。
这两个Views共享用户选择,因此每个被选择的项在每个view中都会被高亮。
先装配出一个QDirModel以供使用,再创建views去显示目录的内容。
这给我展示了使用model的最简单的方式。
model的创建与使用都在main()函数中完成:
intmain(intargc,char*argv[])
{
QApplicationapp(argc,argv);
QSplitter*splitter=newQSplitter;
QDirModel*model=newQDirModel;
//从缺省目录创建数据
QTreeView*tree=newQTreeView(splitter);
tree->setModel(model);
tree->setRootIndex(model->index(QDir:
:
currentPath()));
QListView*list=newQListView(splitter);
list->setModel(model);
list->setRootIndex(model->index(QDir:
:
currentPath()));
//配置一个view去显示model中的数据,只需要简单地调用setModel(),并把目录model作为参数传递
//setRootIndex()告诉views显示哪个目录的信息,这需要提供一个modelindex,然后用这个
//modelindex去model中去获取数据
//index()这个函数是QDirModel特有的,通过把一个目录做为参数,得到了需要的modelindex
//其他的代码只是窗口show出来,进入程序的事件循环就好了
splitter->setWindowTitle("Twoviewsontothesamedirectorymodel");
splitter->show();
returnapp.exec();
}
上面的例子并没有展示如何处理数据项的选择,这包括很多细节,以后会提到。
Model类
基本概念
在model/view构架中,model为view和delegates使用数据提供了标准接口。
在Qt中,标准接口QAbstractItemModel类中被定义。
不管数据在底层以何种数据结构存储,QAabstractItemModel的子类会以层次结构的形式来表示数据,结构中包含了数据项表。
我们按这种约定来访问model中的数据项,但这个约定不会对如何显示这些数据有任何限制。
数据发生改变时,model通过信号槽机制来通知关联的views。
ModelIndexes
为了使数据存储与数据访问分开,引入了modelindex的概念。
通过modelindex,可以引用model中的数据项,Views和delegates都使用indexes来访问数据项,然后再显示出来。
因此,只有model需要了解如何获取数据,被model管理的数据类型可以非常广泛地被定义。
Modelindexes包含一个指向创建它们的model的指针,这会在配合多个model工作时避免混乱。
QAbstractItemModel*model=index.model();
modelindexes提供了对一项数据信息的临时引用,通过它可以访问或是修改model中的数据。
既然model有时会重新组织内部的数据结构,这时modelindexes便会失效,因此不应该保存临时的modelindexes。
假如需要一个对数据信息的长期的引用,那么应该创建一个persistentmodelindex。
这个引用会保持更新。
临时的modelindexes由QModelIndex提供,而具有持久能力的modelindexes则由QPersistentModelIndex提供。
在获取对应一个数据项的modelindex时,需要考虑有关于model的三个属性:
行数,列数,父项的modelindex。
行与列
在最基本的形式中,一个model可作为一个简单的表来访问,每个数据项由行,列数来定位。
这必不意味着
底层的数据用数组结构来存储。
行和列的使用仅仅是一种约定,它允许组件之间相互通讯。
可以通过指定
model中的行列数来获取任一项数据,可以得到与数据项一一对应的那个index。
QModelIndexindex=model->index(row,column,...);
Model为简单的,单级的数据结构如list与tables提供了接口,它们如上面代码所显示的那样,不再需要别的信息被提供。
当我们在获取一个modelindex时,我们需要提供另外的信息。
上图代表一个基本的tablemodel,它的每一项用一对行列数来定位。
通过行列数,可以获取代表一个数据项的modelindex.
QModelIndexindexA=model->index(0,0,QModelIndex());
QModelIndexindexB=model->index(1,1,QModelIndex());
QModelIndexindexC=model->index(2,1,QModelIndex());
一个model的顶级项,由QModelIndex()取得,它们上式被用作父项。
父项
类似于表的接口在搭配使用table或listview时理想的,这种行列系统与view显示的方式是确切匹配的。
然则,像treeviews这种结构需要model提供更为灵活的接口来访问数据项。
每个数据项可能是别的项的
父项,上级的项可以获取下级项的列表。
当获取model中数据项的index时,我们必须指定关于数据项的父项的信息。
在model外部,引用一个数据
项的唯一方法就是通过modelindex,因此需要在求取modelindex时指定父项的信息。
QModelIndexindex=model->index(row,column,parent);
上图中,A项和C项作为model中顶层的兄弟项:
QModelIndexindexA=model->index(0,0,QModelIndex());
QModelIndexindexC=model->index(2,1,QModelIndex());
A有许多孩子,它的一个孩子B用以下代码获取:
QModelIndexindexB=model->index(1,0,indexA);
项角色
model中的项可以作为各种角色来使用,这允许为不同的环境提供不同的数据。
举例来说,Qt:
:
DisplayRole被用于访问一个字符串,它作为文本会在view中显示。
典型地,每个数据项都可以为许多不同的角色提供数据,标准的角色在Qt:
:
ItemDataRole中定义。
我们可以通过指定modelindex与角色来获取我们需要的数据:
QVariantvalue=model->data(index,role);
角色指出了从model中引用哪种类型的数据。
views可以用不同的形式显示角色,因此为每个角色提供正确
的信息是非常重要的。
通过为每个角色提供适当数据,model也为views和delegates提供了暗示,如何正确地
把这些数据项显给用户。
不同的views可以自由地解析或忽略这些数据信息,对于特殊的场合,也可以定义
一些附加的角色。
概念总结:
1,Modelindexes为views与delegages提供model中数据项定位的信息,它与底层的数据结构无关。
2,通过指定行,列数,父项的modelindex来引用数据项。
3,依照别的组件的要求,modelindexes被model构建。
4,使用index()时,如果指定了有效的父项的modelindex,那么返回得到的modelindex对应于父项的某个孩子。
5,使用index()时,如果指定了无效的父项的modelindex,那么返回得到的modelindex对应于顶层项的某个孩子。
6,角色对一个数据项包含的不同类型的数据给出了区分。
使用ModelIndexes
QDirModel*model=newQDirModel;
QModelIndexparentIndex=model->index(QDir:
:
currentPath());
intnumRows=model->rowCount(parentIndex);
for(introw=0;row{
QModelIndexindex=model->index(row,0,parentIndex);
tringtext=model->data(index,Qt:
:
DisplayRole).toString();
//Displaythetextinawidget.
}
以上的例子说明了从model中获取数据的基本原则:
1,model的尺寸可以从rowCount()与columnCount()中得出。
这些函数通常都需要一个表示父项的modelindex。
2,modelindexes用来从model中访问数据项,数据项用行,列,父项modelindex定位。
3,为了访问model顶层项,可以使用QModelIndex()指定。
4,数据项为不同的角色提供不同的数据。
为了获取数据,除了modelindex之外,还要指定角色。
创建新的Models
介绍
model/view组件之间功能的分离,允许创建model利用现成的views。
这也可以使用标准的功能图形用户接口组件像QListView,QTableView和QTreeView来显示来自各种数据源的数据为。
QAbstractListModel类提供了非常灵活的接口,允许数据源以层次结构的形式来管理信息,也允许以某种
方式对数据进行插入、删除、修改和存储。
它也提供了对拖拽操作的支持。
QAbstractListModel与QAbstractTableModel为简单的非层次结构的数据提供了接口,对于比较简单的list和tablemodels来说,这是不错的一个开始点。
设计一个Model
当我们为存在的数据结构新建一个model时,首先要考虑的问题是应该选用哪种model来为这些数据提供接口。
假如数据结构可以用数据项的列表或表来表示,那么可以考虑子类化QAbstractListModel或QAbstractTableModel
既然这些类已经合理地对许多功能提供缺省实现。
然而,假如底层的数据结构只能表示成具有层次结构的树型结构,那么必须得子类化QAbstractItemModel。
无论底层的数据结构采取何种形式,在特定的model中实现标准的QAbstractItemModelAPI总是一个不错的主意,这使得可以使用更自然的方式对底层的数据结构进行访问。
这也使得用数据构建model更为容易,其他
的model/view组件也可以使用标准的API与之进行交互。
一个只读model示例
这个示例实现了一个简单的,非层次结构的,只读的数据model,它基于QStringistModel类。
它有一个QStringList作为它内部的数据源,只实现了一些必要的接口。
为了简单化,它子类化了QAbstractListModel,这个基类提供了合理的缺省行为,对外提供了比QAbstractItemModel更为简单的接口。
当我们实现一个model时,不要忘了QAbstractItemModel本身不存储任何数据,它仅仅提供了给views访问
数据的接口。
classStringListModel:
publicQAbstractListModel
{
Q_OBJECT
public:
StringListModel(constQStringList&strings,QObject*parent=0)
:
QAbstractListModel(parent),stringList(strings){}
introwCount(constQMode