Qt中的模型/视图架构就是用来实现大量数据的存储、处理及其显示的。
参考文献:《Qt Creator 快速入门》第三版 霍亚飞编著
模型/视图架构
MVC(Model-View-Controller)是一种起源于Smalltalk的设计模式,经常用于创建用户界面。MVC包含了3个组件:模型(Model)是应用对象,用来表示数据;视图(View)是模型的用户界面,用来显示数据;控制(controller)定义了用户界面对用户输入的反应。
模型/视图框架中的众多类可以分为3组:模型、视图和委托,它们之间使用信号和槽来实现通信:
》当数据源的数据发生改变时,模型发出信号告知视图;
》当用户与显示的项目交互时,视图发出信号来提供交互信息;
》当编辑项目时,委托发出信号,告知模型和视图编辑器状态。
一、模型类
在模型/视图架构中,模型提供了一个标准的接口供视图和委托来访问数据。这个标准接口使用QAbtructItemModel类来定义。
常见的三种模型分别是列表模型、表格模型和树模型。
模型索引
为了确保数据的表示与数据的获取相分裂,Qt引入了模型索引的概念。每一块可以通过模型获取的数据都使用一个模型索引来表示,视图和委托都使用这些索引来请求数据并显示。
模型索引由QModelIndex类提供,它是对一块数据的临时引用,可以用来检索或者修改模型中的数据。如果要获得一个数据项的模型索引,则必须指定模型的3个属性:行号、列号和父项的模型索引。
QModelIdex index=mode->index(row,colum,parent);
行号和列号都是从0开始,顶层数据项可以使用QModelIndex()作为父项索引。
如下图Tree Model中的A、B、C 3项的模型索引可以使用如下代码获得。
QModelIndex indexA=model->index(0,0,QModelIndex());
QModelIndex indexB=model->index(1,0,indexA);
QModelIndex indexC=model->index(2,0,QModelIndex());
项角色
模型中的数据项可以作为各种角色在其他组件中使用,允许为不同的情况提供不同类型的数据。通常情况下数据项目包含了一些不同角色的数据,这些标准的角色由Qt::ItemDataRole来定义。
二、使用标准项模型QStandardItemMoodel的例子
QStandardItemMoodel类提供了一个通用的模型来存储自定义数据。QStrandardItemModel中的项由QStandardItem类提供,该类可以使用setIcon等函数来指定图标等属性,也可以使用setData函数来设置这些属性。见下面例子。
新建空的Qt项目modelview1,在项目文件中添加“QT += widgets",添加main.cpp文件,更改其内容如下
#include <QApplication>
#include <QFileSystemModel>
#include <QTreeView>
#include <QListView>
#include <QStandardItemModel>
#include <QDebug>
int main(int argc,char* argv[])
{
QApplication app(argc,argv);
//创建标准项模型
QStandardItemModel model;
//获取模型的根项(Root Item),根项是不可见的
QStandardItem* parentItem=model.invisibleRootItem();
//创建标准项item0,并设置显示文本,图标和工具提示
QStandardItem* item0=new QStandardItem;
item0->setText("A");
QPixmap pixmap0(50,50);
pixmap0.fill("red");
item0->setIcon(QIcon(pixmap0));
item0->setToolTip("indexA");
//将创建的标准项作为根项的子项
parentItem->appendRow(item0);
//将创建的标准项作为新的父项
parentItem =item0;
//创建新的标准项,将它作为item0的子项
QStandardItem* item1=new QStandardItem;
item1->setText("B");
QPixmap pixmap1(50,50);
pixmap1.fill("blue");
item1->setIcon(QIcon(pixmap1));
item1->setToolTip("indexB");
parentItem->appendRow(item1);
//创建新的标准项,这里使用了另一种方法来设置文本、图标和工具提示
QStandardItem* item2=new QStandardItem;
QPixmap pixmap2(50,50);
pixmap2.fill("green");
item2->setData("C",Qt::EditRole);
item2->setData("indexC",Qt::ToolTipRole);
item2->setData(QIcon(pixmap2),Qt::DecorationRole);
parentItem->appendRow(item2);
//在树视图中显示模型
QTreeView view;
//为视图指定模型
view.setModel(&model);
view.show();
//获取item0的索引并输出item0的子项数目,然后输出了item1的显示文本和工具提示
QModelIndex indexA=model.index(0,0,QModelIndex());
qDebug()<<"indexA row count:"<<model.rowCount(indexA);
QModelIndex indexB=model.index(0,0,indexA);
qDebug()<<"indexB text:"<<model.data(indexB,Qt::EditRole).toString();
qDebug()<<"indexB toolTip:"<<model.data(indexB,Qt::ToolTipRole).toString();
return app.exec();
}
运行效果
三、自定义新的模型
当要为一个已经存在的数据结构创建新的模型时,需要考虑使用哪种类型的模型来为数据提供接口。如果数据结构可以表示为项目列表或者表格,那么可以子类化QAbstarctListModel或QAbstracTableModel。下面的例子,创建一个基于字符串列表的模型,以QAbstractListModel作为基类。对于自定的类要实现的几个关键点:
数据源:
添加QStringList 成员对象作为内部数据源。
显示:
1、实现父类纯虚接口QVariant data(const QModelIndex& index,int role)const;返回指定模型索引的数据
2、实现父类纯虚接口int rowCount(const QModelIndex& parent=QModelIndex())const;返回指定模型行数
3、重写父类虚接口QVariant headerData(int section, Qt::Orientation orientation,int role = Qt::DisplayRole) const;它可以在树和表格视图的表头显示一些内容(这一项非必须)
编辑功能:
1、重写父类虚接口Qt::ItemFlags flags(const QModelIndex &index) const;委托编辑器以前会检测项目是否是可编辑的,模型必须让委托知道它的项目是可编辑的,这里为模型中的每一个项目返回一个正确的标志来达到这个目的,具体看下面例子代码。
2、重写父类虚接口virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);该函数为委托向模型中设置数据提供了一条途径(委托并不需要知道怎样执行的)。当数据被设置后,模型必须让视图知道有数据已经改变了,在该函数中是通过发射dataChanged()信号实现的,详见代码部分。
增删功能
1、重写父类虚接口bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());实现增加行
2、重写父类虚接口 virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());实现删除行
最后自定义模型类StringListModel的头文件如下
#ifndef STRINGLISTMODEL_H
#define STRINGLISTMODEL_H
#include <QAbstractListModel>
#include <QStringList>
class StringListModel : public QAbstractListModel
{
Q_OBJECT
public:
StringListModel(const QStringList& strings,QObject* parent=0)
:QAbstractListModel(parent),stringList(strings){}
int rowCount(const QModelIndex& parent=QModelIndex())const;
QVariant data(const QModelIndex& index,int role)const;
QVariant headerData(int section, Qt::Orientation orientation,int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex& index)const;
bool setData(const QModelIndex& index,const QVariant &value,int role=Qt::EditRole);
bool insertRows(int position,int rows,const QModelIndex& index=QModelIndex());
bool removeRows(int position,int rows,const QModelIndex& index=QModelIndex());
private:
; QStringList stringList;
};
#endif // STRINGLISTMODEL_H
StringListModel类的实现如下
#include "stringlistmodel.h"
int StringListModel::rowCount(const QModelIndex &parent) const
{
return stringList.count();
}
QVariant StringListModel::data(const QModelIndex &index, int role)const
{
if(!index.isValid())
return QVariant();
if(index.row()==stringList.size())
return QVariant();
if(role==Qt::DisplayRole||role==Qt::EditRole)
return stringList.at(index.row());
else
return QVariant();
}
QVariant StringListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(role!=Qt::DisplayRole)
return QVariant();
if(orientation==Qt::Horizontal)
return QString("Colum %1").arg(section);
else
return QString("Row %1").arg(section);
}
Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
if(!index.isValid())
return Qt::ItemIsEnabled;
return QAbstractItemModel::flags(index)|Qt::ItemIsEditable;
}
bool StringListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if(index.isValid()&&role==Qt::EditRole)
{
stringList.replace(index.row(),value.toString());
emit dataChanged(index,index);
return true;
}
return false;
}
bool StringListModel::insertRows(int position, int rows, const QModelIndex &index)
{
beginInsertRows(QModelIndex(),position,position+rows-1);
for(int row=0;row<rows;row++)
{
stringList.insert(position,"");
}
endInsertRows();
return true;
}
bool StringListModel::removeRows(int position, int rows, const QModelIndex &index)
{
beginRemoveRows(QModelIndex(),position,position+rows-1);
for(int row=0;row<rows;row++)
{
stringList.removeAt(position);
}
endRemoveRows();
return true;
}
main.cpp中测试代码如下
#include <QApplication>
#include "stringlistmodel.h"
#include <QListView>
#include <QTableView>
int main(int argc,char* argv[])
{
QApplication app(argc,argv);
QStringList list;
list<<"a"<<"b"<<"c";
StringListModel model(list);
QListView listView;
listView.setModel(&model);
listView.show();
QTableView tableView;
tableView.setModel(&model);
tableView.show();
model.insertRows(3,2);
model.removeRows(0,1);
return app.exec();
}
最后运行效果如下,可以编辑