0\概念
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 来创建自定义的模型。
为确保数据的表示与数据的获取相分离,Qt 引入了模型索引的概念,输入和委托均可通过模型索引来请求数据并显示。只有模型需要知道怎样获取数据,被模型管理的数据类型可以被广泛的定义。模型索引包含一个指针,指向创建他们的模型,使用多个模型时可避免混淆。模型索引 QModelIndex 类提供对一块数据的临时引用,用来修改或检索模型中的数据,获取一个数据项的模型索引必须指定模型的 3 个属性:行号、列号和父项的模型索引。
2. 视图
Qt 提供了 QListView、QTableView 视图、QTreeView 视图分别实现列表、表格与树视图效果。QListView 将数据项显示为一个列表;QTableView 将模型中的数据显示在一个表格中;QTreeView 将模型中的数据项显示在具有层次的列表中。QTableView 和 QTreeView 在显示项目的时候同时还可以显示标头,通过 QHeaderView 类实现。自定义视图类是基于
QAbstractItemView 抽象基类,如实现条形图,饼状图等特殊显示方式。
3. 委托
在模型/视图框架中,QAbstractItemDelegate 是委托类的抽象基类,Qt 默认的委托实现由 QStyledItemDelegate 类 提 供 , 这 也 被 用 作 Qt 标 准 视 图 的 默 认 委 托 , 选 择
QStyledItemDelegate 或 QItemDelegate 中其一来为视图中的项目绘制和提供编辑器。不同的是 QStyledItemDelegate 使用当前的样式来绘制项目,实现自定义委托建议使用
QStyledItemDelegate 作为基类。
Qt 提供了项目试图的便捷类,这些类底层通过模型/视图框架实现。这些部件分别是
QListWidget 提供一个项目列表,QTreeWidget 显示一个多层次的树结构,QTableWidget
提供了一个以项目作为单元的表格。它们每一个类都继承了 QAbstractItemView 类的行为。
之所以成为便捷因其用起来比较简单,使用于少量的数据的存储和显示。因没有将视图与模型分离,所以没有视图类灵活,不能和任意的模型一起使用。
1\基于系统文件
#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(QString("视图/模型"));
splitter->show();
return app.exec();
}
2\标准模型项
#include<QApplication>
#include<QTreeView>
#include<QStandardItemModel>
#include<QDebug>
int main(int argc,char* argv[]){
QApplication app(argc,argv);
//创建标准项模型
QStandardItemModel model;
//获取标准项模型的根项,根项不可见
QStandardItem *parentItem = model.invisibleRootItem();
//创建标准型item0,设置文本,设置图标,工具提示
QStandardItem *item0 = new QStandardItem;
item0->setText("A");
QPixmap pixmap0(50,50);
pixmap0.fill(Qt::red);
item0->setIcon(QIcon(pixmap0));
item0->setToolTip(QString("A项提示"));
//将item0作为父项的子项
parentItem->appendRow(item0);
parentItem = item0;
//创建item0的子项
QStandardItem *item1 = new QStandardItem;
item1->setText("B");
QPixmap pixmap1(50,50);
pixmap1.fill(Qt::yellow);
item1->setIcon(QIcon(pixmap1));
item1->setToolTip(QString("B的提示"));
parentItem->appendRow(item1);
QStandardItem *item2 = new QStandardItem;
QPixmap pixmap2(50,50);
pixmap2.fill(Qt::green);
item2->setData("C",Qt::EditRole);
item2->setData("index C",Qt::ToolTipRole);
item2->setData(QIcon(pixmap2),Qt::DecorationRole);
parentItem->appendRow(item2);
//树视图中显示数据
QTreeView view;
view.setModel(&model);
view.show();
QModelIndex indexA = model.index(0,0,QModelIndex());
qDebug() << "indexA row coutn"
<<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();
}
3\自定义模型
1\WeaponMoedl.h
#ifndef WEAPONMOEDL_H
#define WEAPONMOEDL_H
#include<QAbstractTableModel>
class WeaponMoedl : public QAbstractTableModel
{
public:
WeaponMoedl(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> army; //军队
QVector<short> weaponType; //武器类型
QMap<short,QString> armyMap; //军队映射
QMap<short,QString> weaponMap;//武器映射
QStringList weapon;//武器
QStringList header;//表头
void populateModel();//表格数据的初始化
};
#endif // WEAPONMOEDL_H
2/WeaponMoedl.cpp
#include "weaponmoedl.h"
WeaponMoedl::WeaponMoedl(QObject *parent):QAbstractTableModel(parent)
{
armyMap[1] = QString("陆军");
armyMap[2] = QString("空军");
armyMap[3] = QString("海军");
armyMap[4] = QString("海军陆战队");
weaponMap[1] = QString("轰炸机");
weaponMap[2] = QString("战斗机");
weaponMap[3] = QString("坦克");
weaponMap[4] = QString("直升机");
weaponMap[5] = QString("航空母舰");
weaponMap[6] = QString("潜水战车");
weaponMap[7] = QString("核潜艇");
weaponMap[8] = QString("超音速");
populateModel();
}
int WeaponMoedl::rowCount(const QModelIndex &parent) const
{
return army.size();
}
int WeaponMoedl::columnCount(const QModelIndex &parent) const
{
return 3;
}
//放回指定索引的数据,将数值映射成文字
QVariant WeaponMoedl::data(const QModelIndex &index, int role) const
{
if(!index.isValid()) return QVariant();
if(role == Qt::DisplayRole){
switch (index.column()) {
case 0:
return armyMap[army[index.row()]];
break;
case 1:
return weaponMap[weaponType[index.row()]];
break;
case 2:
return weapon[index.row()];
break;
default:
return QVariant();
break;
}
}
return QVariant();
}
QVariant WeaponMoedl::headerData(int section, Qt::Orientation orientation, int role) const
{
if(role == Qt::DisplayRole && orientation == Qt::Horizontal)
return header[section];
return QAbstractTableModel::headerData(section,orientation,role);
}
void WeaponMoedl::populateModel()
{
header << QString("军种")
<< QString("种类")
<< QString("武器");
army<< 1 << 2 << 3 << 4 << 3 << 2 << 1;
weaponType << 1 << 3 << 5 << 7 << 8 << 6 << 4 << 2;
weapon << QString("大黄蜂") << QString("华尔兹")
<< QString("扒鸡跑") << QString("尼泊尔")
<< QString("M90") << QString("华飞思")
<< QString("朴良国") << QString("双飞片");
}
3/main
#include<QApplication>
#include<QTableView>
#include"weaponmoedl.h"
int main(int argc, char *argv[])
{
QApplication app(argc,argv);
WeaponMoedl model;
QTableView view;
view.setModel(&model);
view.setWindowTitle(QString("表格/视图"));
view.resize(600,400);
view.show();
return app.exec();
}
4\只读模型
stringListModel.h
#ifndef STRINGLISTMODEL_H
#define STRINGLISTMODEL_H
#include<QAbstractListModel>
class stringListModel : public QAbstractListModel
{
Q_OBJECT
public:
stringListModel(const QStringList &strList,QObject *parent = 0)
:QAbstractListModel(parent),m_stringList(strList){}
//模型行数
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 m_stringList;
};
#endif // STRINGLISTMODEL_H
stringListModel.cpp
#include "stringlistmodel.h"
int stringListModel::rowCount(const QModelIndex &parent) const
{
return m_stringList.count();
}
QVariant stringListModel::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
return QVariant();
if(index.row() == m_stringList.size())
return QVariant();
if(role == Qt::DisplayRole || role == Qt::EditRole)
return m_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("名字%1").arg(section);
else
return QString("%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){
m_stringList.replace(index.row(),value.toString());
emit dataChanged(index,index);
return true;
}
return false;
}
bool stringListModel::insertRows(int row, int count, const QModelIndex &parent)
{
//告知其他组件指定的行开始插入操作
beginInsertRows(parent,row,count + row - 1);
for(int i=0; i<count; ++i)
m_stringList.insert(row,QString("地球"));
//告知其他组件完成操作
endInsertRows();
return true;
}
bool stringListModel::removeRows(int row, int count, const QModelIndex &parent)
{
//告知其他组件指定的行进行删除操作
beginRemoveRows(parent,row,count + row - 1);
for(int i=0; i<count; ++i)
m_stringList.removeAt(row);
//告知其他组件完成操作
endRemoveRows();
return true;
}
main
#include<QApplication>
#include<QListView>
#include<QTableView>
#include"stringlistmodel.h"
int main(int argc, char *argv[])
{
QApplication app(argc,argv);
QStringList list;
list << QString("太阳") << QString("月亮")
<< QString("火星") << QString("木星");
stringListModel model(list);//创建模型
model.insertRows(0,2);
model.removeRows(2,1);
QListView listView;//创建列表视图
listView.setModel(&model);//视图设置模型
listView.show();//视图显示
QTableView tableView;//创建表格视图
tableView.setModel(&model);//视图设置模型
tableView.show();//视图显示
return app.exec();
}
5\选择模型与自定义委托
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include<QItemSelection>
#include <QMainWindow>
#include<QTableView>
#include<QModelIndex>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
public slots:
void getCurrentItemData();//当前选择
void toggleSection();//切换选择
//更新选择,selected表新的选择,deselected表以前的选择
void updateSelection(const QItemSelection &selected,
const QItemSelection &deselected);
//改变当前模型索引
void changeCurrent(const QModelIndex ¤t,
const QModelIndex &previous);
private:
Ui::MainWindow *ui;
QTableView *m_tableView;
QTableView *m_tableView2;
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QStandardItemModel>
#include<QDebug>
#include<QTableView>
#include <spinboxdelegate.h>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//创建标准模型项,7行4列
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);
}
m_tableView = new QTableView;
m_tableView->setModel(model);
setCentralWidget(m_tableView);//设置主窗口中心部件为表格视图
//获取视图的选择模式
QItemSelectionModel *selectionModel = m_tableView->selectionModel();
QModelIndex topLeft;//左上角模型索引
QModelIndex bottomRight;//右下角模型索引
topLeft = model->index(1,1);
bottomRight = model->index(5,2);
//创建模型选择
QItemSelection selection(topLeft,bottomRight);
//以选择的方式来选择项目
selectionModel->select(selection,QItemSelectionModel::Select);
//添加动作Action(动作文本,响应者,槽方法)
// ui->menuBar->addAction(QString("当前项目"),this,&MainWindow::getCurrentItemData);
// ui->menuBar->addAction(QString("切换选择"),this,&MainWindow::toggleSection);
//6.9.6版本,
QAction *action1 = ui->menuBar->addAction(QString("当前项目"));
QAction *action2 = ui->menuBar->addAction(QString("切换选择"));
connect(action1,&QAction::triggered,this,&MainWindow::getCurrentItemData);
connect(action2,&QAction::triggered,this,&MainWindow::toggleSection);
//关联选择模型的选择改变,当前项改变信号
connect(selectionModel,&QItemSelectionModel::selectionChanged,
this,&MainWindow::updateSelection);
connect(selectionModel,&QItemSelectionModel::currentChanged,
this,&MainWindow::changeCurrent);
m_tableView2 = new QTableView;
m_tableView2->setWindowTitle("tableView2");
m_tableView2->resize(600,400);
m_tableView2->setModel(model);
m_tableView2->setSelectionModel(selectionModel);
m_tableView2->show();
//自定义委托
spinboxDelegate *dategate = new spinboxDelegate(this);
m_tableView->setItemDelegate(dategate );
}
MainWindow::~MainWindow()
{
delete ui;
delete m_tableView2;
}
void MainWindow::getCurrentItemData()
{
qDebug() << QString("当前数据: ")
<<m_tableView->selectionModel()->currentIndex().data().toString();
}
void MainWindow::toggleSection()
{
//左上角模型索引
QModelIndex topLeft = m_tableView->model()->index(0,0,QModelIndex());
//右下角模型索引
QModelIndex bottomRight = m_tableView->model()->index(
m_tableView->model()->rowCount(QModelIndex())-1,
m_tableView->model()->columnCount(QModelIndex())-1,
QModelIndex());
//项选择
QItemSelection curSelection(topLeft,bottomRight);
m_tableView->selectionModel()->select(curSelection,
QItemSelectionModel::Toggle);
}
void MainWindow::updateSelection(const QItemSelection &selected, const QItemSelection &deselected)
{
QModelIndex index;
//indexes()返回所有选择项的模型
QModelIndexList list = selected.indexes();
//给选择项填充数据
foreach (index, list) {
QString text = QString("%1,%2").arg(index.row()).arg(index.column());
m_tableView->model()->setData(index,text);
}
//清空上一次的内容
list = deselected.indexes();
foreach (index, list) {
m_tableView->model()->setData(index,"");
}
}
void MainWindow::changeCurrent(const QModelIndex ¤t, const QModelIndex &previous)
{
qDebug() << QString("从(%1,%2)到(%3,%4)")
.arg(previous.row()).arg(previous.column())
.arg(current.row()).arg(current.column());
}
spinboxDelegate.h
#ifndef SPINBOXDELEGATE_H
#define SPINBOXDELEGATE_H
#include<QItemDelegate>
class spinboxDelegate : public QItemDelegate
{
Q_OBJECT
public:
spinboxDelegate(QObject *parent = 0);
//创建编辑器
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
//设置编辑器数据
void setEditorData(QWidget *editor,const QModelIndex &index) const override;
//更新编辑器几何属性
void updateEditorGeometry(QWidget *editor,const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
};
#endif // SPINBOXDELEGATE_H
spinboxDelegate.cpp
#include "spinboxdelegate.h"
#include<QSpinBox>
spinboxDelegate::spinboxDelegate(QObject *parent):QItemDelegate(parent)
{
}
QWidget *spinboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSpinBox *editor = new QSpinBox(parent);
editor->setMinimum(0);
editor->setMaximum(100);
return editor;
}
void spinboxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
int value = index.model()->data(index,Qt::EditRole).toInt();
//类型转化QWidget转QSpinBox
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
//编辑器设置数据
spinBox->setValue(value);
}
void spinboxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry(option.rect);
}