Qt学习:项视图类之自定义模型

    我们先前介绍了三种Qt的预定义模型,分别是QStringListModel,QDirModel(QFileSystemModel), QSortFilterProxyMoel,这些预定义模型为数据的处理和查看提供了很好的方法。但是,有些数据源不能有效的和预定义模型一起工作,因为我们大批量数据的存储是各种各样的,这时候用我们上边提到的三种预定义模型可能能解决问题,但却同时带来了效率和处理上的不便,因此我们需要定义自己的模型,以方便对底层数据集进行优化。
    Qt的MVC架构中有一些比较重要的概念在这里有必要先提一下。
    一个model中的每个数据元素都有一个model索引。这个索引指明这个数据位于model的位置,比如行、列等。这就是前面我们曾经说到过的QModelIndex。每个数据元素还要有一组属性值,称为角色(roles)。这个属性值并不是数据的内容,而是它的属性,比如说,这个数据是用来展示数据的,还是用于显示列头的?因此,这组属性值实际上是Qt的一个enum定义的,比较常见的有Qt::DisplayRole和Qt::EditRole,另外还有Qt::ToolTipRole, Qt::StatusTipRole, 和Qt::WhatsThisRole等。并且,还有一些属性是用来描述基本的显示属性的,比如Qt::FontRole, Qt::TextAlignmentRole, Qt::TextColorRole, Qt::BackgroundColorRole等。 
    对于list model而言,要定位其中的一个数据只需要有一个行号就可以了,这个行号可以通过QModelIndex::row()函数进行访问;对于table model而言,这种定位需要有两个值:行号和列号,这两个值可以通过QModelIndex::row()和QModelIndex::column()这两个函数访问到。另外,对于tree model而言,用于定位的可以是这个元素的父节点。实际上,不仅仅是tree model,并且list model和table model的元素也都有自己的父节点,只不过对于list model和table model,它们元素的父节点都是相同的,并且指向一个非法的QModelIndex。对于所有的model,这个父节点都可以通过QModelIndex::parent()函数访问到。这就是说,每个model的项都有自己的角色数据,0个、1个或多个子节点。既然每个元素都有自己的子元素,那么它们就可以通过递归的算法进行遍历,就像数据结构中树的遍历一样。关于父节点的描述,请看下面这张图(出自C++ GUI Programming with Qt4, 2nd Edition):

    下面我们通过一个简单的例子来看看如何实现自定义model。这个例子来自C++ GUI Programming with Qt4, 2nd Edition。首先描述一下需求。这里我们要实现的是一个类似于货币汇率表的table。或许你会想,这是一个很简单的实现,直接用QTableWidget不就可以了吗?的确,如果直接使用QTableWidget确实很方便。但是,试想一个包含了100种货币的汇率表。显然,这是一个二维表,并且,对于每一种货币,都需要给出相对于其他100种货币的汇率(在这里,我们把自己对自己的汇率也包含在内,只不过这个汇率永远是1.0000)。那么,这张表要有100 x 100 = 10000个数据项。现在要求我们减少存储空间。于是我们想,如果我们的数据不是显示的数据,而是这种货币相对于美元的汇率,那么,其他货币的汇率都可以根据这个汇率计算出来了。比如说,我存储的是人民币相对美元的汇率,日元相对美元的汇率,那么人民币相对日元的汇率只要作一下比就可以得到了。我没有必要存储10000个数据项,只要存储100个就够了。于是,我们要自己实现一个model。
    我们定义一个CurrencyModel类来实现这个功能。 CurrencyModel将和一个标准的QTableWidget类一同使用。这个自定义模型的底层数据被封装在一个QMap<QString, double>中,每一个键都是一个货币编码,并且每一个值都是以美元为单位的货币值。这次我们先看这个类怎么用的,以下是main函数的实现部分:

//main.cpp
#include "currencymodel.h"
#include <QApplication>
#include <QTableView>

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

    QMap<QString, double> currencyMap;
    currencyMap.insert("AUD", 1.3257);
    currencyMap.insert("CHF", 1.2980);
    currencyMap.insert("SGD", 1.6911);
    currencyMap.insert("USD", 1.0000);

    CurrencyModel currencyModel(currencyMap);

    QTableView tableView;
    tableView.setModel(¤cyModel);
    tableView.setAlternatingRowColors(true);
    tableView.show();
    
    return a.exec();
}


            我们用自己定义的一个关联容器来存储我们的值,并在构造CurrencyModel 对象的时候把这个数据集存进我们自定义的model中,然后就用QTableView来观察这些数据。

//currencymodel.h
#ifndef CURRENCYMODEL_H
#define CURRENCYMODEL_H

#include <QAbstractTableModel>
#include <QMap>

class CurrencyModel : public QAbstractTableModel
{
    Q_OBJECT
    
public:
    CurrencyModel(const QMap<QString, double> &map, QObject *parent = 0);

    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

//currencymodel.cpp
#include "currencymodel.h"

CurrencyModel::CurrencyModel(const QMap<QString, double> &map, QObject *parent)
    : QAbstractTableModel(parent)
{
    currencyMap = map;
}

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

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

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();
}

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

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


    我们选择了继承QAbstractTableModel。虽然是自定义model,但各种model之间也会有很多共性。Qt提供了一系列的抽象类供我们继承,以便让我们只需要覆盖掉几个函数就可以轻松地定义出我们自己的model。Qt提供了QAbstractListModel和QAbstractTableModel两类,前者是一维数据model,后者是二维数据model。如果你的数据很复杂,那么可以直接继承QAbstractItemModel。
    data()函数返回单元格的数据。它有两个参数:第一个是QModelIndex,也就是单元格的位置;第二个是role,也就是这个数据的角色。这个函数的返回值是QVariant。至此,我们还是第一次见到这个类型。这个类型相当于是Java里面的Object,它把绝大多数Qt提供的数据类型都封装起来,起到一个数据类型“擦除”的作用。比如我们的table单元格可以是string,也可以是int,也可以是一个颜色值,那么这么多类型怎么返回呢?于是,Qt提供了这个QVariant类型,你可以把这很多类型都存放进去,到需要使用的时候使用一系列的to函数取出来即可。比如你把int包装成一个QVariant,使用的时候要用QVariant::toInt()重新取出来。这里需要注意的是,QVariant类型的放入和取出必须是相对应的,你放入一个int就必须按int取出,不能用toString(), Qt不会帮你自动转换。
    程序的运行效果:

 


    以下是这个类的实现部分:

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页