前言
QT中的Model/View框架实现了标准MVC架构
的功能,但是他的体系架构和标准MVC有些
差别。
一.需要理解的概念
数据项:界面上所要显示的数据;
数据子项:显示数据项时所用的字体,颜色,背景颜色等数据;
角色:每一个数据子项所起的作用;
索引:指定将要访问哪个数据项(QModelIndex
)
无效索引:指QModelIndex
的一个特殊对象(没有指向任何一个数据项)
二.使用Model/View框架几种方式
QAbstractItemModel:深入理解一个模型类应该如何
与Model/View框架中的其他类协同工作。
需求:用TreeModel显示数据集种的数据。
i
:当前节点对应
的数组下标(i-1)/2
:父节点的下标2*i+1
:左子节点的下标2*i+2
:右子节点的下标(i+1)%2
:该节点对应的行号
- 接口1(index):
视图类
→ 某个数据项
QModelIndex TreeModel::index (
int row/*childNode's row*/,
int column/*childNode's column*/,
const QModelIndex& parent/*parentNode's index*/
) const
{
if (!parent.isValid()){
quintptr id = 0;
return createIndex(row, column, id);
}
int parent_idx = parent.internalId();
int idx = parent_idx * 2 + ( row + 1 );
return createIndex(row, column, idx);
}
- 接口2(parent):
视图类
→ 某个数据项的父
节点
QModelIndex TreeModel::parent ( const QModelIndex & index ) const
{
qDebug()<<u8"M parent()";
if (index.internalId() == 0) return QModelIndex();
int parent_idx = (index.internalId() - 1 )/2;
return createIndex( (parent_idx+1) % 2, 0, parent_idx );
}
- 接口3(rowCount):
视图类
→ 数据项的子
节点
int TreeModel::rowCount ( const QModelIndex & parent ) const
{
if (!parent.isValid()) return 1;
if (parent.internalId() < N/2 ) return 2;
return 0;
}
- 接口4(columnCount()):
视图类
→ 某个角色对应的数据子项
int TreeModel::columnCount ( const QModelIndex & parent ) const{ return 1; }
- 接口5(data):
视图类
→某个角色对应的数据子项
QVariant TreeModel::data ( const QModelIndex & index, int role ) const
{
switch (role) {
case Qt::DisplayRole:
int value = m_numbers[ index.internalId() ];
return QVariant( value );
}
return QVariant();
}
样式
需求:叶节点(红色,字体大小26),非叶节点(黑色,字体大小20)
QVariant TreeModel::data ( const QModelIndex & index, int role ) const
{
switch (role) {
case Qt::DisplayRole:{
int value = m_numbers[ index.internalId() ];
return QVariant( value );
};
case Qt::ForegroundRole:
if(index.internalId() >= N/2)return QBrush(Qt::red);
case Qt::FontRole:
QFont font;
if(index.internalId() >= N/2) font.setPointSize(26);
return font;
};
return QVariant();
}
子数据项
当视图类使用
setModel()
与模型类建立联系后,其内部已经把dataChanged
信号和槽函数绑定了,所以我们只需要在数据发生变化的时候去触发信号的发送
即可
TreeModel::TreeModel()
{
...
timer = new QTimer(this);
timer->setInterval(1000);
connect(timer, SIGNAL(timeout()) , this, SLOT(timerHit()));
timer->start();
}
void TreeModel::timerHit()
{
m_numbers[14] = ( m_numbers[14] + 1 ) % 60;
m_numbers[6] = m_numbers[14]+ m_numbers[13];
m_numbers[2] = m_numbers[6] + m_numbers[5];
m_numbers[0] = m_numbers[2] + m_numbers[1];
QModelIndex idx_14 = createIndex(1/*View*/,0,14);
QModelIndex idx_6 = createIndex(1,0,6);
QModelIndex idx_2 = createIndex(1,0,2);
quintptr id = 0;
QModelIndex idx_0 = createIndex(0,0,id);
emit dataChanged(idx_14, idx_14);
emit dataChanged(idx_6, idx_6 );
emit dataChanged(idx_2, idx_2 );
emit dataChanged(idx_0, idx_0 );
}
标头
QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if((role == Qt::DisplayRole) && (orientation == Qt::Orientation::Horizontal) && (section == 0))
return QString(u8"满二叉树");
return QVariant();
}
编辑数据项
需求:双击叶节点
,可以输入其他值。双击其他节点,没有任何反应
控件行为:双击某个数据项后,视图类会调用
flags()
虚函数(查询目标数据项能否编辑);
Qt::ItemFlags TreeModel::flags(const QModelIndex & index) const
{
if ( index.internalId() < N/2 )
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
else
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
}
可以编辑的话,会去调用
data()
纯虚函数(获取编辑角色
对应的数据子项
数据);
QVariant TreeModel::data ( const QModelIndex & index, int role ) const
{
...
case Qt::EditRole:
return QVariant(m_numbers[index.internalId()]);
...
}
用户编辑后回车,会把值传递给
setData()
,我们只需要去做对应的处理即可。
bool TreeModel::setData(const QModelIndex & index, const QVariant & value, int role)
{
if (role != Qt::EditRole) return true;
int id = index.internalId();
while (1) {
if ( id >= N/2 )
m_numbers[id] = value.toInt();
else
m_numbers[id] = m_numbers[2 * id + 1] + m_numbers[ 2 * id + 2];
QModelIndex idx = createIndex(1,0,id);
emit dataChanged(idx,idx);
if ( id == 0 ) break;
id = ( id - 1 ) / 2;
};
return true;
}
选择与数据集
相对应的模型类
特点:这些类帮我们实现了部分功能,可以使我们工作量减少!
QAbstractListModel
:列表数据集,行号可以唯一确定一个数据项在列表中的位置,因此只需要重载rowCount()
和data()
。
QAbstractTableModel
:表格数据集,一个行号和一个列号可以唯一确定一个数据项的位置。
QStandardItemModel
类:数据量不大,性能要求不高,可以使用这个类来表示一个数据集。(会用就行了,它的机制以后再去理解)。
#include <QApplication>
#include <QTreeView>
#include <QListView>
#include <QTableView>
#include <QStandardItemModel>
#include <QSplitter>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QSplitter splitter;
QStandardItemModel listModel;
QStandardItem *rootItem = listModel.invisibleRootItem();
for (int row = 0; row < 4; ++row) {
QStandardItem *item = new QStandardItem(QString("%0").arg(row) );
rootItem->appendRow( item );
}
QListView listView;
listView.setModel ( & listModel );
splitter.addWidget( & listView );
QStandardItemModel tableModel(4, 4);
for (int row = 0; row < 4; ++row) {
for (int column = 0; column < 4; ++column) {
QStandardItem *item = new QStandardItem(QString("%0,%1").arg(row).arg(column));
tableModel.setItem(row, column, item);
}
}
QTableView tableView;
tableView.setModel( & tableModel );
splitter.addWidget( & tableView );
QStandardItemModel treeModel;
QStandardItem *parentItem = treeModel.invisibleRootItem();
for (int i = 0; i < 4; ++i) {
QStandardItem *item = new QStandardItem(QString("%0").arg(i));
parentItem->appendRow(item);
parentItem = item;
}
QTreeView treeView;
treeView.setModel( & treeModel );
splitter.addWidget(& treeView );
splitter.show();
return app.exec();
}
便利模型类
特点:不需要程序员
派生
任何新的模型类。
QStringList
:字符串列表
#include <QApplication>
#include <QListView>
#include <QStringListModel>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QStringList list;
list << "One" << "Two" << "Three" << "Four" << "Five";
QStringListModel listModel(list);
QListView listView;
listView.setModel( &listModel );
listView.show();
return app.exec();
}
QFileSystemModel
:树结构(磁盘信息)
#include <QApplication>
#include <QTreeView>
#include <QFileSystemModel>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QFileSystemModel model;
model.setRootPath("C:/Documents and Settings");
QTreeView treeView;
treeView.setModel(&model);
treeView.show();
return app.exec();
}
一.两者的对比
每个数据项的绘制(
委托
),其他的绘制工作(视图
)
每个数据项的编辑命令(委托
),其他的交互命令(视图
)
从上面委托类和视图类所负责的内容来看,委托类和视图类结合一起负责程序的V层和C层,视图层和控制层结合了在一起,谁也离不开谁。