(一)Model/View 结构
Model-View-Controller(MVC), 是从Smalltalk发展而来的一种设计模式,常用于创建用户界面。
MVC包含3个组件:
- Model: 应用对象,用来表示数据
- View: 模型的用户界面,用来显示数据
- Controller: 定义了用户界面对用户输入的反应方式
Delegate(委托):为了对用户输入进行灵活处理被引入,用于定制数据的渲染和编辑方式
其中,模型与数据源进行通信,为架构中的其他组件提供了接口。视图从模型中获得模型索引(Model Index),模型索引用来表示数据项。在标准的视图中,委托渲染数据项,当编辑项目时,委托使用模型索引直接与模型进行通信。
(二)Models
1.所有模型的基类:
QAbstractItemModel
2.可子类化以下模型创建自定义模型:
QAbstractItemModel
QAbstractListModel
QAbstractTableModel
3.其他现成的模型:
QStringListModel: 用来存储一个简单的QString项目列表
QStandardItemModel: 管理复杂的树形结构数据项,每一个数据项可以包含任意的数据
QFileSystemModel: 提供了本地文件系统中文件和目录信息
QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel: 用来访问数据库
4.基本概念
①常见的模型:列表模型、表格模型、树模型
②模型索引:每一块可以通过模型获取的数据都使用一个模型索引来表示,视图和委托都使用这些索引来请求数据项并显示。模型索引包含一个指针,指向创建它们的模型。由QModelIndex类提供,它是对一块数据的临时引用,可以用来检索或修改模型中的数据。
③行和列:要获得一个数据项的模型索引,必须指定模型的3个属性:行号、列号、父项的模型索引
QModelIndex index = model->index(row, column, parent);
④父项:当数据项是以根项为父项时,父项的模型索引可以用QModelIndex()表示
⑤项角色:模型中的数据项可以作为各种角色在其他组件中会用,允许为不同的情况提供不同类型的数据
常用角色类型:
常量 |
描述 |
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 |
数据项的大小提示,将会应用到视图(数据为QString类型) |
5.示例代码
<1> 文件模型的使用
#include <QApplication>
#include <QFileSystemModel>
#include <QTreeView>
#include <QListView>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//创建文件系统模型
QFileSystemModel model;
//指定要监视的目录
model.setRootPath(QDir::currentPath());
//创建树视图
QTreeView tree;
//为视图指定模型
tree.setModel(&model);
//指定根索引
tree.setRootIndex(model.index(QDir::currentPath()));
//创建列表视图
QListView list;
list.setModel(&model);
list.setRootIndex(model.index(QDir::currentPath()));
tree.show();
list.show();
return a.exec();
}
<2> 模型索引的使用
#include <QApplication>
#include <QTreeView>
#include <QDebug>
#include <QStandardItemModel>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QStandardItemModel model;
//获取模型的根项,根项不可见
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);
QStandardItem *item1 = new QStandardItem;
item1->setText("B");
QPixmap pixmap1(50, 50);
pixmap1.fill("blue");
item1->setIcon(QIcon(pixmap1));
item1->setToolTip("indexB");
item0->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);
item0->appendRow(item2);
//在树视图中显示模型
QTreeView view;
view.setModel(&model);
view.show();
//获取数据项
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 a.exec();
}
<3> 自定义模型方法
MyStrListModel.h
#include <QAbstractListModel>
#include <QStringList>
class MyStrListModel : public QAbstractListModel
{
Q_OBJECT
public:
MyStrListModel(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 row, int count, const QModelIndex &parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
private:
QStringList stringList;
};
MyStrListModel.cpp
#include "MyStrListModel.h"
int MyStrListModel::rowCount(const QModelIndex &parent) const
{
return stringList.count();
}
QVariant MyStrListModel::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 MyStrListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
{
return QVariant();
}
if (orientation == Qt::Horizontal)
{
return QString("Column %1").arg(section);
}
else
{
return QString("Row %1").arg(section);
}
}
Qt::ItemFlags MyStrListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
{
return Qt::ItemIsEnabled;
}
return QAbstractItemModel::flags(index)| Qt::ItemIsEditable;
}
bool MyStrListModel::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 MyStrListModel::insertRows(int row, int count, const QModelIndex &parent)
{
beginInsertRows(QModelIndex(), row, count + row);
for (int n = 0; n < count; ++n)
{
stringList.insert(row, "");
}
endInsertRows();
return true;
}
bool MyStrListModel::removeRows(int row, int count, const QModelIndex &parent)
{
beginRemoveRows(QModelIndex(),row, count + row);
for (int n = 0; n < count; ++n)
{
stringList.removeAt(row);
}
endRemoveRows();
return true;
}
main.cpp
#include "MyStrListModel.h"
#include <QApplication>
#include <QListView>
#include <QTableView>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QStringList list;
list << "a" << "b" << "c";
MyStrListModel model(list);
model.insertRows(3, 2);
model.removeRows(0, 1);
QListView listView;
listView.setModel(&model);
listView.show();
QTableView tableView;
tableView.setModel(&model);
tableView.show();
return a.exec();
}
(三)Views:
1.所有视图的基类:
QAbstractItemView
2.可子类化以下视图创建自定义视图:
QAbstractItemView
QTableView: 将数据项显示为一个表格
QListView: 将数据项显示为一个列表
QTreeView: 将数据项显示在具有层次的列表中
3.视图中被选择的部分
视图中被选择的项目信息存储在一个QItemSelectionModel实例中,这样被选择的项目模型索引便保持在一个独立的模型中,与所有的视图都是独立的。当在一个模型上设置多个视图时,就可以实现在多个视图之间共享选择。
使用选择模式示例代码:
代码包含:
- 选择模型的使用
- 获得选择项目、对选择项目进行反选
- 选择项目改变时动态设置
- 当多个视图显示同一个模型的数据时,共享选择
MainWindow.h
#include <QMainWindow>
class QTableView;
class QItemSelection;
class QModelIndex;
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void getCurrentItemData();
void toggleSelection();
void updateSelection(const QItemSelection &selected, const QItemSelection &deselected);
void changeCurrent(const QModelIndex ¤t, const QModelIndex &previous);
private:
Ui::MainWindow *ui;
QTableView *tableView;
QTableView *tableView2;
};
MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QStandardItemModel>
#include <QTableView>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//******* 1. 选择模型的使用 ********//
//设置基础数据
QStandardItemModel *model = new QStandardItemModel(7, 4, this);
for (int row = 0; row < 7; ++row)
{
for (int column = 0; column < 4; ++column)
{
QStandardItem *item = new QStandardItem(QString("%1").arg(row * 4 + column));
model->setItem(row, column, item);
}
}
//设置视图并显示
tableView = new QTableView;
tableView->setModel(model);
setCentralWidget(tableView);
//获取视图的项目选择模型
QItemSelectionModel *selectionModel = tableView->selectionModel();
//使用索引创建选择
QModelIndex topLeft;
QModelIndex bottomRight;
topLeft = model->index(1, 1, QModelIndex());
bottomRight = model->index(5, 2, QModelIndex());
QItemSelection selection(topLeft, bottomRight);
//用指定的选择模式选择项目
selectionModel->select(selection, QItemSelectionModel::Select);
//******* 2. 获得选择项目、对选择项目进行反选 ********//
//向主窗口工具栏中添加两个动作图标
ui->mainToolBar->addAction(tr("当前项目"), this, SLOT(getCurrentItemData()));
ui->mainToolBar->addAction(tr("切换选择"), this, SLOT(toggleSelection()));
//******* 3. 选择项目改变时动态设置 ********//
//关联选择模型改变信号
connect(selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection))
,this, SLOT(updateSelection(QItemSelection,QItemSelection)));
//关联选择项改变信号
connect(selectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex))
,this, SLOT(changeCurrent(QModelIndex,QModelIndex)));
//***** 4. 当多个视图显示同一个模型的数据时,共享选择***//
tableView2 = new QTableView;
tableView2->setWindowTitle("tableView2");
tableView2->resize(400, 300);
tableView2->setModel(model);
tableView2->setSelectionModel(selectionModel);
tableView2->show();
}
MainWindow::~MainWindow()
{
delete ui;
delete tableView;
delete tableView2;
}
//输出当前项目的内容
void MainWindow::getCurrentItemData()
{
qDebug() << tr("当前项目的内容:")
<< tableView->selectionModel()->currentIndex().data().toString();
}
void MainWindow::toggleSelection()
{
QModelIndex topLeft = tableView->model()->index(0, 0, QModelIndex());
QModelIndex bottomRight = tableView->model()->index(tableView->model()->rowCount(QModelIndex()) - 1
,tableView->model()->columnCount(QModelIndex())-1
,QModelIndex());
QItemSelection curSelection(topLeft, bottomRight);
tableView->selectionModel()->select(curSelection, QItemSelectionModel::Toggle);
}
void MainWindow::updateSelection(const QItemSelection &selected, const QItemSelection &deselected)
{
QModelIndex index;
QModelIndexList list = selected.indexes();
//为现在选择的项目填充值
foreach (index, list)
{
QString text = QString("(%1, %2)").arg(index.row()).arg(index.column());
tableView->model()->setData(index, text);
}
//清空上一次选择的项目
list = deselected.indexes();
foreach(index, list)
{
tableView->model()->setData(index, "");
}
}
void MainWindow::changeCurrent(const QModelIndex ¤t, const QModelIndex &previous)
{
qDebug() << tr("move(%1, %2) to (%3, %4)")
.arg(previous.row()).arg(previous.column())
.arg(current.row()).arg(current.column());
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
(四)Delegates:
在模型/视图结构中没有包含一个完全分离的组件来处理与用户的交互,一般视图用来将模型中的数据展示给用户,也用来处理用户的输入,为了获得更高的灵活性,交互可以交由委托来进行,于是引入了delegate
1.委托的基类:
QAbstractItemDelegate
2.两种委托:
①QStyledItemDelegate:
Qt4.4后,此为默认的委托实现,也被用作Qt标准视图的默认委托,其使用当前的样式绘制它的项目
②QItemDelegate
<1>简单的基于部件的委托可以通过子类化QItemDelegate进行实现,可以使用已定义好的一些函数的默认实现
<2>Qt中的标准视图都使用QItemDelegate的实例来提供编辑功能,这种委托接口的默认实现为QListView、QTableView、QTreeView等标准视图的每一个项目提供了普通风格的渲染
③委托的编辑器:可以通过两种方式实现:
<1>使用部件来管理编辑过程
<2>直接处理事件
注:以上代码实现的编译环境:
编译器:Qt 5.9 MinGW 32bit
编辑器:Qt Cteator 4.3
支持参考书: 《Qt Creator 快速入门》第2版——北京航空航天大学出版社