一、概述

Qt 包含一系列的View类以实现Model/View架构,Model/View架构可以实现数据和展示层的分离。使用VIew能够带来非常大的灵活性,具有十分显著的优点。举例来说,如QTableView和QTableWidget都可以实现表格显示,QTableView使用Model/View架构,而QTableWidget通过内置的QTableWidgetItem管理数据,没有实现模型视图分离,因此两者有如下明显的区别:

1)QTableView性能更好,无论是内存占用率还是界面刷新率都要优于QTableWidget

2)QTableView可以胜任大数据量的显示需求,而QTableWidget不能

3)QTableView支持动态复杂数据加载

4)QTableView支持各表格栏目自定义绘制,支持一份数据驱动多表格同步展示

采用Model/View架构的代价是复杂性,QTableView就比QTableWidget更复杂,因此需要根据实际使用需求,一般简单静态表格使用QTableWidget就可以。

总体而言,MVC架构通过数据、视图分离能够带来性能的显著提升以及高度的灵活性,Qt提供了一种略微不同于MVC(Model-View-Controller)的架构,称之为MVD(Model-View-Delgate)架构。即Controller实际上应由用户自己组合实现,Qt未提供专门的类来处理,Qt提供了一系列的Delegate协调Model和View,实现渲染、数据编辑和转换,不直接参与界面布局,而是与View配合进行细节控制以及将Model数据转换为View可用的格式。

下面对Model、Delagete的使用结合一些简单的例子进行说明。

二、自定义Model

将数据用专门的Model类进行表示,可以带来明显的好处。比如可以很容易实现数据的多角度(View)视图,摆脱代码之间的深度耦合,实现代码的复用等等。

Qt 已经提供了一些现成Model类:

1) QStringListModel

2) QStandardItemModel

3) QFileSystemModel

4) QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel

如果这些标准的Model不能满足需要,可以通过子类化QAbstractItemModel、QAbstractListModel或QAbstractTableModel来创建自己的数据模型。

本人把自定义的Model分成三类:

一是通用的Model,就是一般的能清晰地用数据结构表示的Model;二是TreeModel,因为Tree涉及到父节点、子节点等层次结构,与一般的Model有所不同;三是其它一些特殊的Model,可能平时想不到可以用Model来表示,但实际上采用Model方式后能够极大简化代码。

下面是一个构建自定义通用Model的例子。

看上去是一个平平无奇的表格,只是这个表格是由Model驱动的。假设你的数据结构如下定义: 

//接收机数据
typedef struct _s_satdataraw
{
    int prn;         //	卫星号
    float cno;     	//	载噪比
    float azimuth; 	//	方位角
    float elev;    	//	仰角
    double x;       	//	位置X
    double y;       	//	位置Y
    double z;       	//	位置Z
    double vx;       	//	X方向速度
    double vy;       	//	Y方向速度
    double vz;       	//	Z方向速度
    char mark[32];    	//	数据说明
} SatRawData;

则定义数据模型如下:

class Model : public QAbstractTableModel
{
    Q_OBJECT
    Q_DISABLE_COPY(Model)
public:
    //为了可读性定义列名
    enum Column {
        ColumnPrn = 0,
        ColumnCno,
        ColumnAzimuth,
        ColumnElev,
        ColumnX,
        ColumnY,
        ColumnZ,
        ColumnVx,
        ColumnVy,
        ColumnVz,
        ColumnMark,
        ColumnCount
    };

    enum ValueType {
        TitleText = 0xAA,  //标题栏
        ColumnValue,       //值
        ColumnTips         //提示信息
    };

    explicit Model(QObject *parent = 0);

    int columnCount(const QModelIndex &parent) const;
    int rowCount(const QModelIndex &parent) const;
    QVariant data(const QModelIndex &index, int role) const;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const;

    void setValue(SatRawData& value) { m_columes.append(value);};
private:
    static QVariant getValue(ValueType type,int column,const SatRawData *value=nullptr);
private:
    QList<SatRawData> m_columes;
};

作为自定义的Model,需要重写QAbstractTableModel中的四个函数:

int columnCount(const QModelIndex &parent) const;

int rowCount(const QModelIndex &parent) const;

QVariant data(const QModelIndex &index, int role) const;

QVariant headerData(int section, Qt::Orientation orientation, int role) const;

int Model::columnCount(const QModelIndex &/*parent*/) const
{
    return ColumnCount;
}

int Model::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid()) return 0;
    return m_columes.count();
}

QVariant Model::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    const SatRawData &value = m_columes.at(index.row());
    if (role == Qt::DisplayRole) {
        return getValue(ColumnValue,index.column(), &value);
    }
    else if (role == Qt::ToolTipRole) {
        return getValue(ColumnTips,index.column(), &value);
    }
    return QVariant();
}

//标题栏显示
QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation != Qt::Horizontal)
        return QVariant();

    if (role != Qt::DisplayRole)
        return QVariant();

    return getValue(TitleText,section);
}

//返回标题栏、提示信息或栏目单元格所对应的具体值
QVariant Model::getValue(ValueType type, int column, const SatRawData *value)
{
    const char *title[] = { //标题栏
        "卫星号",
        "载噪比",
        "方位角",
        "仰角",
        "X",
        "Y",
        "Z",
        "Vx",
        "Vy",
        "Vz",
        "说明"
    };
    const char *tipes[] = { //提示信息
        "卫星号",
        "接收机实时测量值",
        "方位角,单位度",
        "仰角,单位度",
        "ECEF坐标系位置X",
        "ECEF坐标系位置Y",
        "ECEF坐标系位置Z",
        "ECEF坐标系X方向速度",
        "ECEF坐标系Y方向速度",
        "ECEF坐标系Z方向速度",
        "数据说明"
    };

    QVariant vt = QVariant();
    if(column > ColumnCount) return vt;

    if(type == TitleText) {
        return title[column];
    }
    if((type == ColumnValue)&&(value == nullptr)) {
        return vt;
    }
    if((type == ColumnTips)&&(column != ColumnPrn))  {
        return tipes[column];
    }

    //Value
    switch (column) {
        case ColumnPrn:
            if(type == ColumnValue) {
                vt = QString("BDS %1").arg(value->prn);
            }
            else { // ColumnTips
                vt =  QString("卫星[%1]:\n"
                  "仰角、方位角: (%2,%3)\n"
                  "ECEF坐标: (%4,%5,%6)\n"
                  "卫星速度: (Vx = %7,Vy = %8,Vz = %9)\n").
                arg(value->prn).
                arg(value->elev).
                arg(value->azimuth).
                arg(value->x).
                arg(value->y).
                arg(value->z).
                arg(value->vx).
                arg(value->vy).
                arg(value->vz);
            }
            break;
        case ColumnCno:
            vt = value->cno + 30;
            break;
        case ColumnAzimuth:
            vt = (value->azimuth<0)?value->azimuth+360:value->azimuth;
            break;
        case ColumnElev:
            vt = (value->elev<0)?0:value->elev;
            break;
        case ColumnX:
            vt = value->x;
            break;
        case ColumnY:
            vt = QString("%1").arg(value->y,12,'f');
            break;
        case ColumnZ:
            vt = value->z;
            break;
        case ColumnVx:
            vt = value->vx;
            break;
        case ColumnVy:
            vt = value->vy;
            break;
        case ColumnVz:
            vt = value->vz;
            break;
        case ColumnMark:
            vt = QString("%1").arg(value->mark);
            break;
        default:
            break;
    }
    return vt;
}

至此一个Model已经定义完成。

如果仅仅需要显示一个表格,上述代码稍显复杂,但是可以看出Model方式在处理上有极大的灵活性,同时实现了模型与展示的分离。

下面显示Model数据则十分简单:

    QTableView view;
    view.setModel(new Model(&view));
    view.show();

其中view也可以更改为QListView view或QTreeView view都不会影响程序正常运行。本例的代码见:

【免费】相关文章见QTMVC编程MODEL/DELEGATE/VIEW,包含一些QTModel、Delegate开发的例子资源-CSDN文库

下一节介绍更复杂一点的TreeModel,即含有子项的Model。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐