1. Model/View結構
這種結構,其實就是將介面元件與所編輯的資料分離開來,又通過資料來源的方式連線起來,相當於解耦,檢視層只關心顯示和與使用者互動,而資料層負責與實際的資料進行通訊,併為檢視元件提供資料介面
網上比較經典的圖如下
是不是很清晰明瞭
關於MV的例項之前已經發過一期,這裡就不再贅述,連結如下
2. 自定義模型
2.1 定義
實現自定義模型可以通過QAbstractItemModel類繼承,也可以通過QAbstractListModel,QAbstractTableModel類繼承實現列表模型或表格模型。
2.2 標準資料模型
Qt實現了4類標準資料模型供我們在不同的場景下使用:
- QStringListModel:儲存字串列表。
- QStandardItemModel:儲存樹狀結構的任意資料。
- QFileSystemModel:儲存本地檔案系統上的檔案和目錄資訊。
- QSqlQueryModel、QSqlRelationalTableModel、QSqlTableModel:儲存關係型資料庫中的資料。
如果使用情況和上述情況之一比較相似,則可以考慮繼承對應的模型類,並重新實現少數虛擬函式。
2.3 抽象模型
抽象資料模型有3類:
- QAbstractItemModel:項模型,這是所有資料模型的基類。
- QAbstractListModel:列表模型,結合QListView使用最合適。
- 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是個什麼東東,在這裡我直接列出官方的文件
比較常用的有下面這些
這裡提到了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 執行結果
3. 總結
根據這篇部落格,完整的梳理了一下,自定義模型需要乾的事情,如果有錯誤的話,請在評論區進行說明,我會修改