前言

QT中的Model/View框架实现了标准MVC架构的功能,但是他的体系架构和标准MVC有些差别。


QT demo_数据项

一.需要理解的概念

数据项:界面上所要显示的数据;
数据子项:显示数据项时所用的字体,颜色,背景颜色等数据;
角色:每一个数据子项所起的作用;
索引:指定将要访问哪个数据项(QModelIndex
无效索引:指QModelIndex的一个特殊对象(没有指向任何一个数据项)

二.使用Model/View框架几种方式

QAbstractItemModel:深入理解一个模型类应该如何与Model/View框架中的其他类协同工作。

需求:用TreeModel显示数据集种的数据。
i:当前节点对应的数组下标
(i-1)/2:父节点的下标
2*i+1:左子节点的下标
2*i+2:右子节点的下标
(i+1)%2:该节点对应的行号

  1. 接口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);
}
  1. 接口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 );
}
  1. 接口3(rowCount):视图类 → 数据项的节点
int TreeModel::rowCount ( const QModelIndex & parent ) const
{
    if (!parent.isValid()) return 1;
    if (parent.internalId() < N/2 ) return 2;
    return 0;
}
  1. 接口4(columnCount()):视图类→ 某个角色对应的数据子项
int TreeModel::columnCount ( const QModelIndex & parent ) const{ return 1; }
  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层,视图层和控制层结合了在一起,谁也离不开谁。