Qt5MV自定義模型與例項淺析

進擊的汪sir發表於2021-07-21

1. Model/View結構

這種結構,其實就是將介面元件與所編輯的資料分離開來,又通過資料來源的方式連線起來,相當於解耦,檢視層只關心顯示和與使用者互動,而資料層負責與實際的資料進行通訊,併為檢視元件提供資料介面

網上比較經典的圖如下

image-20210718224949224

是不是很清晰明瞭

關於MV的例項之前已經發過一期,這裡就不再贅述,連結如下

Qt Model/view 小例項 檔案目錄瀏覽器

2. 自定義模型

2.1 定義

實現自定義模型可以通過QAbstractItemModel類繼承,也可以通過QAbstractListModel,QAbstractTableModel類繼承實現列表模型或表格模型。

2.2 標準資料模型

Qt實現了4類標準資料模型供我們在不同的場景下使用:

  1. QStringListModel:儲存字串列表
  2. QStandardItemModel:儲存樹狀結構的任意資料
  3. QFileSystemModel:儲存本地檔案系統上的檔案和目錄資訊
  4. QSqlQueryModel、QSqlRelationalTableModel、QSqlTableModel:儲存關係型資料庫中的資料

如果使用情況和上述情況之一比較相似,則可以考慮繼承對應的模型類,並重新實現少數虛擬函式。

2.3 抽象模型

抽象資料模型有3類:

  1. QAbstractItemModel:項模型,這是所有資料模型的基類。
  2. QAbstractListModel:列表模型,結合QListView使用最合適。
  3. QAbstractTableModel:表模型,結合QTableView使用最合適。

2.4 自定義模型例項

我們以繼承QAbstractTableModel為例子,來實現一個自定義模型

如果我們繼承一個類,就必須得實現它的全部的純虛擬函式

對於這個抽象表格模型類,我們得繼承下面這些純虛擬函式

static string BlogAdress = "https://www.cnblogs.com/wanghongyang/";

virtual int rowCount(const QModelIndex &parent=QModelIndex()) const;
virtual int columnCount(const QModelIndex &parent=QModelIndex()) const;

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

接下來說一下這4個虛擬函式的作用

virtual int rowCount(const QModelIndex &parent=QModelIndex()) const;

返回給定父節點下的行數。當父節點有效時,意味著rowCount返回父節點的子節點數。

virtual int columnCount(const QModelIndex &parent=QModelIndex()) const;

與前面的對應,返回給定父節點的子節點的列數。

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

為索引引用的項返回儲存在給定角色下的資料。

注意:如果你沒有返回值,返回一個無效的QVariant而不是返回0。

這裡我們說明一下,role是個什麼東東,在這裡我直接列出官方的文件

image-20210719204750094

比較常用的有下面這些

image-20210719212112702


這裡提到了QVariant,那我們再簡單的談談QVariant的用法

QVariant 類用於封裝資料成員的型別及取值等資訊,該類類似於 C++共用體 union,一個QVariant 物件,一次只能儲存一個單一型別的值。該類封裝了 Qt 中常用的型別,對於QVariant 不支援的型別 ( 比如使用者自定義型別 ) ,則需要使用Q_DECLARE_METATYPE( Type )巨集進行註冊

QVariant 擁有常用型別的單形參建構函式,因此可把這些常用型別轉換為 QVariant 型別,同時 QVariant 還過載了賦值運算子,因此可把常用型別的值直接賦給 QVariant 物件。

注意:QVariant 沒有 char 型別的建構函式,若使用 char 值會被轉換為對應的 int 型

下面分情況討論QVariant的使用

1) 支援的型別

對於QVariant支援的型別,可直接賦值,但是取值時,對於存入的是什麼型別,取出也要為這個型別

QVariant var;
var.setValue(12);
int data=var.toInt();

或者

QVariant var=12;
int data=var.toInt();

2) 對於不支援的型別(自定義型別為例)

如自己定義的結構體。由於Qt都是基於元物件系統,故要在標頭檔案裡面要註冊此類屬於元型別。儲存用到了QVariant QVariant::fromValue(const T &value)void QVariant::setValue(const T &value)。獲取用到了T QVariant::value() const,在這之前一般要bool QVariant::canConvert(int targetTypeId) const先用進行判斷,是否可以轉換。例子如下:

.h檔案宣告

 struct MyClass{
    int id;
    QString name;
};
Q_DECLARE_METATYPE(MyClass)

.cpp檔案定義

//儲存資料
    MyClass myClass;
    myClass.id=0;
    myClass.name=QString("LiMing");
 
    data[0]=QString("ddd");	
    data[1]=123;
    data[3]=QVariant::fromValue(myClass);
 
 
//獲取資料
    QString str=data.value(0).toString();
    int val=data.value(1).toInt();
// 注意,先判斷
    if(data[3].canConvert<MyClass>())
    {
        MyClass myClass=data[3].value<MyClass>();
        int id=myClass.id;
        QString name=myClass.name;
    }

說完了QVariant,我們繼續說自定義模型最後一個虛擬函式

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

返回標頭中指定方向的給定角色和區段的資料。對於水平標頭,節號對應於列號。類似地,對於垂直標題,節號對應於行號。

2.5 具體實現

建構函式

ModelEx::ModelEx(QObject *parent) :
    QAbstractTableModel(parent)
{
    armyMap[1]=tr("空軍");
    armyMap[2]=tr("海軍");
    armyMap[3]=tr("陸軍");
    armyMap[4]=tr("海軍陸戰隊");

    weaponTypeMap[1]=tr("轟炸機");
    weaponTypeMap[2]=tr("戰鬥機");
    weaponTypeMap[3]=tr("航空母艦");
    weaponTypeMap[4]=tr("驅逐艦");
    weaponTypeMap[5]=tr("直升機");
    weaponTypeMap[6]=tr("坦克");
    weaponTypeMap[7]=tr("兩棲攻擊艦");
    weaponTypeMap[8]=tr("兩棲戰車");
    populateModel();
}

建構函式中,存放的是資料,下面是定義的私有成員變數

    QVector<short> army;
    QVector<short> weaponType;

    QMap<short,QString> armyMap;
    QMap<short,QString> weaponTypeMap;

    QStringList  weapon;
    QStringList  header;

下面是populateModel()函式

void ModelEx::populateModel()
{
    header<<tr("軍種")<<tr("種類")<<tr("武器");
    army<<1<<2<<3<<4<<2<<4<<3<<1;
    weaponType<<1<<3<<5<<7<<4<<8<<6<<2;
    weapon<<tr("B-2")<<tr("尼米茲級")<<tr("阿帕奇")<<tr("黃蜂級")<<tr("阿利伯克級")<<tr("AAAV")<<tr("M1A1")<<tr("F-22");
}

簡單來說,army和weapon就是將數字與具體的值關聯起來而儲存值的容器

然後看看我們重新實現的純虛擬函式(重點)

int ModelEx::columnCount(const QModelIndex &parent) const
{
    return 3;
}

因為模型的列固定為3,所以這裡我們直接返回3

int ModelEx::rowCount(const QModelIndex &parent) const
{
    return army.size();
}

模型的行數要根據數量的大小來定,所以返回size();

QVariant ModelEx::data(const QModelIndex &index, int role) const
{
    if(!index.isValid())
        return QVariant();// 這裡不能直接返回0

    if(role==Qt::DisplayRole)
    {
        switch(index.column())
        {
        case 0:
            return armyMap[army[index.row()]];
            break;
        case 1:
            return weaponTypeMap[weaponType[index.row()]];
            break;
        case 2:
            return weapon[index.row()];
        default:
            return QVariant();
        }
    }
    return QVariant();
}

這個函式用來返回指定索引的資料,將數值對映為文字

其中 role==Qt::DisplayRole: 模型中的條目能夠有不同的角色,這樣可以在不同的情況下提供不同的資料。例如,Qt::DisplayRole用來存取檢視中顯示的文字,角色由列舉類Qt::ItemDataRole定義。

其中index.column()是用來選擇是第幾列,根據列的不同來選擇不同的資料

QVariant ModelEx::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role==Qt::DisplayRole&&orientation==Qt::Horizontal)
        return header[section];
    return QAbstractTableModel::headerData(section,orientation,role);
}

headerData()函式返回固定的表頭資料,設定水平表頭的標題

這裡的orientation==Qt::Horizontal是設定為水平標題

return QAbstractTableModel::headerData(section,orientation,role);

這一行,是當條件不滿足時,呼叫父類的headerData函式,來處理剩下的問題

2.6 執行結果

image-20210719213204388

3. 總結

根據這篇部落格,完整的梳理了一下,自定義模型需要乾的事情,如果有錯誤的話,請在評論區進行說明,我會修改

博主部落格:https://www.cnblogs.com/wanghongyang/

相關文章