一、模型视图原理
在Qt的界面中,常用到List、Table、Tree的数据结构视图。
实现上述三个数据结构视图有两种方式:一种是直接用QListWidget、QTreeWidget 和 QtableWidget 这种便利类。这种方式的优点是操作便捷、直观,缺点是可能会出现数据不同步的问题。另一种方式是model/view方式,即通过自定义数据结构、模型满足需要。对于新手而言,model/view可较为繁琐,但上手后将会极大提高程序的性能。两种方式与数据的交互方式完全不同,Widget通过相对于的Item项管理数据,便捷性较差;而model/view模式可以将数据集和视图分开管理,好处是一个model可以在多个view上使用,且开发者只需为model提供数据,而不必关心视图问题。
1 MVD模式介绍
MVC 把图形界面分为三个部分:模型(Model)、视图(View)、控制器(Controller),在Qt中,这里的Contorller为Delegate代理,用于数据处理。
模型:用于管理数据,注意,数据不一定需要位于模型之中。
Qt 使用抽象类 QAbstractItemModel 来描述模型,所有的模型都是通过子类化该抽像类而实现的。Qt 实现了一些标准的现成模型。
视图:就是呈现在用户面前的界面外观,视图负责把模型中的数据显示给用户。
Qt 使用抽象类 QAbstractItemView 来描述视图,所有的视图都是通过子类化该抽像类而实现的。Qt 实现了一些标准的现成视图,比如 QListView(列表视图), QTableView(表格视图),QTreeView(树视图)等。
QListWidget、QTreeWidget 和 QtableWidget 3个可用于数据编辑的组件。这 3 个类称为便利类(convenience classes),它们分别是 3 个视图类的子类,其层次关系如图 所示。
数据:是一个统称,既可以是数据项也可以是数据元素。
数据项:是由多个数据元素组成的,每个数据元素都有自已的角色。
节点(单元格、项目):这三个概念通常用于指模型中的某一个数据项所在的位置,只是对于不同的模型结构会有不同的称呼,比如树形结构通常称为节点,表格结构通常称为单元格,而项目是一种更通用的称呼,Qt 中通常称其为项目(item)或数据项。
QStandardItemModel model(3,3,this); //创建一个 3 行 3 列的表格结构的模型
QTableView v1; //创建一个表格视图
//设置模型的数据,使用索引的形式设置每个数据项的值
model.setData(model.index(0,0),123);
model.setData(model.index(0,1),222);
model.setData(model.index(0,2),333);
model.setData(model.index(1,0),444);
model.setData(model.index(1,1),555);
model.setData(model.index(1,2),666);
model.setData(model.index(2,0),777);
model.setData(model.index(2,1),888);
model.setData(model.index(2,2),999);
v1.setModel(&model); //设置视图 v1 的模型
v1.show(); //显示视图
代理:处理用户在界面的交互数据操作。(将在后面介绍)
二、模型索引与数据角色
1模型结构
Qt 中,无论数据被存储为何种数据结构,模型总是以层次结构(即树形结构)来表示数据,视图按照此约定来访问模型中的数据,若数据是列表(list)或表格(tab)结构的数据,则可以把其看作是只含有顶层节点,不含任何子节点的树形结构。也就是说,我们在子类化QAbstractItemModel 来自定义自已的模型结构时,始终应以树形结构为出发点,来组织自已的模型结构
2模型索引与项的角色
为了保证数据的表示与数据存取方式隔离,数据模型中引入了模型索引的概念。通过数据模型存取的每个数据都有一个模型索引,视图组件和代理都通过模型索引来获取数据。
QModelIndex 表示模型索引的类。模型索引提供数据存取的一个临时指针,用于通过数据模型提取或修改数据。因为模型内部组织数据的结构随时可能改变,所以模型索引是临时的。如果需要使用持久性的模型索引,则要使用 QPersistentModelIndex 类。
2-1行号和列号
数据模型的基本形式是用行和列定义的表格数据,但这并不意味着底层的数据是用二维数组存储的,使用行和列只是为了组件之间交互方便的一种规定。通过模型索引的行号和列号就可以存取数据。在创建模型索引的函数中需要传递行号、列号和父项的模型索引。对于列表和表格模式的数据模型,顶层节点总是用 QModelIndex() 表示。
①、树形结构:
//获取数据项 A 的模型索引,A 的父索引由一个无效模型索引指定。
QModelIndex A = model.index(0,0, QModelIndex());
//获取数据项 B 的模型索引,B 位于父索引 iA 的第 1 行第 1 列位置。
QModelIndex B = model.index(1,1, A);
QModelIndex C = model.index(1,1, QModelIndex());
②、表格结构
//获取数据项 B 的模型索引,B 是顶级数据项,因此其父索引是一个无效索引。
QModelIndex B = model.index(1,2, QModelIndex());
③、列表结构
//获取数据项 B 的模型索引,B 是顶级数据项,因此其父索引是一个无效索引。
QModelIndex B = model.index(1,0, QModelIndex());
2-2项的角色
同一个类型的数据可以作为不同的角色(或作用)来使用,比如对于字符串"EEE",可把该字符串以文本的形式显示在视图的相应位置上,也可把该字符串作为工具提示使用,还可把该字符串作为 what's this 的帮助提示等。由此可见,数据的角色,决定了该数据在视图中的显示方式,角色不同,显示方式也不同。
model.setData(model.index(0,1),222,Qt::DisplayRole); //设置显示的文本
model.setData(model.index(0,1),QIcon("F:/1i.png"),Qt::DecorationRole); //设置图标
model.setData(model.index(0,1),"EEE",Qt::ToolTipRole); //设置工具提示
如果自己重载data函数,可以这样使用项角色
QVariant MyModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
int col = index.column();
// generate a log message when this method gets called
qDebug() << QString("row %1, col%2, role %3")
.arg(row).arg(col).arg(role);
switch(role){
case Qt::DisplayRole:
if (row == 0 && col == 1) return QString("<--left");
if (row == 1 && col == 1) return QString("right-->");
return QString("Row%1, Column%2")
.arg(row + 1)
.arg(col +1);
break;
case Qt::FontRole:
if (row == 0 && col == 0)
{
QFont boldFont;
boldFont.setBold(true);
return boldFont;
}
break;
case Qt::BackgroundRole:
if (row == 1 && col == 2)
{
QBrush redBackground(Qt::red);
return redBackground;
}
break;
case Qt::TextAlignmentRole:
if (row == 1 && col == 1)
{
return Qt::AlignRight + Qt::AlignVCenter;
}
break;
case Qt::CheckStateRole:
if (row == 1 && col == 0) //add a checkbox to cell(1,0)
{
return Qt::Checked;
}
}
return QVariant();
}