模型/视图编程
Qt中的模型/视图架构用来实现大量的数据存储、处理及显示。
MVC(Model-View-Controller)包括了3个组件:模型(model)是应用对象,用来表示数据;视图(View)是模型的用户界面,用来显示数据;控制(Controller)定义了用户界面对用户输入的反应方式。
委托(Delegate)用于定制数据的渲染和编辑方式。
模型
所有的模型都基于QAbstractItemModel类,该类提供了十分灵活的接口来处理各种视图,这些视图的数据表现形式为表格(table)、列表(list)、树(tree)。
Qt提供了一些现成的模型来处理数据项:
QStringListModel存储简单的QString项目列表;
QStandardItemModel管理复杂的属性结构数据项,每一个数据项可以包含任意的数据;
QFileSystemModel、QSqlTableModel和QSqlRelationTableModel用来访问数据库。
当标准模型还无法满足需要时,可子类化QAbstractItemModel、QAbstractListModel或QAbstractTableModel来创建自定义的模型。
常见的3种模型为列表模型、表格模型、树模型,如下图所示:
为确保数据的表示与数据获取相分离,Qt引入了模型索引的概念,输入和委托均可通过模型索引来请求数据并显示。只有模型需要知道怎样获取数据,被模型管理的数据类型可以被广泛的定义。模型索引包含一个指针,指向创建他们的模型,使用多个模型时可避免混淆。模型索引QModeIIndex类提供对一块数据的临时引用, 用来修改或检索模型中的数据,获取一个数据项的模型索引必须指定模型的3个属性:行号、列号和父项的模型索引。如:
QModelIndex index = model->index(row,column,parent);
也可以通过模型指定的相关数据项对应的模型索引以及特定的角色来获取需要的类型数据,如:
QVariant value = model->data(index,role);
常用的角色类型:
示例:
#include<QApplication>
#include<QDebug>
#include<QTreeView>
#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(QString("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(QString("B"));
QPixmap pixmap1(50,50);
pixmap1.fill(Qt::blue);
item1->setIcon(pixmap1);
item1->setToolTip(QString("B项"));
QStandardItem* item2 = new QStandardItem;
QPixmap pixmap2(50,50);
pixmap2.fill(Qt::green);
item2->setData("C",Qt::EditRole);
item2->setData("indexC",Qt::ToolTipRole);
item2->setData(QIcon(pixmap2),Qt::DecorationRole);
parentItem->appendRow(item1);
parentItem->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();
}
视图
Qt提供了QListView、QTabelView视图、QTreeView视图分别实现列表、表格与树视图效果。QListView将数据项显示为一个列表;QTableView将模型中的数据显示在一个表格中;QTreeView将模型中的数据项显示在具有层次的列表中。QTableView和QTreeView在显示项目的时候同时还可以显示标头,通过QHeaderView类实现。自定义视图类是基于QAbstractItemView抽象基类,如实现条形图,饼状图等特殊显示方式。
示例1:
#include<QApplication>
#include<QAbstractItemModel>
#include<QAbstractItemView>
#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);
//设置视图对象的选择方式为多选,list/table选择方式同tree
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 "weaponmodel.h"
Weaponmodel::Weaponmodel(QObject* parent):QAbstractTableModel(parent)
{
armyMap[1] = QString("空军");
armyMap[2] = QString("海军");
armyMap[3] = QString("陆军");
armyMap[4] = QString("海军陆战队");
weaponMap[1] = QString("战斗机1");
weaponMap[2] = QString("战斗机2");
weaponMap[3] = QString("战斗机3");
weaponMap[4] = QString("战斗机4");
weaponMap[5] = QString("战斗机5");
weaponMap[6] = QString("战斗机6");
weaponMap[7] = QString("战斗机7");
weaponMap[8] = QString("战斗机8");
populateModel();
}
int Weaponmodel::rowCount(const QModelIndex &parent)const
{
return army.size();
}
int Weaponmodel::columnCount(const QModelIndex &parent)const
{
return 3;
}
//返回指定索引的数据
QVariant Weaponmodel::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
{
return QVariant();
}
if(role == Qt::DisplayRole)
{
switch (index.column()) {
case 0:
return army[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 Weaponmodel::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 Weaponmodel::populateModel()
{
header<<QString("军种")<<QString("种类")<<QString("武器");
army<<1<<2<<3<<4<<2<<4<<3<<1;
weaponType<<1<<3<<5<<7<<4<<8<<6<<2;
weapon<<QString("B-2")<<QString("尼尔兹级")<<QString("阿帕奇")<<QString("黄蜂级")<<QString("比例伯克级")<<QString("AAAV")<<QString("M1A1")<<QString("F-22");
}
示例3
#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("column %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)
{
m_stringList.replace(index.row(),value.toString());
emit dataChanged(index,index);
return true;
}
return false;
}
bool StringListModel::inseratRows(int position, int rows, const QModelIndex &index)
{
//告知其它组建指定的行开始插入操作
beginInsertRows(QModelIndex(),position,position + rows -1 );
for(int row = 0 ; row < rows; row++)
{
m_stringList.insert(position,QString("你好"));
}
//告知其它组件完成操作
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++)
{
m_stringList.removeAt(position);
}
//告知其它组件完成操作
endRemoveRows();
return true;
}
#include<QApplication>
#include"stringlistmodel.h"
#include<QListView>
#include<QTableView>
int main(int argc,char* argv[])
{
QApplication app(argc,argv);
QStringList list;
list<<QString("太阳")<<QString("地球")<<QString("月亮")<<QString("木星");
StringListModel 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 app.exec();
}
示例4:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QStandardItem>
#include<QDebug>
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 colum= 0;colum < 4;colum++)
{
QStandardItem* item = new QStandardItem(QString("%1").arg(row+4 +colum));
//标准模型设置数据项
model->setItem(row,colum,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);
//添加动作addAction(动作文本,响应者,槽方法)
ui->menuBar->addAction(QString("当前项目"),this,&MainWindow::getCurrenItemData);
ui->menuBar->addAction(QString("切换选择"),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(400,300);
m_tableView2->setModel(model);
m_tableView2->setSelectionModel(selectionModel);
m_tableView2->show();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::getCurrenItemData()
{
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());
}
委托
在模型/视图框架中,QAbstractItemDelegate是委托类的抽象基类,Qt默认的委托实现由QStyledItemDelegate类提供,这也被用作Qt标准视图的默认委托,选择 QStyledItemDelegate或QItemDelegate中其一来为视图中的项目绘制和提供编辑器。不同的是QStyledItemDelegate使用当前的样式来绘制项目,实现自定义委托建议使用QStyledItemDelegate作为基类。
Qt提供了项目试图的便捷类,这些类底层通过模型/视图框架实现。这些部件分别是QListWidget提供一个项目列表,QTreeWidget显示一个多层次的树结构,QTableWidget提供了一个以项目作为单元的表格。它们每一个类都继承了QAbstractItemView类的行为。之所以成为便捷因其用起来比较简单,使用于少量的数据的存储和显示。因没有将视图与模型分离,所以没有视图类灵活,不能和任意的模型一起使用。
通过自定义委托来实现更高级的渲染。
示例:QSpinbox
#include "spinboxdelexgate.h"
#include<QSpinBox>
SpinBoxDelexgate::SpinBoxDelexgate(QObject* parent):QItemDelegate(parent)
{
}
QWidget *SpinBoxDelexgate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSpinBox *editor = new QSpinBox(parent);
editor->setMinimum(0);//最小值
editor->setMaximum(100);//最大值
return editor;
}
void SpinBoxDelexgate::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 SpinBoxDelexgate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
//视图设置自定义委托
editor->setGeometry(option.rect);
}
便捷部件类
示例:
#include<QApplication>
#include<QDebug>
#include<QListWidget>
#include<QTreeWidget>
#include<QTableWidget>
int main(int argc,char* argv[])
{
QApplication app(argc,argv);
QListWidget listWidget;
//添加项目
new QListWidgetItem("天涯",&listWidget);
QListWidgetItem* listWidgetItem = new QListWidgetItem;
listWidgetItem->setText("海角");
QPixmap pixmap(50,50);
pixmap.fill(Qt::blue);
listWidgetItem->setIcon(QIcon(pixmap));
listWidgetItem->setToolTip("大海的角落");
//部件插入项目
listWidget.insertItem(1,listWidgetItem);
//部件设置排序(降序)
listWidget.sortItems(Qt::DescendingOrder);
listWidget.show();
//创建树部件
QTreeWidget treeWidget;
//设置列数
treeWidget.setColumnCount(2);
QStringList headers;
headers<<"名字"<<"数量";
//设置头
treeWidget.setHeaderLabels(headers);
//添加项目
QTreeWidgetItem *item1 = new QTreeWidgetItem(&treeWidget);//指定父项
item1->setText(0,"开心超人");
QTreeWidgetItem *item11 = new QTreeWidgetItem(item1);
item11->setText(0,"甜心超人");
item11->setText(1,"小心超人");
//添加项目,并指定它的前一个项目是item1
QTreeWidgetItem* item2 = new QTreeWidgetItem(&treeWidget,item1);
item2->setText(0,"小小怪");
treeWidget.show();
//创建表格部件
QTableWidget tableWidget(3,2);
QTableWidgetItem *tableWidgetem = new QTableWidgetItem("Tom");
tableWidget.setItem(1,1,tableWidgetem);
//创建表格项目,作为表头
QTableWidgetItem* headerV = new QTableWidgetItem("Cat");
tableWidget.setVerticalHeaderItem(0,headerV);
QTableWidgetItem* headerH = new QTableWidgetItem("What");
tableWidget.setHorizontalHeaderItem(0,headerH);
tableWidget.show();
return app.exec();
}