Qt包含一組使用模型/視圖結構的類,可以用來管理數據並呈現給用戶。這種體系結構引入的分離使開發人員更靈活地定制項目,並且提供了一個標准模型的接口,以允許廣泛范圍的數據源被使用到到現有的視圖中。
模型 - 視圖 - 控制器(MVC)是一種設計模式,由三類對象組成:
模型:應用程序對象。
視圖:屏幕演示。
控制器:定義了用戶界面響應用戶輸入的方式。
在引入MVC之前,用戶界面的設計往往是將這些對象組合在一起。MVC的解耦帶來了靈活性和重用性。
如果視圖和控制器對象相結合,其結果是模型/視圖結構,仍然分離了數據與呈現給用戶的方式,但提供了基於相同原理的簡單框架。這種分離使得它可以在幾個不同的視圖中顯示相同的數據,並且實現新類型的視圖,而無需改變底層的數據結構。為了靈活地處理用戶輸入,則引入了委托的概念。在此框架引入委托的優點是:它允許項目數據顯示和自定義編輯。
模型/視圖結構
模型與數據源進行通信,在這個體系結構中為其它組件提供了一個接口。通信的性質依賴於數據源的類型以及模型的實現方式。
視圖從模型中得到模型索引,這些都引用到數據項。通過為模型提供模型索引,視圖可以從數據源中檢索數據項。
在標准的視圖里,委托呈現數據項目。當一個項目被編輯,委托與模型直接利用模型索引進行通信。
模型/視圖/委托通信
模型、視圖、委托使用信號和槽相互通信:
模型的信號:通知視圖關於改變由數據源保持的數據。
視圖的信號:提供了關於用戶交互顯示的項目信息。
委托的信號:當編輯時告訴模型和視圖編輯器的狀態。
模型
所有的模型都基於QAbstractItemModel類。這個類定義了一個使用視圖和委托來訪問數據的接口。數據本身不是必須要存儲在模型中,可以在一個數據結構或一個單獨的類、文件、數據庫、或其它一些應用組件。
QAbstractItemModel為數據提供了一個接口,它足夠的靈活性來處理表格、列表、樹形式的數據視圖。然而,實現新的列表和類似於表的數據結構模型時,QAbstractListModel和QAbstractTableModel類是更好的起點,因為它們提供了適當的常用的功能的默認實現。這些類可以派生子類,用來提供支持特定種類的列表和表格的模型。
Qt提供了一些現成的模型,可以用來處理數據項:
QStringListModel:用於存儲簡單的QString的列表項。
QStandardItemModel:管理更復雜的樹結構件,其中每一個項目可以包含任意數據。
QFileSystemModel:提供有關本地文件系統的文件和目錄信息。
QSqlQueryModel、QSqlTableModel、QSqlRelationalTableModel:使用模型/視圖約定來訪問數據庫。
如果這些標准模型不能滿足要求,則可以繼承化QAbstractItemModel、QAbstractListModel或QAbstractTableModel來創建自定義模型。
視圖
提供了完整的實現為各種不同的視圖:而QListView顯示項目列表,QTableView中從一個表模型中顯示數據,QTreeView則顯示了層次列表的數據模型項目。每個類是基於QAbstractItemView抽象基類。雖然這些類是准備使用的實現,他們也可以被子類化,以提供自定義的視圖。
委托
QAbstractItemDelegate在模型/視圖框架中代表抽象的基類。默認的委托實現由QStyledItemDelegate提供,這被Qt的標准視圖用作默認的委托。然而,QStyledItemDelegate和QItemDelegate獨立替代繪畫,且為視圖項提供編輯器。它們之間的區別在於QStyledItemDelegate使用當前樣式來繪制項目。因此,建議實現自定義委托或當與Qt樣式表一起使用時,使用QStyledItemDelegate作為基類。
排序
在模型/視圖結構中有兩種接近的排序方式,選擇哪種方式取決於你的基礎模型。
如果你的模型是可排序的,也就是說,如果重新實現了QAbstractItemModel::sort()方法,QTableView和QTreeView都提供了一個API,允許以編程方式排序來排序模型數據。此外,可以啟用交互式排序(即允許用戶將數據通過單擊視圖的標題進行排序),由QHeaderView::sortIndicatorChanged()信號分別連接到QTableView:: sortByColumn()槽或QTreeView::sortByColumn()槽。
另一種方法,如果模型沒有所需的接口,或者如果想使用一個列表視圖來顯示數據,使用代理模型呈現數據視圖之前應轉換模型的結構。
方便的類
一些便利類都源於標准視圖類的依賴Qt的項目為基礎的項目視圖和表類應用的好處。他們不打算被繼承。
這些類的實例包括QListWidget,QTreeWidget和QTableWidget。
這些類比視圖類靈活性差,且不能與任意的模型使用。建議使用模型/視圖的方法來處理在項目視圖中的數據,除非強烈需要一個基於項目的類。
如果想利用模型/視圖提供的特性方法,同時使用一個基於項目的接口,可以考慮使用視圖類,例如:QListView、QTableView、QTreeView與QStandardItemModel。
使用視圖與現有的模型
QListView和QTreeView類是最合適的視圖來使用QFileSystemModel。下面介紹的示例在樹視圖中顯示一個目錄,旁邊列表視圖中顯示相同的信息。該視圖共享用戶的選擇,這樣選擇的項目在兩個視圖中高亮顯示。
代碼如下:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QSplitter *splitter = new QSplitter;
QFileSystemModel *model = new QFileSystemModel;
model->setRootPath(QDir::currentPath());
QTreeView *tree = new QTreeView(splitter);
tree->setModel(model);
tree->setRootIndex(model->index(QDir::currentPath()));
QListView *list = new QListView(splitter);
list->setModel(model);
list->setRootIndex(model->index(QDir::currentPath()));
splitter->setWindowTitle("Two views onto the same file system model");
splitter->show();
return app.exec();
}
上面的例子中,我們忽略提及如何處理選擇的項目。下面將詳細講述在視圖中處理所選的項目。
基本概念
在模型/視圖結構中,模型提供了視圖與委托訪問數據的標准接口。在Qt中,標准的接口由QAbstractItemModel類定義。無論多么數據項被存儲在任何底層的數據結構中,QAbstractItemModel的所有子類所代表的數據作為包含視圖項的分層結構。視圖使用這個約定來訪問模型中的數據項,但並不限制將該信息傳達給用戶的方式。
Model indexes
為確保數據被分開被訪問,模型索引的概念被引入。可以通過模型索引來獲得每條信息。視圖與委托使用這些索引來請求顯示的數據項。
因此,模型只需要知道如何獲取數據,並通過模型管理的數據的類型可以被相當普遍定義。型號索引包含一個指向創建它們的模型的指針,在處理多個模型時可以防止混亂。
QAbstractItemModel *model = index.model();
模型索引提供臨時參考信息,並且可以用於通過模型來檢索或修改數據。由於模型可能重組其內部結構,模型的索引可能會變得無效,不宜存儲。如果需要長期參考一條信息,必須創建一個持久性模型索引。這為模型保持最新信息提供了一個參考。臨時模型索引由QModelIndex類提供,持久性模型索引由QPersistentModelIndex類提供。
取得對應於數據項的模型索引,模型中必須制定三個屬性:一個行號、一個列號,以及父項的模型索引。
行和列
在最基本的形式中,模型可以被一個簡單的表訪問,表項位於行號和列號,這並不意味着底層數據存儲在數據結構中,使用行號和列號只是一個慣例,以允許組件相互通信。我們可以通過指定行號和列號的模型索引有關的任何特定信息,通過下面的方式得到項目的索引:
QModelIndex index = model->index(row, column, ...);
模型提供的接口簡單,單級的數據結構如列表和表格不需要提供任何其他信息。但是,正如上面的代碼所示,當獲得一個模型索引時,我們需要提供更多的信息。
行和列
圖中顯示了一個基本的表模型,其中的每個項目的位置由一對行號和列號表示。我們通過模型索引(一個項目數據)行號和列號來獲取。
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
父節點
由模型提供的類似於表的接口給數據項是理想的當在表格或列表視圖中使用數據,行號和列號准確地映射到視圖顯示項目的方式。然而,結構,如樹視圖需要更模型為項目暴露一個更靈活的接口。因此,每個項目也可以是另一個表的父項,大致相同的方式,在一個樹視圖中的頂級項目可以包含另一個列表項。
當請求的一個模型項的索引時,必須提供有關該項目的父項目的一些信息。在模型外,指定一個項目的唯一途徑是通過一個模型索引,所以父模型索引也必須如下給出:
QModelIndex index = model->index(row, column, parent);
父項,行和列
該圖顯示了一個樹模型,其中每個項目都依賴於由一個父項,一個行號和一個列號。
項目的“A”和“C”表示模型頂層的兄弟姐妹:
項目“A”有很多孩子,可以通過如下方式由“A”索引得到“B”索引:
QModelIndex indexB = model->index(1, 0, indexA);
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
項目角色
模型中的項目可以為其他組件演繹不同的角色,允許為不同的情況提供不同類型的數據。例如,Qt::DisplayRole可以在用於訪問視圖中被顯示為文本的字符串。通常情況下,包含數據的項目用於若干不同的角色,且標准角色被Qt::ItemDataRole定義。
我們可以通過模型索引傳遞給相應的項目向模型請求項目數據,並通過指定一個角色來獲取想要的數據類型,如下:
QVariant value = model->data(index, role);
數據類型被稱為模型的角色指示器。視圖可以以不同的方式顯示角色,因此,為每個角色提供相應的信息非常重要。
項目數據最常見的用途是覆蓋在Qt::ItemDataRole中定義的標准角色。通過為每個角色提供相應的項目數據,模型可以為視圖和委托提供有關項目應如何呈現給用戶的指示,不同的視圖可以根據需要來解釋或忽略此信息。此外,也可以為應用程序的特定目的而定義附加的角色。
使用模型索引
為了演示如何將數據從一個模型中進行檢索,使用模型索引,我們創建了一個QFileSystemModel,在窗體上沒有視圖以及顯示文件和目錄的名稱。雖然這並不是使用模型的正常方式,它表明模型在處理模型索引上的約定。
我們用以下述方式構建了一個文件系統模型:
QFileSystemModel *model = new QFileSystemModel;
QModelIndex parentIndex = model->index(QDir::currentPath());
int numRows = model->rowCount(parentIndex);
在這種情況下,我們設置了一個默認QFileSystemModel,由該模型使用index()的特定實現來獲取父索引,使用rowCount()函數來計算行號。
為簡單起見,我們只關心模型中的第一列中的項目。我們檢查每一行,依次獲取每一行中的第一個項目的模型索引,以及讀出所存儲在該模型項目中的數據。
for (int row = 0; row <<span style=" color:#c0c0c0;"> numRows; ++row) {
QModelIndex index = model->index(row, 0, parentIndex);
為了獲得一個模型索引,我們指定了行號、列號(第一列為零),以及所有我們想要的項目的父項目的模型索引。每個項目中的文本檢索可以使用模型的data()函數來獲取。我們指定了模型索引和Qt::DisplayRole來獲取數據項中的字符串。
QString text = model->data(index, Qt::DisplayRole).toString();
// Display the text in a widget.
}
使用模型
我們創建一個字符串列表模型作為例子,設置一些數據,並構造一個視圖來顯示模型的內容。這都可以在一個單一的函數執行:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QStringList numbers;
numbers << "One" << "Two" << "Three" << "Four" << "Five";
請注意,QStringListModel被聲明為一個QAbstractItemModel。這使我們能夠為模型使用抽象接口,並確保代碼仍然有效,即使我們使用不同的模型替換了字符串列表模型。
由而QListView提供的列表視圖足以展示字符串列表模型中的項目。我們構建視圖,並設置模型可以使用下面代碼:
QAbstractItemModel *model = new QStringListModel(numbers);
QListView *view = new QListView;
view->setModel(model);
視圖正常顯示方式
view->show();
return app.exec();
}
視圖展現模型的內容,通過模型的接口訪問數據。當用戶試圖編輯一個項目時,視圖使用缺省代表提供一個編輯器部件。
上面的圖顯示QListView如何使用字符串列表模型表示數據。由於模型可編輯,視圖會自動允許列表中的每個項目使用默認的委托進行編輯。
一個模型的多個視圖
一個模型可以為多個視圖所使用。在下面的代碼中,我們創建兩個表視圖,使用的均是創建好的同一個模型。
QTableView *firstTableView = new QTableView;
QTableView *secondTableView = new QTableView;
firstTableView->setModel(model);
secondTableView->setModel(model);
在模型/視圖框架中使用信號和槽是指更改模型可以傳遞給所有相連的視圖,以確保始終可以訪問相同的數據,而不管所使用的視圖。
上面的圖顯示了統一模型的兩種不同的視圖,每個都包含了一些選定的項目。盡管模型中的數據在視圖顯示一致,每個視圖維護它自己的內部選擇模型。這在某些情況下有用,但對於許多應用來說,則需要一個共享的選擇模型。
視圖共享選擇
雖然視圖類提供自己的默認選擇模型很方便,但當我們使用多個視圖到同一個模型時,通常需要所有的模型數據和用戶的選擇在所有視圖顯示一致。由於視圖類允許其內部選擇模型進行更換,那么可以使用如下方式實現視圖之間的統一:
secondTableView->setSelectionModel(firstTableView->selectionModel());
第二個視圖給出了第一個視圖的選擇模型。這兩種視圖現在在同一個選擇模型進行操作,保持了數據和所選項目的同步。