#(50):自定義可編輯模型
上一章我們了解了如何自定義只讀模型。顧名思義,只讀模型只能夠用于展示只讀數據,用戶不能對其進行修改。如果允許用戶修改數據,則應該提供可編輯的模型。可編輯模型與只讀模型非常相似,至少在展示數據方面幾乎是完全一樣的,所不同的是可編輯模型需要提供用戶編輯數據后,應當如何將數據保存到實際存儲值中。
我們還是利用上一章的`CurrencyModel`,在此基礎上進行修改。相同的代碼這里不再贅述,我們只列出增加以及修改的代碼。相比只讀模型,可編輯模型需要增加以下兩個函數的實現:
~~~
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole);
~~~
還記得之前我們曾經介紹過,在 Qt 的 model/view 模型中,我們使用委托 delegate 來實現數據的編輯。在實際創建編輯器之前,委托需要檢測這個數據項是不是允許編輯。模型必須讓委托知道這一點,這是通過返回模型中每個數據項的標記 flag 來實現的,也就是這個 flags() 函數。這本例中,只有行和列的索引不一致的時候,我們才允許修改(因為對角線上面的值恒為 1.0000,不應該對其進行修改):
~~~
Qt::ItemFlags CurrencyModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags(index);
if (index.row() != index.column()) {
flags |= Qt::ItemIsEditable;
}
return flags;
}
~~~
注意,我們并不是在判斷了`index.row() != index.column()`之后直接返回`Qt::ItemIsEditable`,而是返回`QAbstractItemModel::flags(index) | Qt::ItemIsEditable`。這是因為我們不希望丟棄原來已經存在的那些標記。
我們不需要知道在實際編輯的過程中,委托究竟做了什么,只需要提供一種方式,告訴 Qt 如何將委托獲得的用戶輸入的新的數據保存到模型中。這一步驟是通過`setData()`函數實現的:
~~~
bool CurrencyModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
if (index.isValid()
&& index.row() != index.column()
&& role == Qt::EditRole) {
QString columnCurrency = headerData(index.column(),
Qt::Horizontal, Qt::DisplayRole)
.toString();
QString rowCurrency = headerData(index.row(),
Qt::Vertical, Qt::DisplayRole)
.toString();
currencyMap.insert(columnCurrency,
value.toDouble() * currencyMap.value(rowCurrency));
emit dataChanged(index, index);
return true;
}
return false;
}
~~~
回憶一下我們的業務邏輯:我們的底層數據結構中保存的是各個幣種相對美元的匯率,顯示的時候,我們使用列與行的比值獲取兩兩之間的匯率。例如,當我們修改`currencyMap["CNY"]/currencyMap["HKD"]`的值時,我們認為人民幣相對美元的匯率發生了變化,而港幣相對美元的匯率保持不變,因此新的數值應當是用戶新輸入的值與原來`currencyMap["HKD"]`的乘積。這正是上面的代碼所實現的邏輯。另外注意,在實際修改之前,我們需要檢查 index 是否有效,以及從業務來說,行列是否不等,最后還要檢查角色是不是`Qt::EditRole`。這里為方便起見,我們使用了`Qt::EditRole`,也就是編輯時的角色。但是,對于布爾類型,我們也可以選擇使用設置`Qt::ItemIsUserCheckable`標記的`Qt::CheckStateRole`,此時,Qt 會顯示一個選擇框而不是輸入框。注意這里的底層數據都是一樣的,只不過顯示方式的區別。當數據重新設置是,模型必須通知視圖,數據發生了變化。這要求我們必須發出`dataChanged()`信號。由于我們只有一個數據發生了改變,因此這個信號的兩個參數是一致的(`dataChanged()`的兩個參數是發生改變的數據區域的左上角和右下角的索引值,由于我們只改變了一個單元格,所以二者是相同的)。最后,如果數據修改成功就返回 true,否則返回 false。
當我們完成以上工作時,還需要修改一下`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 || role == Qt::EditRole) {
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();
}
~~~
我們的修改很簡單:僅僅是增加了`role == Qt::EditRole`這么一行判斷。這意味著,當是`EditRole`時,Qt 會提供一個默認值。我們可以試著刪除這個判斷來看看其產生的效果。
最后運行一下程序,修改一下數據就會發現,當我們修改過一個單元格后,Qt?會自動刷新所有受影響的數據的值。這也正是?model / view 模型的強大之處:對數據模型的修改會直接反映到視圖上。
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)