程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> Qt學習之路(45): 自定義model之一

Qt學習之路(45): 自定義model之一

編輯:關於C語言

前面我們說了Qt提供的幾個預定義model。但是,面對變化萬千的需求,那幾個model是遠遠不能滿足我們的需要的。另外,對於Qt這種框架來說,model的選擇首先要能滿足絕大多數功能的需要,這就是說,可能這個model中的某些功能你永遠也不會用到,但是還要帶著它,這樣做的後果就是效率不會很高。所以,我們還必須要能夠自定義model。   在我們真正的完成自定義model之前,先來看看在Qt的model-view架構中的幾個關鍵的概念。一個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): 2010-1-17 下面我們通過一個簡單的例子來看看如何實現自定義model。這個例子來自C++ GUI Programming with Qt4, 2nd Edition。首先描述一下需求。這裡我們要實現的是一個類似於貨幣匯率表的table。或許你會想,這是一個很簡單的實現,直接用QTableWidget不就可以了嗎?的確,如果直接使用QTableWidget確實很方便。但是,試想一個包含了100種貨幣的匯率表。顯然,這是一個二維表,並且,對於每一種貨幣,都需要給出相對於其他100種貨幣的匯率(在這裡,我們把自己對自己的匯率也包含在內,只不過這個匯率永遠是1.0000)。那麼,這張表要有100 x 100 = 10000個數據項。現在要求我們減少存儲空間。於是我們想,如果我們的數據不是顯示的數據,而是這種貨幣相對於美元的匯率,那麼,其他貨幣的匯率都可以根據這個匯率計算出來了。比如說,我存儲的是人民幣相對美元的匯率,日元相對美元的匯率,那麼人民幣相對日元的匯率只要作一下比就可以得到了。我沒有必要存儲10000個數據項,只要存儲100個就夠了。於是,我們要自己實現一個model。   CurrencyModel就是這樣一個model。它底層的數據使用一個QMap<QString, double>類型的數據,作為key的QString是貨幣名字,作為value的double是這種貨幣對美元的匯率。然後我們來看代碼:   .h 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;
};   .cpp CurrencyModel::CurrencyModel(QObject *parent)
        : QAbstractTableModel(parent)
{
}

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

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

QString CurrencyModel::currencyAt(int offset) const
{
        return (currencyMap.begin() + offset).key();
}   我們選擇了繼承QAbstractTableModel。雖然是自定義model,但各種model之間也會有很多共性。Qt提供了一系列的抽象類供我們繼承,以便讓我們只需要覆蓋掉幾個函數就可以輕松地定義出我們自己的model。Qt提供了QAbstractListModel和QAbstractTableModel兩類,前者是一維數據model,後者是二維數據model。如果你的數據很復雜,那麼可以直接繼承QAbstractItemModel。這三個類之間的關系可以表述如下:(出自C++ GUI Programming with Qt4, 2nd Edition):   2010-1-17 構造函數中沒有添加任何代碼,只要調用父類的構造函數就可以了。然後我們重寫了rowCount()和columnCount()這兩個函數,用於返回model的行數和列數。由於我們使用一維的map記錄數據,因此這裡的行和列都是map的大小。然後我們看最復雜的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。至此,我們還是第一次見到這個類型。這個類型相當於是Java裡面的Object,它把絕大多數Qt提供的數據類型都封裝起來,起到一個數據類型“擦除”的作用。比如我們的table單元格可以是string,也可以是int,也可以是一個顏色值,那麼這麼多類型怎麼返回呢?於是,Qt提供了這個QVariant類型,你可以把這很多類型都存放進去,到需要使用的時候使用一系列的to函數取出來即可。比如你把int包裝成一個QVariant,使用的時候要用QVariant::toInt()重新取出來。這裡需要注意的是,QVariant類型的放入和取出必須是相對應的,你放入一個int就必須按int取出,不能用toString(), Qt不會幫你自動轉換。或許你會問,Qt不是提供了一個QObject類型嗎?為什麼不像Java一樣都用Object呢?關於這一點豆子也沒有官方文檔,不過可以猜測一下。和Java不同,C++的面向對象體系不是單根的,C++對象並不是都繼承於某一個類,因此,如果你要實現一個這種功能的類,做到“類型擦除”,就必須用一個類包含所有的數據類型。就相當於設計一個能放進所有形狀的盒子,你才能把各種各樣的形狀放進去。這樣的話這個類就會變得異常龐大。對於Qt,QObject類是大多數類繼承的類,理應越小越好,因此就把這個功能抽取出來,形成了一個新類。這也只是豆子的猜測,大家不必往心裡去:-)   好了,下面看這個類的內容。首先判斷傳入的index是不是合法,如果不合法直接return一個空白的QVariant。然後如果role是Qt::TextAlignmentRole,也就是文本的對象方式,那麼就返回int(Qt::AlignRight | Qt::AlignVCenter);否則,role如果是Qt::DisplayRole,就按照我們前面所說的邏輯進行計算,然後按照字符串返回。這時候你就會發現,其實我們在if…else…裡面返回的不是一種數據類型,if裡面是int,而else裡面是QString,這就是QVariant的作用了,也正是“類型擦除”的意思。   剩下的三個函數就很簡單了:headerData()返回列名或者行名;setCurrencyMap()用於設置底層的數據源;currencyAt()返回偏移量為offset的鍵值。   至於調用就很簡單了: CurrencyTable::CurrencyTable()
{
        QMap<QString, double> data;
        data["NOK"] = 1.0000;
        data["NZD"] = 0.2254;
        data["SEK"] = 1.1991;
        data["SGD"] = 0.2592;
        data["USD"] = 0.1534;

        CurrencyModel *model = new CurrencyModel;
        model->setCurrencyMap(data);

        QTableView *view = new QTableView(this);
        view->setModel(model);
        view->resize(400, 300);
}   好了,最後讓我們來看一下最終結果吧!   2010-1-17   注意,這一章中的代碼不是完整的代碼,缺少view的頭文件,不過這只是一個空白的文件。你也可以直接把view的代碼放到main()函數裡面運行。

本文出自 “豆子空間” 博客,請務必保留此出處http://devbean.blog.51cto.com/448512/267435

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved