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

這是咱們的實際運行效果:

相關文章
相關標籤/搜索