【Qt笔记】自定义只读模型

model/view 模型将数据与视图分割开来,也就是说,咱们能够为不一样的视图,QListViewQTableViewQTreeView提供一个数据模型,这样咱们能够从不一样角度来展现数据的方方面面。可是,面对变化万千的需求,Qt 预约义的几个模型是远远不能知足须要的。所以,咱们还必须自定义模型。编程

相似QAbstractView类之于自定义视图,QAbstractItemModel 为自定义模型提供了一个足够灵活的接口。它可以支持数据源的层次结构,可以对数据进行增删改操做,还可以支持拖放。不过,有时候一个灵活的类每每显得过于复杂,因此,Qt 又提供了QAbstarctListModelQAbstractTableModel两个类来简化非层次数据模型的开发。顾名思义,这两个类更适合于结合列表和表格使用。数据结构

本节,咱们正式开始对自定义模型进行介绍。函数

 

在开始自定义模型以前,咱们首先须要思考这样一个问题:咱们的数据结构适合于哪一种视图的显示方式?是列表,仍是表格,仍是树?若是咱们的数据仅仅用于列表或表格的显示,那么QAbstractListModel或者QAbstractTableModel 已经足够,它们为咱们实现了不少默认函数。可是,若是咱们的数据具备层次结构,而且必须向用户显示这种层次,咱们只能选择QAbstractItemModel。无论底层数据结构是怎样的格式,最好都要直接考虑适应于标准的QAbstractItemModel的接口,这样就可让更多视图可以轻松访问到这个模型。编码

如今,咱们开始自定义一个模型。这个例子修改自《C++ GUI Programming with Qt4, 2nd Edition》。首先描述一下需求。咱们想要实现的是一个货币汇率表,就像银行营业厅墙上挂着的那种电子公告牌。固然,你能够选择QTableWidget。的确,直接使用QTableWidget确实很方便。可是,试想一个包含了 100 种货币的汇率表。显然,这是一个二维表,而且对于每一种货币,都须要给出相对于其余 100 种货币的汇率(咱们把本身对本身的汇率也包含在内,只不过这个汇率永远是 1.0000)。如今,按照咱们的设计,这张表要有 100 x 100 = 10000 个数据项。咱们但愿减小存储空间,有没有更好的方式?因而咱们想,若是咱们的数据不是直接向用户显示的数据,而是这种货币相对于美圆的汇率,那么其它货币的汇率均可以根据这个汇率计算出来了。好比,我存储人民币相对美圆的汇率,日元相对美圆的汇率,那么人民币相对日元的汇率只要做一下比就能够获得了。这种数据结构就没有必要存储 10000 个数据项,只要存储 100 个就够了(实际状况中这多是不现实的,由于两次运算会带来更大的偏差,但这不在咱们如今的考虑范畴中)。设计

因而咱们设计了CurrencyModel类。它底层使用QMap<QString, double>数据结构进行存储,QString类型的键是货币名字,double类型的值是这种货币相对美圆的汇率。(这里提一点,实际应用中,永远不要使用 double 处理金额敏感的数据!由于 double 是不精确的,不过这一点显然不在咱们的考虑中。)code

首先从头文件开始看起:对象

#ifndef CURRENCYMODEL_H
#define CURRENCYMODEL_H

#include <QWidget>
#include <QAbstractTableModel>
#include <QMap>

class CurrencyModel : public QAbstractTableModel
{
public:
    CurrencyModel(QObject *parent = 0);
    void setCurrencyMap(const QMap<QString, double> &map);
    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
    QVariant data(const QModelIndex &index, int role) const;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const;
private:
    QString currencyAt(int offset) const;
    QMap<QString, double> currencyMap;
};

#endif // CURRENCYMODEL_H

这段代码平淡无奇,咱们继承了QAbstractTableModel类,而后重写了所要求的几个函数。构造函数一样如此:继承

CurrencyModel::CurrencyModel(QObject *parent)
    : QAbstractTableModel(parent)
{
}

rowCount()columnCount()用于返回行和列的数目。记得咱们保存的是每种货币相对美圆的汇率,而须要显示的是它们两两之间的汇率,所以这两个函数都应该返回这个 map 的项数:接口

int CurrencyModel::rowCount(const QModelIndex & parent) const
{
    return currencyMap.count();
}

int CurrencyModel::columnCount(const QModelIndex & parent) const
{
    return currencyMap.count();
}

headerData()用于返回列名:开发

QVariant CurrencyModel::headerData(int section, Qt::Orientation, int role) const
{
    if (role != Qt::DisplayRole) {
        return QVariant();
    }
    return currencyAt(section);
}

咱们在前面的章节中介绍过有关角色的概念。这里咱们首先判断这个角色是否是用于显示的,若是是,则调用currencyAt()函数返回第 section 列的名字;若是不是则返回一个空白的QVariant对象。currencyAt()函数定义以下:

QString CurrencyModel::currencyAt(int offset) const
{
    return (currencyMap.begin() + offset).key();
}

若是不了解QVariant类,能够简单认为这个类型至关于 Java 里面的 Object,它把 Qt 提供的大部分数据类型封装起来,起到一个类型擦除的做用。好比咱们的单元格的数据能够是 string,能够是 int,也能够是一个颜色值,这么多类型怎么使用一个函数返回呢?回忆一下,返回值并不用于区分一个函数。因而,Qt 提供了QVariant类型。你能够把不少类型存放进去,到须要使用的时候使用一系列的 to 函数取出来便可。好比把 int 包装成一个 QVariant,使用的时候要用QVariant::toInt()从新取出来。这很是相似于 union,可是 union 的问题是,没法保持没有默认构造函数的类型,因而 Qt 提供了QVariant做为 union 的一种模拟。

setCurrencyMap()函数则是用于设置底层的实际数据。因为咱们不可能将这种数据硬编码,因此咱们必须为模型提供一个用于设置的函数:

void CurrencyModel::setCurrencyMap(const QMap<QString, double> &map)
{
    beginResetModel();
    currencyMap = map;
    endResetModel();
}

咱们固然能够直接设置 currencyMap,可是咱们依然添加了beginResetModel()endResetModel()两个函数调用。这将告诉关心这个模型的其它类,如今要重置内部数据,你们要作好准备。这是一种契约式的编程方式。

接下来即是最复杂的data()函数:

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

    if (role == Qt::TextAlignmentRole) {
        return int(Qt::AlignRight | Qt::AlignVCenter);
    } else if (role == Qt::DisplayRole) {
        QString rowCurrency = currencyAt(index.row());
        QString columnCurrency = currencyAt(index.column());
        if (currencyMap.value(rowCurrency) == 0.0) {
            return "####";
        }
        double amount = currencyMap.value(columnCurrency)
                            / currencyMap.value(rowCurrency);
        return QString("%1").arg(amount, 0, 'f', 4);
    }
    return QVariant();
}

data()函数返回一个单元格的数据。它有两个参数:第一个是QModelIndex,也就是单元格的位置;第二个是role,也就是这个数据的角色。这个函数的返回值是QVariant类型。咱们首先判断传入的index是否是合法,若是不合法直接返回一个空白的QVariant。而后若是roleQt::TextAlignmentRole,也就是文本的对齐方式,返回int(Qt::AlignRight | Qt::AlignVCenter);若是是Qt::DisplayRole,就按照咱们前面所说的逻辑进行计算,而后以字符串的格式返回。这时候你就会发现,其实咱们在 if…else… 里面返回的不是一种数据类型:if 里面返回的是 int,而 else 里面是QString,这就是QVariant的做用了。

为了看看实际效果,咱们可使用这样的main()函数代码:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QMap<QString, double> data;
    data["USD"] = 1.0000;
    data["CNY"] = 0.1628;
    data["GBP"] = 1.5361;
    data["EUR"] = 1.2992;
    data["HKD"] = 0.1289;

    QTableView view;
    CurrencyModel *model = new CurrencyModel(&view);
    model->setCurrencyMap(data);
    view.setModel(model);
    view.resize(400, 300);
    view.show();

    return a.exec();
}

这是咱们的实际运行效果:

相关文章
相关标签/搜索