MVC模型/视图编程
Qt中的模型/视图架构用来实现大量的数据存储、处理及显示。
MVC(Model-View-Controller)包括了3个组件:
- 模型(Model)是应用对象,用来表示数据;
- 视图(View)是模型的用户界面,用来显示数据;
- 控制(Controller)定义了用户界面对用户输入的反应方式。委托(Delegate)用于定制数据的渲染和编辑方式。
1. 模型
-
所有的模型都基于QAbstractItemModel类,该类提供了十分灵活的接口来处理各种视图,这些视图可以将数据的表现形式为表格(table)、列表(list)、树(tree)。
-
Qt提供了一些现成的模型来处理数据项:
-
QStringListModel 存储简单的QString项目列表;
-
QStandardItemModel管理复杂的属性结构数据项,每一个数据项可以包含任意的数据;
-
QFileSystemModel提供了本地文件系统中文件和目录信息;
-
QSqlQueryModel、QSqlTableModel和QSqlRelationTableModel用来访问数据库。
-
标准模型还无法满足需要时,可子类化QAbstractItemModel、QAbstractListModel或QAbstractTableModel来创建自定义的模型。
常见的3种模型为列表模型、表格模型、树模型,如下图所示:
模型索引
- 为确保数据的表示与数据的获取相分离,Qt引入了模型索引的概念,输入和委托均可通过模型索引来请求数据并显示。只有模型需要知道怎样获取数据,被模型管理的数据类型可以被广泛的定义。模型索引包含一个指针,指向创建他们的模型,使用多个模型时可避免混淆。模型索引QModelIndex类提供对一块数据的临时引用,用来修改或检索模型中的数据,获取一个数据项的模型索引必须指定模型的3个属性:行号、列号和父项的模型索引。
- 如:QModelIndex index = model->index(row,column,parent);
- 也可通过模型指定的相关数据项对应的模型索引以及特定的角色来获取需要的类型数据,
- 如:QVariant value = model->data(index,role);
常用的角色类型:
常量 |
描述 |
Qt::DisplayRole |
数据被渲染为文本(数据为QString类型) |
Qt::DecorationRole |
数据被渲染为图标等装饰(数据为QColor,QIcon,或QPixmap类型) |
Qt::EditRole |
数据可以在编辑器中进行编辑(数据为QString类型) |
Qt::ToolTipRole |
数据显示在数据项的工具提示中(数据类型为QString) |
Qt::StatusTipRole |
数据显示在状态栏中(数据为QString类型) |
Qt::WhatsThisRole |
数据显示在数据项的“What’s This?”模式下(数据为QString类型) |
Qt::SizeHintRole |
数据项的大小提示,将会应用到视图(数据为QSize类型) |
Qt::FontRole |
默认代理的绘制使用的字体 |
Qt::TextAlignmentRole |
默认代理的对齐方式 |
Qt::BackgroundRole |
默认代理的背景画刷 |
Qt::ForegroundRole |
默认代理的前景画刷 |
Qt::CheckStateRole |
默认代理的检查框状态 |
Qt::UserRole |
用户自定义的数据的起始位置 |
- 新建名称testModelIndex, 选择空项目, 工程文件中添加 QT += widgets
- 添加main.cpp文件
2.视图
- Qt提供了QListView、QTableView视图、QTreeView视图分别实现列表、表格与树视图效果。
- QListView将数据项显示为一个列表;
- QTableView将模型中的数据显示在一个表格中;
- QTreeView将模型中的数据项显示在具有层次的列表中。
- QTableView和QTreeView在显示项目的时候同时还可以显示标头,通过QHeaderView类实现。
- 自定义视图类是基于QAbstractItemView抽象基类,如实现条形图,饼状图等特殊显示方式。
视图类的选择行为
常量 |
描述 |
QAbstractView::SelectItems |
选择单个项目 |
QAbstractView::SelectRows |
只选择行 |
QAbstractView::SelectColumns |
只选择列 |
视图类的选择模式
常量 |
描述 |
QAbstractItemView::SigleSelection |
当用户选择一个项目时,所有已经选择的项目将成为未选择状态,而且用户无法在已经选择的项目上单击来取消选择 |
QAbstractView::ContiguousSelection |
用户单击一个项目的同时,按Shift键,则所有当前项目和单击项目之间的项目都将被选择或取消选择 |
QAbstractView::ExtendedSelection |
具有ContiguousSelection的特性,且可按Ctrl键进行不连续选择 |
QAbstractView::MultiSelection |
用户选择一个项目时不影响其他已经选择的项目 |
QAbstractView::NoSelection |
项目无法被选择 |
选择模型更新方式
常量 |
描述 |
QItemSelectionModel::NoUpdate |
不做选择 |
QItemSelectionModel::Clear |
选择被清除 |
QItemSelectionModel::Select |
选择指定索引 |
QItemSelectionModel::Deselect |
取消指定索引的选择 |
QItemSelectionModel::Toggle |
切换指定索引的选择 |
QItemSelectionModel::Current |
当前选择被更新 |
QItemSelectionModel::Rows |
索引扩展为跨行 |
QItemSelectionModel::Columns |
索引扩展为跨列 |
QItemSelectionModel::SelectCurrent |
Select Current组合 |
QItemSelectionModel::ToggleCurrent |
Toggle Current组合 |
QItemSelectionModel::ClearAndSelect |
Clear Select组合 |
3. 委托
- 在模型/视图框架中,QAbstractItemDelegate是委托类的抽象基类,Qt默认的委托实现由QStyledItemDelegate类提供,这也被用作Qt标准视图的默认委托,选择 QStyledItemDelegate或QItemDelegate中其一来为视图中的项目绘制和提供编辑器。
- 不同的是QStyledItemDelegate使用当前的样式来绘制项目,实现自定义委托建议使用QStyledItemDelegate作为基类。
- Qt提供了项目试图的便捷类,这些类底层通过模型/视图框架实现。这些部件分别是QListWidget提供一个项目列表,QTreeWidget显示一个多层次的树结构,QTableWidget提供了一个以项目作为单元的表格。它们每一个类都继承了QAbstractItemView类的行为。之所以成为便捷因其用起来比较简单,使用于少量的数据的存储和显示。
- 因没有将视图与模型分离,所以没有视图类灵活,不能和任意的模型一起使用。
模型视图使用示例(查询本机文件系统)
- 新建空项目testModelView,pro文件添加 QT += widgets,并添加main.cpp文件
main.cpp文件代码:
#include <QApplication>
#include <QAbstractItemModel> //抽象类模型
#include <QAbstractItemView> //视图
#include <QItemSelectionModel> //项选择模型
#include <QDirModel> //目录模型
#include <QTreeView> //树视图
#include <QListView> //列表视图
#include <QTableView> //表格视图
#include <QSplitter> //窗口分割
int main(int argc, char*argv[]){
QApplication app(argc, argv);
QDirModel model; //创建目录模型
QTreeView tree; //创建树视图
QListView list; //创建列表视图
QTableView table; //创建表格视图
tree.setModel(&model); //树视图设置模型
list.setModel(&model); //列表视图设置模型
table.setModel(&model); //表格视图设置模型
//设置视图对象的选择方式为多选
tree.setSelectionMode(QAbstractItemView::MultiSelection);
list.setSelectionMode(tree.selectionMode()); //将树的选择方式传给列表
table.setSelectionMode(tree.selectionMode()); //将树的选择方式传给表格
//双击树视图后, 发射信号, 列表与表格视图刷新双击树视图的内容
QObject::connect(&tree, SIGNAL(doubleClicked(QModelIndex)),
&list, SLOT(setRootIndex(QModelIndex)));
QObject::connect(&tree, SIGNAL(doubleClicked(QModelIndex)),
&table, SLOT(setRootIndex(QModelIndex)));
//设置窗口分割
QSplitter *splitter = new QSplitter;
splitter->addWidget(&tree); //添加树模型
splitter->addWidget(&list); //添加列表模型
splitter->addWidget(&table); //添加表格模型
splitter->setWindowTitle("模型/视图"); //设置标题
splitter->show(); //显示窗体
return app.exec();
}
testModelview运行效果
标准模型项操作示例:
- 新建空项目testQModelIndex,pro文件添加QT+= widgets,并添加main.cpp文件,编辑main.cpp文件代码
#include <QApplication>
#include <QTreeView>
#include <QStandardItemModel>
#include <QDebug>
int main(int argc, char*argv[]){
QApplication app(argc, argv);
//创建标准项模型
QStandardItemModel model;
//获取标准项模型的根项, 根项是不可见的
QStandardItem *parentItem = model.invisibleRootItem();
//创建标准项
QStandardItem *item_0 = new QStandardItem;
item_0->setText("A");
//设置图标
QPixmap pixmap_0(50,50); //设置宽和高为50
pixmap_0.fill(Qt::red); //设置颜色为红色
item_0->setIcon(pixmap_0); //为标准项设置图标
item_0->setToolTip("A项的提示"); //设置工具提示
parentItem->appendRow(item_0);
parentItem = item_0; //将 item_0 作为父项
//创建 item_0 的子项
QStandardItem *item_1 = new QStandardItem;
item_1->setText("B");
//设置 item_1 的图标
QPixmap pixmap_1(50,50);
pixmap_1.fill(Qt::blue);
item_1->setIcon(pixmap_1);
item_1->setToolTip("B项的提示");
//将子项添加到父项下
parentItem->appendRow(item_1);
QStandardItem *item_2 = new QStandardItem;
QPixmap pixmap_2(50,50);
pixmap_2.fill(Qt::green);
item_2->setData("C", Qt::EditRole); //设置文本,可编辑角色
item_2->setData("C项的提示", Qt::ToolTipRole); //工具提示角色
item_2->setData(QIcon(pixmap_2), Qt::DecorationRole); //图标,渲染角色
parentItem->appendRow(item_2);
//在树视图中显示数据
QTreeView view;
view.setModel(&model);
view.show();
//获取信息
QModelIndex index_A = model.index(0,0, QModelIndex());
qDebug() << "index_A 包含行数: "<<
model.rowCount(index_A);
QModelIndex index_B = model.index(0,0, index_A);
qDebug() << "index_B 文本信息:"
<< model.data(index_B,Qt::EditRole).toString();
qDebug() << "index_B 提示信息:"
<< model.data(index_B,Qt::ToolTipRole).toString();
return app.exec();
}
testModelIndex运行效果
自定义模型示例:军事武器模型
- 新建空项目testCustomModel,pro文件添加QT+= widgets,并添加main.cpp文件
- 添加c++类WeaponModel继承自QAbstractTableModel.
编辑weaponmodel.h代码
weaponmodel.h
#ifndef WEAPONMODEL_H
#define WEAPONMODEL_H
#include <QAbstractTableModel>
class WeaponModel : public QAbstractTableModel {
public:
WeaponModel(QObject *parent = 0);
//重写父类的函数
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
private:
QVector<short> m_army; //军队
QVector<short> m_weaponType; //武器类型
QMap<short,QString> m_armyMap; //军队映射
QMap<short,QString> m_weaponMap; //武器映射
QStringList m_weapon; //武器链表
QStringList m_header; //武器表头
void populateModel(); //初始化表格数据
};
#endif // WEAPONMODEL_H
weaponmodel.cpp
#include "weaponmodel.h"
WeaponModel::WeaponModel(QObject *parent) : QAbstractTableModel(parent) {
m_armyMap[1] = QString("空军");
m_armyMap[2] = QString("海军");
m_armyMap[3] = QString("陆军");
m_armyMap[4] = QString("火箭军");
m_weaponMap[1] = QString("轰炸机");
m_weaponMap[2] = QString("战斗机");
m_weaponMap[3] = QString("航空母舰");
m_weaponMap[4] = QString("驱逐舰");
m_weaponMap[5] = QString("武装直升机");
m_weaponMap[6] = QString("坦克");
m_weaponMap[7] = QString("洲际核导弹");
m_weaponMap[8] = QString("超音速导弹");
populateModel();
}
int WeaponModel::rowCount(const QModelIndex &parent) const {
//返回军队的行数
return m_army.size();
}
int WeaponModel::columnCount(const QModelIndex &parent) const {
//返回列表头的列数
return m_header.size();
}
//返回索引的数据, 将数字映射成字符串
QVariant WeaponModel::data(const QModelIndex &index, int role) const {
if(!index.isValid()) return QVariant(); //传入值无效
if(role == Qt::DisplayRole){
switch(index.column()){ //当前模型的列
case 0:
return m_armyMap[m_army[index.row()]];
break;
case 1:
return m_weaponMap[m_weaponType[index.row()]];
break;
case 2:
return m_weapon[index.row()];
break;
default:
return QVariant();
}
}
return QVariant();
}
QVariant WeaponModel::headerData(int section, Qt::Orientation orientation, int role) const {
if(role == Qt::DisplayRole && orientation == Qt::Horizontal){
return m_header[section];
}
return QAbstractTableModel::headerData(section, orientation, role);
}
void WeaponModel::populateModel() {
//初始化表头
m_header << QString("军种")
<< QString("武器种类")
<< QString("武器");
//初始化军队数据
m_army << 1 << 1 << 2 << 2 << 3 << 3 << 4 << 4;
//初始化武器类型数据
m_weaponType << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8;
//初始化武器
m_weapon << QString("B-2") << QString("歼-20")
<<QString("山东舰") << QString("济南舰")
<< QString("武直-10") << QString("99式步兵坦克")
<< QString("东风-41") << QString("东风-17");
}
main.cpp
#include <QApplication>
#include <QTableView>
#include "weaponmodel.h"
int main(int argc, char*argv[]){
QApplication app(argc, argv);
WeaponModel model; //创建自定义模型
QTableView view; //创建表格视图
view.setModel(&model); //指定自定义模型在表格视图显示
view.setWindowTitle("自定义表格视图"); //设置窗体标题
view.resize(400,300); //设置窗体大小
view.show(); //显示表格窗体
return app.exec();
}
testCustomModel运行效果