模型、視圖、委托
模型/視圖架構基於MVC設計模式發展而來。MVC中,模型(Model)用來表示數據;視圖(View)是界面,用來顯示數據;控制(Controller)定義界面對用戶輸入的反應方式。
Qt中視圖和控制結合在一起形成新的模型/視圖架構。這同樣將數據的存儲和數據向用戶展示進行了分離,但提供了更為簡單的框架。數據和界面分離,使得相同的數據可以在不同的視圖顯示,而且可以擴展新的視圖,而不需要改變底層的數據框架。為了靈活處理用戶輸入,引入了委托,也稱為代理,使用它可以定制數據的渲染和編輯方式。
模型與數據通信,為其它組件提供接口。視圖通過模型索引(Model Index)從模型中獲取數據,模型索引用來表示數據項。委托渲染數據項,編輯項目時,委托使用模型索引直接與模型通信。
它們之間的關系為:
(1).當數據源發生變化時,模型發出信號通知視圖。
(2).用戶對界面進行操作時,視圖發出信號提供交互信息。
(3).用戶編輯數據時,代理發出信號告知模型和視圖編輯器的狀態。
1. 模型
模型用來提供數據,並提供了一個標准的接口供視圖和委托來訪問數據。這個接口由QAbstractItemModel類來定義,不管數據項是什么結構,它都會以層次結構來表示數據,這個結構中包含了數據項表。視圖按照這種約定來訪問模型中的數據,但這不會影響數據的顯示,視圖可以使用任何形式將數據顯示出來。當數據發生變化時,模型會通過信號和槽機制通知關聯的視圖。
(1) 模型索引 QModelIndex
模型索引使數據的表示與數據的獲取相分離,每一塊可以獲取的數據都用一個模型索引來表示,視圖和委托使用模型索引來請求數據項並顯示。
模型索引包含一個模型指針,指向創建它們的模型,使用多個模型時可以避免出錯。
由於模型中的數據在隨時變化,因此模型引隨時會變化,不需要也不應當保存一個模型索引,如果需要長時間引用一塊數據,則必須使用QPersistentModelIndex創建模型索引。
QAbstractItemModel類中提供了 index接口獲取模型索引,QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()),通常可將數據看成是一個表格結構,提供行和列的索引,對於樹形結構,則提供其父索引。
(2) 數據項 QStandardItem
模型中的數據項可以作為各種角色在其它組件中使用,允許為其它組件提供不同類型的數據。通過為每個角色提供合適的數據,模型可以告知視圖和委托如何顯示數據。
Qt::DisplayRole |
數據為QString類型,渲染為文本 |
Qt::DecorationRole |
數據被渲染為圖標等裝飾,類型通常為QColor,QIcon,QPixmap等 |
Qt::EditRole |
數據為QString類型,可在編輯器中編輯 |
Qt::ToolTipRole |
數據為QString類型,顯示在工具提示中 |
Qt::StatusTipRole |
數據為QString類型,顯示在狀態欄 |
Qt::WhatThisRole |
數據為QString類型,顯示在“What’s This?”模式下 |
Qt::SizeHintRole |
數據類型為QSize,表示數據項的大小,將會應用到視圖 |
(3) QAbstractItemModel 提供了靈活的接口,可以將數據表示為列表、表格、數等形式。其子類 QAbstractListModel 和 QAbstractTableModel 為列表和表格結構的數據提供了一些常見的默認實現。
Qt定義了許多默認的模型實現,在應用中可以直接拿來使用。
QStandardItemModel:一個可被當作表模型、表格模型、數模型使用的通用模型,可用來管理復雜的樹型結構樹據,每一個數據項都可以包含任意數據。但它有個缺點:加載大數據時較慢。
QStringListModel:用來存儲一個簡單的QString項目的列表模型。
QFileSystemModel:提供本地文件系統中的文件和目錄信息。
QSqlQueryModel:訪問數據庫。
(4) 自定義模型
當要為一個已經存在的數據結構創建一個新的模型時,需要考慮使用哪種類型的模型來為數據提供接口,如果數據結構可以表示為項目列表或表格,則可以考慮子類化QAbstractListModel 和 QAbstractTableModel來實現。
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; // 為不同角色提供數據
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); // 設置對應模型索引下的數據
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; // 表頭數據
Qt::ItemFlags flags(const QModelIndex &index) const; // 返回模型索引是否可編輯,是否可用,編輯,選擇等等狀態
int rowCount(const QModelIndex &parent = QModelIndex()) const; // 當前父索引下的行數
int columnCount(const QModelIndex &parent = QModelIndex()) const; // 當前父索引下的列數
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()); // 添加行 添加前需要調用 beginInsertRows,添加完成后調用 endInsertRows
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); // 刪除行 刪除前需要調用 beginRemoveRows,刪除完成后調用 endRemoveRows
2. 視圖
視圖用來顯示數據。
視圖中的標准接口由 QAbstractItemView 類提供。
視圖通常管理從模型獲取的數據的整體布局,它們可以自己渲染獨立的數據項,也可以使用委托來渲染和編輯。
除了呈現數據,視圖還處理項目間的導航以及項目選擇的某些方面,如設置選擇行為 SelectionBehavior、選擇模式SelectionMode,上下文菜單和拖放等。
(1) QItemSelectionModel 選擇模型
這里的選擇模型和前面的模型是不同的概念,在視圖中被選擇項目的信息都存儲在 QItemSelectionModel 實例中,這樣被選擇項目的模型索引便保持在一個獨立的模型中,與所有的視圖都是獨立的。在一個模型上設置多個視圖時就可以實現多個視圖共享。QItemSelectionModel 對象可以通過視圖的接口獲取和重新設置。
QItemSelectionModel *selectionModel() const;
void setSelectionModel(QItemSelectionModel *selectionModel);
QItemSelectionModel 對象會保存當前模型的指針,也可以返回操作的模型索引列表。
QModelIndexList selectedIndexes() const;
(2) Qt提供了幾種常用的視圖,都是使用規范的格示來顯示數據,如果還要實現條形圖、餅狀圖或更復雜的圖形,就要重新實現視圖類。
QListView: 將數據顯示為一個列表。
QTableView: 將模型中的數據顯示在一個表格中。
QTreeView: 將模型中的數據項顯示在具有層次的列表中。
QColumnView: 提供一個多級視圖(每點開一選項都會在它旁邊出現一個菜單)。
3.委托
委托提供特殊顯示和編輯功能。委托的標准接口由 QAbstractItemDelegate 類提供。委托通過 paint() 和 sizeHint() 函數來使它們可以渲染自身的內容。
委托的編輯功能可以通過兩種方式來實現,一種是使用部件來管理編輯過程,另一種是直接處理事件(通過子類化輸入控件完成)。
QItemDelegate 和QStyledItemDelegate 是委托Qt提供的兩種委托實現。
QStyledItemDelegate使用當前的樣式來繪制項目,當要自定義的委托要使用樣式表一起應用時,建議使用它作為基類。
QListView、QTableView和QTreeView都使用QItemDelegate來提供編輯功能,這使得它們只有普通風格的渲染。
視圖可以獲取和設置委托。
void setItemDelegate(QAbstractItemDelegate *delegate);
QAbstractItemDelegate *itemDelegate() const;
(1) 自定義委托示例
當視圖需要編輯器時,它會告知委托為被修改的項目提供一個編輯器部件,委托會調用 createEditor 函數提供一個合適的部件。
在自定義委托時,createEditor返回一個可編輯輸入的控件對象,如果不需要編輯,則返回 nullptr, 返回的指針對象不需要保存,因為視圖在不需要的時候會銷毀它。
setEditorData 函數將模型中的數據渲染到編輯器中。
setModelData 函數在用戶完成了輸入之后,將數據存儲到模型中。
updateEditorGeometry 函數用來調整編輯器的位置和大小,QStyleOptionViewItem對象提供了幾何布局相關的信息。
代理對象會在完成編輯后發射 closeEditor 信號來告知視圖。
class SpinDelegate : public QItemDelegate
{
Q_OBJECT
public:
using QItemDelegate::QItemDelegate;
// 創建編輯器
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
// 為編輯器設置數據
void setEditorData(QWidget* editor, const QModelIndex& index) const override;
// 將數據寫入到模型
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;
// 更新編輯器的幾何布局
void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
};
QWidget* SpinDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QSpinBox* editor = new QSpinBox{parent};
editor->setRange(0, 40);
return editor;
}
void SpinDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
QSpinBox* spinBox = dynamic_cast<QSpinBox*>(editor);
if (spinBox)
{
bool isOK = false;
int value = index.model()->data(index, Qt::EditRole).toInt(&isOK);
if (isOK)
{
spinBox->setValue(value);
}
}
}
void SpinDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
{
QSpinBox* spinBox = dynamic_cast<QSpinBox*>(editor);
if (spinBox)
{
spinBox->interpretText(); // 確保取得最新的數值
int value = spinBox->value();
model->setData(index, value, Qt::EditRole);
}
}
void SpinDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
editor->setGeometry(option.rect);
}