http://doc.trolltech.com/main-snapshot/model-view-programming.htmlhtml
Qt 4推出了一組新的item view類,它們使用model/view結構來管理數據與表示層的關係。這種結構帶來的功能上的分離給了開發人員更大的彈性來定製數據項的表示,它也提供一個標準的model接口,使得更多的數據源能夠被這些item view使用。這裏對model/view的結構進行了描述,結構中的每一個組件都進行了解釋,給出了一些例子說明了提供的這些類如何使用。mysql
Model/View 結構程序員
Model-View-Controller(MVC), 是從Smalltalk發展而來的一種設計模式,常被用於構建用戶界面。經典設計模式的著做中有這樣的描述:算法
MVC 由三種對象組成。Model是應用程序對象,View是它的屏幕表示,Controller定義了用戶界面如何對用戶輸入進行響應。在MVC以前,用戶界面設計傾向於三者揉合在一塊兒,MVC對它們進行了解耦,提升了靈活性與重用性。sql
假如把view與controller結合在一塊兒,結果就是model/view結構。這個結構依然是把數據存儲與數據表示進行了分離,它與MVC都基於一樣的思想,但它更簡單一些。這種分離使得在幾個不一樣的view上顯示同一個數據成爲可能,也能夠從新實現新的view,而沒必要改變底層的數據結構。爲了更靈活的對用戶輸入進行處理,引入了delegate這個概念。它的好處是,數據項的渲染與編程能夠進行定製。
如上圖所示,model與數據源通信,並提供接口給結構中的別的組件使用。通信的性質依賴於數據源的種類
與model實現的方式。view從model獲取model indexes,後者是數據項的引用。經過把model indexes提供給model,view能夠從數據源中獲取數據。數據庫
在標準的views中,delegate會對數據項進行渲染,當某個數據項被選中時,delegate經過model indexes與model直接進行交流。總的來講,model/view 相關類能夠被分紅上面所提到的三組:models,views,delegates。這些組件經過抽象類來定義,它們提供了共同的接口,在某些狀況下,還提供了缺省的實現。抽象類意味着須要子類化以提供完整的其餘組件但願的功能。這也容許實現定製的組件。models,views,delegates之間經過信號,槽機制來進行通信:編程
從model發出的信號通知view數據源中的數據發生了改變。
從view發出的信號提供了有關被顯示的數據項與用戶交互的信息。
從delegate發生的信號被用於在編輯時通知model和view關於當前編輯器的狀態信息。設計模式
Models數組
全部的item models都基於QAbstractItemModel類,這個類定義了用於views和delegates訪問數據的接口。
數據自己沒必要存儲在model,數據可被置於一個數據結構或另外的類,文件,數據庫,或別的程序組件中。
關於model的基本概念在Model Classes部分中描述。
QAbstractItemModel提供給數據一個接口,它很是靈活,基本知足views的須要,不管數據用如下任何樣的形式
表現,如tables,lists,trees。然而,當你從新實現一個model時,若是它基於table或list形式的數據結構,最好從QAbstractListModel,QAbstractTableModel開始作起,由於它們提供了適當的常規功能的缺省實現。這些類能夠被子類化以支持特殊的定製需求。子類化model的過程在Create New Model部分討論
QT提供了一些現成的models用於處理數據項:
QStringListModel 用於存儲簡單的QString列表。
QStandardItemModel 管理複雜的樹型結構數據項,每項均可以包含任意數據。
QDirModel 提供本地文件系統中的文件與目錄信息。
QSqlQueryModel, QSqlTableModel,QSqlRelationTableModel用來訪問數據庫。
假如這些標準Model不知足你的須要,你應該子類化QAbstractItemModel,QAbstractListModel或是
QAbstractTableModel來定製。網絡
Views
不一樣的view都完整實現了各自的功能:QListView把數據顯示爲一個列表,QTableView把Model 中的數據以table的形式表現,QTreeView用具備層次結構的列表來顯示model中的數據。這些類都基於QAbstractItemView抽象基類,儘管這些類都是現成的,完整的進行了實現,但它們均可以用於子類化以便知足定製需求。
Delegates
QAbstractItemDelegate 是model/view架構中的用於delegate的抽象基類。缺省的delegate實如今QItemDelegate類中提供。它能夠用於Qt標準views的缺省 delegate.
排序
在model/view架構中,有兩種方法進行排序,選擇哪一種方法依賴於你的底層Model。
假如你的model是可排序的,也就是它從新實現了QAbstractItemModel::sort()函數,QTableView與QTreeView都提供了API,容許你以編程的方式對Model數據進行排序。另外,你也能夠進行交互方式下的排序(例如,容許用戶經過點擊view表頭的方式對數據進行排序),能夠這樣作:把QHeaderView::sectionClicked()信號與QTableView::sortByColum()槽或QTreeView::sortByColumn()槽進行聯結就行了。
另外一種方法是,假如你的model沒有提供須要的接口或是你想用list view表示數據,能夠用一個代理
model在用view表示數據以前對你的model數據結構進行轉換。
便利類
許多便利類都源於標準的view類,它們方便了那些使用Qt中基於項的view與table類,它們不該該被子類化,
它們只是爲Qt 3的等價類提供一個熟悉的接口。這些類有QListWidget,QTreeWidget,QTableWidget,它們提供瞭如Qt 3中的QListBox, QlistView,QTable類似的行爲。這些類比View類缺乏靈活性,不能用於任意的models,推介使用model/view的方法處理數據。
介紹
Qt提供了兩個標準的models:QStandardItemModel和QDirModel。QStandardItemModel是一個多用途的model,可用於表示list,table,tree views所須要的各類不一樣的數據結構。這個model也持有數據。QDirModel維護相關的目錄內容的信息,它自己不持有數據,僅是對本地文件系統中的文件與目錄的描述。QDirModel是一個現成的model,很容易進行配置以用於現存的數據,使用這個model,能夠很好地展現如何給一個現成的view設定model,研究如何用model indexes來操縱數據。
model與views的搭配使用
QListView與QTreeView很適合與QDirModel搭配。下面的例子在tree view與list view顯示了相同的信息,QDirModel提供了目錄內容數據。這兩個Views共享用戶選擇,所以每一個被選擇的項在每一個view中都會被高亮。
先裝配出一個QDirModel以供使用,再建立views去顯示目錄的內容。這給我展現了使用model的最簡單的方式。
model的建立與使用都在main()函數中完成:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QSplitter *splitter = new QSplitter;
QDirModel *model = newQDirModel;
//從缺省目錄建立數據
QTreeView *tree = new QTreeView(splitter);
tree->setModel(model);
tree->setRootIndex(model->index(QDir::currentPath()));
QListView *list = newQListView(splitter);
list->setModel(model);
list->setRootIndex(model->index(QDir::currentPath()));
//配置一個view去顯示model中的數據,只須要簡單地調用setModel(),並把目錄model做爲參數傳遞
//setRootIndex()告訴views顯示哪一個目錄的信息,這須要提供一個model index,而後用這個
//model index去model中去獲取數據
//index()這個函數是QDirModel特有的,經過把一個目錄作爲參數,獲得了須要的model index
//其餘的代碼只是窗口show出來,進入程序的事件循環就行了
splitter->setWindowTitle("Two views onto the same directory model");
splitter->show();
return app.exec();
}
上面的例子並無展現如何處理數據項的選擇,這包括不少細節,之後會提到。
Model類
基本概念
在model/view構架中,model爲view和delegates使用數據提供了標準接口。在Qt中,標準接口QAbstractItemModel類中被定義。無論數據在底層以何種數據結構存儲,QAabstractItemModel的子類會以層次結構的形式來表示數據,結構中包含了數據項表。咱們按這種約定來訪問model中的數據項,但這個約定不會對如何顯示這些數據有任何限制。數據發生改變時,model經過信號槽機制來通知關聯的views。
Model Indexes
爲了使數據存儲與數據訪問分開,引入了model index的概念。經過model index,能夠引用model中的數據項,Views和delegates都使用indexes來訪問數據項,而後再顯示出來。所以,只有model須要瞭解如何獲取數據,被model管理的數據類型能夠很是普遍地被定義。Model indexes包含一個指向建立它們的model的指針,這會在配合多個model工做時避免混亂。
QAbstractItemModel *model = index.model();
model indexes提供了對一項數據信息的臨時引用,經過它能夠訪問或是修改model中的數據。既然model有時會從新組織內部的數據結構,這時model indexes便會失效,所以不該該保存臨時的model indexes。假如須要一個對數據信息的長期的引用,那麼應該建立一個persistent model index。這個引用會保持更新。臨時的model indexes由QModelIndex提供,而具備持久能力的model indexes則由QPersistentModelIndex提供。在獲取對應一個數據項的model index時,須要考慮有關於model的三個屬性:行數,列數,父項的model index。
行與列
在最基本的形式中,一個model可做爲一個簡單的表來訪問,每一個數據項由行,列數來定位。這必不意味着
底層的數據用數組結構來存儲。行和列的使用僅僅是一種約定,它容許組件之間相互通信。能夠經過指定
model中的行列數來獲取任一項數據,能夠獲得與數據項一一對應的那個index。
QModelIndex index = model->index(row, column, ...);
Model爲簡單的,單級的數據結構如list與tables提供了接口,它們如上面代碼所顯示的那樣,再也不須要別的信息被提供。當咱們在獲取一個model index時,咱們須要提供另外的信息。
上圖表明一個基本的table model,它的每一項用一對行列數來定位。經過行列數,能夠獲取表明一個數據項的model index .
QModelIndex indexA = model->index(0, 0,QModelIndex());
QModelIndex indexB = model->index(1, 1,QModelIndex());
QModelIndex indexC = model->index(2, 1,QModelIndex());
一個model的頂級項,由QModelIndex()取得,它們上式被用做父項。
父項
相似於表的接口在搭配使用table或list view時理想的,這種行列系統與view顯示的方式是確切匹配的。
然則,像tree views這種結構須要model提供更爲靈活的接口來訪問數據項。每一個數據項多是別的項的
父項,上級的項能夠獲取下級項的列表。
當獲取model中數據項的index時,咱們必須指定關於數據項的父項的信息。在model外部,引用一個數據
項的惟一方法就是經過model index,所以須要在求取model index時指定父項的信息。
QModelIndex index = model->index(row, column, parent);
上圖中,A項和C項做爲model中頂層的兄弟項:
QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());
A有許多孩子,它的一個孩子B用如下代碼獲取:
QModelIndex indexB = model->index(1, 0, indexA);
項角色
model中的項能夠做爲各類角色來使用,這容許爲不一樣的環境提供不一樣的數據。舉例來講,Qt::DisplayRole被用於訪問一個字符串,它做爲文本會在view中顯示。典型地,每一個數據項均可覺得許多不一樣的角色提供數據,標準的角色在Qt::ItemDataRole中定義。咱們能夠經過指定model index與角色來獲取咱們須要的數據:
QVariant value = model->data(index, role);
角色指出了從model中引用哪一種類型的數據。views能夠用不一樣的形式顯示角色,所以爲每一個角色提供正確
的信息是很是重要的。經過爲每一個角色提供適當數據,model也爲views和delegates提供了暗示,如何正確地
把這些數據項顯給用戶。不一樣的views能夠自由地解析或忽略這些數據信息,對於特殊的場合,也能夠定義
一些附加的角色。
概念總結:
1,Model indexes爲views與delegages提供model中數據項定位的信息,它與底層的數據結構無關。
2,經過指定行,列數,父項的model index來引用數據項。
3,依照別的組件的要求,model indexes被model構建。
4,使用index()時,若是指定了有效的父項的model index,那麼返回獲得的model index對應於父項的某個孩子。
5,使用index()時,若是指定了無效的父項的model index,那麼返回獲得的model index對應於頂層項的某個孩子。
6, 角色對一個數據項包含的不一樣類型的數據給出了區分。
使用Model Indexes
QDirModel *model = new QDirModel;
QModelIndex parentIndex = model->index(QDir::currentPath());
int numRows = model->rowCount(parentIndex);
for (int row = 0; row < numRows; ++row)
{
QModelIndex index = model->index(row, 0, parentIndex);
tring text = model->data(index, Qt::DisplayRole).toString();
// Display the text in a widget.
}
以上的例子說明了從model中獲取數據的基本原則:
1,model的尺寸能夠從rowCount()與columnCount()中得出。這些函數一般都須要一個表示父項的model index。
2,model indexes用來從model中訪問數據項,數據項用行,列,父項model index定位。
3, 爲了訪問model頂層項,可使用QModelIndex()指定。
4, 數據項爲不一樣的角色提供不一樣的數據。爲了獲取數據,除了model index以外,還要指定角色。
建立新的Models
介紹
model/view組件之間功能的分離,容許建立model利用現成的views。這也可使用標準的功能 圖形用戶接口組件像QListView,QTableView和QTreeView來顯示來自各類數據源的數據爲。
QAbstractListModel類提供了很是靈活的接口,容許數據源以層次結構的形式來管理信息,也容許以某種
方式對數據進行插入、刪除、修改和存儲。它也提供了對拖拽操做的支持。
QAbstractListModel與QAbstractTableModel爲簡單的非層次結構的數據提供了接口,對於比較簡單的list和table models來講,這是不錯的一個開始點。
設計一個Model
當咱們爲存在的數據結構新建一個model時,首先要考慮的問題是應該選用哪一種model來爲這些數據提供接口。
假如數據結構能夠用數據項的列表或表來表示,那麼能夠考慮子類化QAbstractListModel或QAbstractTableModel
,既然這些類已經合理地對許多功能提供缺省實現。
然而,假如底層的數據結構只能表示成具備層次結構的樹型結構,那麼必須得子類化QAbstractItemModel。
不管底層的數據結構採起何種形式,在特定的model中實現標準的QAbstractItemModel API老是一個不錯的主意,這使得可使用更天然的方式對底層的數據結構進行訪問。這也使得用數據構建model 更爲容易,其餘
的model/view組件也可使用標準的API與之進行交互。
一個只讀model示例
這個示例實現了一個簡單的,非層次結構的,只讀的數據model,它基於QStringistModel類。它有一個QStringList做爲它內部的數據源,只實現了一些必要的接口。爲了簡單化,它子類化了QAbstractListModel,這個基類提供了合理的缺省行爲,對外提供了比QAbstractItemModel更爲簡單的接口。當咱們實現一個model時,不要忘了QAbstractItemModel自己不存儲任何數據,它僅僅提供了給views訪問
數據的接口。
class StringListModel : public QAbstractListModel
{
Q_OBJECT
public:
StringListModel(const QStringList &strings, QObject *parent = 0)
: QAbstractListModel(parent), stringList(strings) {}
introwCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
private:
QStringList stringList;
};
除了構造函數,咱們僅須要實現兩個函數:rowCount()返回model中的行數,data()返回與特定model index對應的數據項。具備良好行爲的model也會實現headerData(),它返回tree和table views須要的,在標題中顯示的數據。
由於這是一個非層次結構的model,咱們沒必要考慮父子關係。假如model具備層次結構,咱們也應該實現index()與parent()函數。
Model的尺寸
咱們認爲model中的行數與string list中的string數目一致:
int StringListModel::rowCount(const QModelIndex &parent) const
{
return stringList.count();
}
在缺省狀況下,從QAbstractListModel派生的model只具備一列,所以不須要實現columnCount()。
Model 標題與數據
QVariant StringListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= stringList.size())
return QVariant();
if (role == Qt::DisplayRole)
return stringList.at(index.row());
else
return QVariant();
}
QVariant StringListModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal)
return QString("Column %1").arg(section);
else
return QString("Row %1").arg(section);
}
一個數據項可能有多個角色,根據角色的不一樣輸出不一樣的數據。上例中,model中的數據項只有一個角色 ,
DisplayRole,然而咱們也能夠重用提供給DisplayRole的數據,做爲別的角色使用,如咱們能夠做爲ToolTipRole來用。
可編輯的model
上面咱們演示了一個只讀的model,它只用於向用戶顯示,對於許多程序來講,可編輯的list model可能更有用。咱們只須要給只讀的model提供另外兩個函數flags()與setData()的實現。下列函數聲明被添加到類定義中:
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole);
讓model可編輯
delegate會在建立編輯器以前檢查數據項是不是可編輯的。model必須得讓delegate知道它的數據項是可
編輯的。這能夠經過爲每個數據項返回一個正確的標記獲得,在本例中,咱們假設全部的數據項都是
可編輯可選擇的:
Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsEnabled;
returnQAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}
咱們沒必要知道delegate執行怎樣實際的編輯處理過程,咱們只需提供給delegate一個方法,delegate會使用它對model中的數據進行設置。這個特殊的函數就是setData():
bool StringListModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
stringList.replace(index.row(), value.toString());
emit dataChanged(index, index);
return true;
}
return false;
}
當數據被設置後,model必須得讓views知道一些數據發生了變化,這可經過發射一個dataChanged() 信號實現。
由於只有一個數據項發生了變化,所以在信號中說明的變化範圍只限於一個model index。
插入,刪除行
在model中改變行數與列數是可能的。固然在本列中,只考慮行的狀況,咱們只須要從新實現插入、刪除
的函數就能夠了,下面應在類定義中聲明:
bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex());
bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex());
既然model中的每行對應於列表中的一個string,所以,insertRows()函數在string list 中指定位置插入一個空string,
父index一般用於決定model中行列的位置,本例中只有一個單獨的頂級項,困此只須要在list中插入空string。
bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)
{
beginInsertRows(QModelIndex(), position, position+rows-1);
for (int row = 0; row < rows; ++row) {
stringList.insert(position, "");
}
endInsertRows();
return true;
}
beginInsertRows()通知其餘組件行數將會改變。endInsertRows()對操做進行確認與通知。
返回true表示成功。
刪除操做與插入操做相似:
bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent)
{
beginRemoveRows(QModelIndex(), position, position+rows-1);
for (int row = 0; row < rows; ++row) {
stringList.removeAt(position);
}
endRemoveRows();
return true;
}
View 類
概念
在model/view架構中,view從model中得到數據項而後顯示給用戶。數據顯示的方式沒必要與model提供的表示方式相同,能夠與底層存儲數據項的數據結構徹底不一樣。
內容與顯式的分離是經過由QAbstractItemModel提供的標準模型接口,由QAsbstractItemview提供的標準視圖接口共同實現的。廣泛使用model index來表示數據項。view負責管理從model中讀取的數據的外觀佈局。
它們本身能夠去渲染每一個數據項,也能夠利用delegate來既處理渲染又進行編輯。
除了顯示數據,views也處理數據項的導航,參與有關於數據項選擇的部分功能。view也實現一些基本的用戶接口特性,如上下文菜單與拖拽功能。view也爲數據項提供了缺省的編程功能,也可搭配delegate實現更爲特殊的定製編輯的需求。
一個view建立時必不須要model,但在它能顯示一些真正有用的信息以前,必須提供一個model。view經過使用
selections來跟蹤用戶選擇的數據項。每一個view能夠維護單獨使用的selections,也能夠在多個views之間共享。有些views,如QTableView和QTreeView,除數據項以外也可顯示標題(Headers),標題部分經過一個view來實現,QHeaderView。標題與view同樣老是從相同的model中獲取數據。從 model中獲取數據的函數是QabstractItemModel::headerDate(),通常老是以表單的形式中顯示標題信息。能夠從QHeaderView子類化,以實現更爲複雜的定製化需求。
使用現成的view
Qt提供了三個現成的view 類,它們可以以用戶熟悉的方式顯示model中的數據。QListView把model中的數據項以一個簡單的列表的形式顯示,或是以經典的圖標視圖的形式顯示。QTreeView把model中的數據項做爲具備層次結構的列表的形式顯示,它容許以緊湊的深度嵌套的結構進行顯示。QTableView倒是把model中的數據項以表格的形式展示,更像是一個電子表格應用程序的外觀佈局。
以上這些標準view的行爲足以應付大多數的應用程序,它們也提供了一些基本的編輯功能,也能夠定製特殊的需求。
使用model
之前的例子中建立過一個string list model,能夠給它設置一些數據,再建立一個view把model中的內容展現出來:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// Unindented for quoting purposes:
QStringList numbers;
numbers << "One" << "Two" << "Three" << "Four" << "Five";
QAbstractItemModel *model = newStringListModel(numbers);
//要注意的是,這裏把StringListModel做爲一個QAbstractItemModel來使用。這樣咱們就能夠
//使用model中的抽象接口,並且若是未來咱們用別的model代替了當前這個model,這些代碼也會照樣工做。
//QListView提供的列表視圖足以知足當前這個model的須要了。
QListView *view = new QListView;
view->setModel(model);
view->show();
return app.exec();
}
view會渲染model中的內容,經過model的接口來訪問它的數據。當用戶試圖編輯數據項時,view會使用缺省的delegate來提供一個編輯構件。
一個model,多個views
爲多個views提供相同的model是很是簡單的事情,只要爲每一個view設置相同的model。
QTableView *firstTableView = new QTableView;
QTableView *secondTableView = new QTableView;
firstTableView->setModel(model);
secondTableView->setModel(model);
在model/view架構中信號、槽機制的使用意味着model中發生的改變會傳遞中聯結的全部view中,這保證了
無論咱們使用哪一個view,訪問的都是一樣的一份數據。
上面的圖展現了一個model上的兩個不一樣的views,儘管在不一樣的view中顯示的model中的數據是一致的,每一個
view都維護它們本身的內部選擇模型,但有時候在某些狀況下,共享一個選擇模型也是合理的。
處理數據項的選擇
view中數據項選擇機制由QItemSelectionModel類提供。全部標準的view缺省都構建它們本身的選擇模型,
以標準的方式與它們交互。選擇模型能夠用selectionModel()函數取得,替代的選擇模型也能夠經過
setSelectionModel()來設置。當咱們想在一個model上提供多個一致的views時,這種對選擇模型的控制能力很是有用。一般來說,除非你子類化一個model或view,你沒必要直接操縱selections的內容。
多個views之間共享選擇
接着上邊的例子,咱們能夠這樣:
secondTableView->setSelectionModel(firstTableView->selectionModel());
如今全部views都在一樣的選擇模型上操做,數據與選擇項都保持同步。
上面的例子中,兩個view的類型是相同的,假如這兩個view類型不一樣,那麼所選擇的數據項在每一個view
中的表現形式會有很大的不一樣。例如,在一個table view中一個連續的選擇,在一個tree view中表現出
來的可能會是幾個高亮的數據項片段的組合。
在views中選擇數據項
概念
用於新的view類中的選擇模型比Qt3中的模型有了很大的改進。它爲基於model/view架構的選擇提供了更爲全面的描述。儘管對提供了的views來講,負責操縱選擇的標準類已經足以應付,可是你也能夠建立特定的選擇模型來知足你特殊的需求。
關於在view被選擇的數據項的信息保持在QItemSelectionModel類的實例中。它也爲每一個獨立的model中的數據項維護model indexes信息,與任何views都關聯關係。既然一個model可用於多個views,那麼在多個views之間共享選擇信息也是能夠作到的,這使得多個views能夠以一致的方式進行顯示。
選擇由多個選擇範圍組成。經過僅僅記錄開始model indexes與結束model indexes,最大化地記錄了能夠選擇的範圍。非連續選擇數據項由多個選擇範圍來描述。選擇模型記錄model indexes的集合來描述一個選擇。最近選擇的數據項被稱爲currentselection。應用程序能夠經過使用某種類型的選擇命令來修改選擇的效果。
在進行選擇操做時,能夠把QItemSelectionModel當作是model中全部數據項選擇狀態的一個記錄。一旦創建一個選擇模型,全部項的集合均可以選擇,撤消選擇,或者選擇狀態進行切換而不須要知道哪一個數據項是否已經被選擇過。全部被選擇的項的indexes在任什麼時候候均可以獲得,經過信號槽機制能夠通知別的組件發生的變化。
使用選擇模型
標準view類提供了缺省的選擇模型,它們能夠在大次數程序中使用。一個view中的選擇模型能夠經過調用view的函數selectionModel()取得,也能夠經過setSelectionModel()在多個views之間共享選擇模型,所以總的來講構建一個新的模型通常狀況不太必要。
經過給QItemSelection指定一個model,一對model indexes,能夠建立一個選擇。indexes的用法依賴於給定的model,這兩個indexes被解釋成選擇的區塊中的左上角項和右下角項。model中的項的選擇服從於選擇模型。
選擇項
構建一個table model ,它有32個項,用一個table view進行顯示:
TableModel *model = new TableModel(8, 4, &app);
QTableView *table = new QTableView(0);
table->setModel(model);
QItemSelectionModel *selectionModel = table->selectionModel();
QModelIndex topLeft;
QModelIndex bottomRight;
topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(5, 2, QModelIndex());
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);
結果以下:
讀取選擇狀態
存儲在選擇模型中indexes能夠用selectionIndexes()函數來讀取。它返回一個未排序的model indexes列表,咱們能夠遍歷它,若是咱們知道他們關聯於哪一個model的話。
QModelIndexList indexes = selectionModel->selectedIndexes();
QModelIndex index;
foreach(index, indexes) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
model->setData(index, text);
}
選擇模型在選擇發生變化時會發出信號。這用於通知別的組件包括總體與當前焦點項所發生的變化。咱們能夠鏈接selectionChanged()信號到一個槽,檢查當信號產生時哪些項被選擇或被取消選擇。這個槽被調用時帶有兩個參數,它們都是QItemSelection對象,一個包含新被選擇的項,另外一個包含新近被取消選擇的項。下面的代碼演示了給新選擇的項添加數據內容,新近被取消選擇的項的內容被清空。
void MainWindow::updateSelection(const QItemSelection &selected,
const QItemSelection &deselected)
{
QModelIndex index;
QModelIndexList items = selected.indexes();
foreach (index, items) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
model->setData(index, text);
}
items = deselected.indexes();
foreach (index, items)
model->setData(index, "");
}
也能夠經過響應currentChanged()信號來跟蹤當前焦點項.對應的槽就有兩個接收參數,一個表示以前的焦點,另外一個表示當前的焦點。
void MainWindow::changeCurrent(const QModelIndex ¤t,
const QModelIndex &previous)
{
statusBar()->showMessage(
tr("Moved from (%1,%2) to (%3,%4)")
.arg(previous.row()).arg(previous.column())
.arg(current.row()).arg(current.column()));
}
更新選擇
選擇指令是經過選擇標誌提供的,它被定義在QItemSelectionModel::SelectionFlag中。經常使用的有Select標記,Toggle標記,Deselect標記,Current標記,Clear標記,其意義一目瞭然。沿上面例子的結果執行如下代碼:
QItemSelection toggleSelection;
topLeft = model->index(2, 1, QModelIndex());
bottomRight = model->index(7, 3, QModelIndex());
toggleSelection.select(topLeft, bottomRight);
selectionModel->select(toggleSelection, QItemSelectionModel::Toggle);
結果以下:
缺省狀況下,選擇指令只針對單個項(由model indexes指定)。然而,選擇指令能夠經過與另外標記的結合來改變整行和整列。舉例來講,假如你只使用一個index來調用select(),可是用Select標記與Rows標記的組合,那麼包括那個項的整行都將被選擇。看如下示例:
QItemSelection columnSelection;
topLeft = model->index(0, 1, QModelIndex());
bottomRight = model->index(0, 2, QModelIndex());
columnSelection.select(topLeft, bottomRight);
selectionModel->select(columnSelection,
QItemSelectionModel::Select | QItemSelectionModel::Columns);
QItemSelection rowSelection;
topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(1, 0, QModelIndex());
rowSelection.select(topLeft, bottomRight);
selectionModel->select(rowSelection,
QItemSelectionModel::Select | QItemSelectionModel::Rows);
結果以下
選擇模型中全部項
爲了選擇model中的全部項,必須先得建立一個選擇,它包括當前層次上的全部項:
QModelIndex topLeft = model->index(0, 0, parent);
QModelIndex bottomRight = model->index(model->rowCount(parent)-1,
model->columnCount(parent)-1, parent);
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);
頂級index能夠這樣:
QModelIndex parent = QModelIndex();
對具備層次結構的model來講,可使用hasChildren()函數來決定給定項是不是其它項的父項。
Delegate 類
概念
與MVC模式不一樣,model/view結構沒有用於與用戶交互的徹底獨立的組件。通常來說, view負責把數據展現給用戶,也處理用戶的輸入。爲了得到更多的靈性性,交互經過delegagte執行。它既提供輸入功能又負責渲染view中的每一個數據項。 控制delegates的標準接口在QAbstractItemDelegate類中定義。Delegates經過實現paint()和sizeHint()以達到渲染內容的目的。然而,簡單的基於widget的delegates,能夠從QItemDelegate子類化,而不是QAbstractItemDelegate,這樣可使用它提供的上述函數的缺省實現。delegate可使用widget來處理編輯過程,也能夠直接對事件進行處理。
使用現成的delegate
Qt提供的標準views都使用QItemDelegate的實例來提供編輯功能。它以普通的風格來爲每一個標準view渲染數據項。這些標準的views包括:QListView,QTableView,QTreeView。全部標準的角色都經過標準views包含的缺省delegate進行處理。一個view使用的delegate能夠用itemDelegate()函數取得,而setItemDelegate() 函數能夠安裝一個定製delegate。
一個簡單的delegate
這個delegate使用QSpinBox來提供編輯功能。它主要想用於顯示整數的models上。儘管咱們已經創建了一個基於整數的table model,但咱們也可使用QStandardItemModel,由於delegate能夠控制數據的錄入。咱們又建了一個table view來顯示model的內容,用咱們定製的delegate來編輯。
咱們從QItemDelegate子類化,這樣能夠利用它缺省實現的顯示功能。固然咱們必需提供函數來管理用於編輯的widget:
class SpinBoxDelegate : public QItemDelegate
{
Q_OBJECT
public:
SpinBoxDelegate(QObject *parent = 0);
QWidget *createEditor(QWidget *parent, constQStyleOptionViewItem &option,
const QModelIndex &index) const;
voidsetEditorData(QWidget *editor, constQModelIndex &index) const;
void setModelData(QWidget *editor,QAbstractItemModel *model,
const QModelIndex &index) const;
voidupdateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, constQModelIndex &index) const;
};
須要注意的是,當一個delegate建立時,不須要安裝一個widget,只有在真正須要時才建立這個用於編輯的widget。
提供編輯器
在這個例子中,當table view須要提供一個編輯器時,它要求delegate提供一個可用於編輯的widget,它應該適用於當前正被修改的數據項。這正是createEditor()函數應該實現的:
QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &/* option */,
const QModelIndex &/* index */) const
{
QSpinBox *editor = new QSpinBox(parent);
editor->setMinimum(0);
editor->setMaximum(100);
return editor;
}
咱們不須要跟蹤這個widget的指針,由於view會在不須要時銷燬這個widget。咱們也給編輯安裝了delegate缺省的事件過濾器,這提供了用戶指望的標準編輯快捷鍵。view經過咱們定義相應的函數來保證編輯器的數據與幾何佈局被正確的設置。咱們也能夠根據不一樣的model index來建立不一樣的編輯器,好比,咱們有一列整數,一列字符串,咱們能夠根據哪一種列被編輯來建立一個QSpinBox或是QLineEdit。delegate必需提供一個函數把model中的數據拷貝到編輯器中。
void SpinBoxDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
int value = index.model()->data(index, Qt::DisplayRole).toInt();
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
spinBox->setValue(value);
}
向model提交數據
這須要咱們實現另一個函數setModelData():
void SpinBoxDelegate::setModelData(QWidget *editor,QAbstractItemModel *model,
const QModelIndex &index) const
{
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
spinBox->interpretText();
int value = spinBox->value();
model->setData(index, value);
}
標準的QItemDelegate類當它完成編輯時會發射closeEditor()信號來通知view。view保證編輯器widget關閉與銷燬。本例中咱們只提供簡單的編輯功能,所以不須要發送個信號。
更新編輯器幾何佈局
最近在看Qt的Model/View Framework,在網上搜了搜,好像中文的除了幾篇翻譯沒有什麼有價值的文章。E文的除了Qt的官方介紹,其它文章也不多。看到一個老外在blog中寫道Model/View是他認爲Qt中最很差的一部分了。真的是這樣嗎?爲了回饋開源社區,我寫了這篇blog,寫的是我認爲比較有價值的東東。題目起得是解析,但也沒有特別細節的介紹,點到爲止,有興趣的Tx能夠繼續討論。我所看的資料有《C++ GUI Programming with Qt 4, Second Edition》、Qt官網和Qt源代碼。
在UI中,最經常使用的就是list/grid/tree了(在Qt中,grid被稱爲table)。尤爲是作那些數據庫相關的程序,可能每一個界面都要用到list或grid。在Qt中,它們被歸爲item view class。有兩種實現,一種叫item based,這些類名以widget結尾,如QListWidget等。另外一種叫model based,這些類以view結尾,如QListView等。
item based widget使用起來很簡單,與MFC中的CListCtrl/CTreeCtrl等差很少。這些類既是數據容器(data container),又負責顯示(presentation),還要處理與用戶的交互(user action)。但使用這些類的缺點就是在處理大量數據顯示時顯得力不從心。我記得使用CListCtrl的report mode顯示超過5萬條記錄時須要等待很長時間。由於它要把全部的數據都拷貝進來,再顯示,好比這個list一次只能顯示50條記錄,但它把剩下的49950條也拷貝進了內存,併爲每條記錄的顯示都加上格式等額外的信息。我想這種實現對於專業的軟件來講是沒法接受的。那麼怎麼提升它們的性能呢?有些人想出了virtual list/grid。它們顯示以前並不須要把全部的數據都裝進來,若有5萬條記錄,剛開始時它顯示前50條,顯示哪條時去取哪條。也就是說後49950條它無論,當用戶移動滾動條或上下鍵改變顯示內容時,它會根據一系列參數計算出目前須要顯示哪幾條。如1000-1050條,那麼再去取這幾條數據顯示。因爲實現起來有必定的難度,因此不少MFC實現的virtual list/grid顯示出來都是「素顏」的(能快速顯示出來已經不錯了)。固然,coding無止境,這種取數據的方式咱們稱爲delayed fetch,若是實現很差的化,當電腦速度慢且用戶急速拖滾動條時也有可能出現白屏、拖尾、模糊等狀況。因此還能夠作prefetch和caching來盡力避免這些狀況。好了,這裏鋪墊的已經夠多了,下面咱們直奔主題。
model based view就是咱們今天要重點「解析」的。對應的UI類有QListView/QTreeView/QTableView。那麼這些view與上面的widget有什麼區別呢?名字就是最大的區別。widget原本就是指小東東,而view但是很「大氣」的視圖哦。那麼這些view大哥是怎麼顯示的呢?下面就說說我不肯多說但又不得不說的一個設計模式:MVC (Model/View/Controller),model表明數據(data set),view表明顯示(presentation),controller處理與用戶交互(user action)。MVC模式源自smalltalk,如今好像被用的不少了。它的做用實際上是把上面介紹的相似CListCtrl這樣的類解放出來,由於它們要幹不少事情,太累了。還有就是對於有些程序員來講,實現一個功能複雜的類可能會力不從心。分紅3個類每一個類解決不一樣的問題可能更好實現。從軟件工程的角度上講,這種方式的decoupling能夠下降系統風險。好了,關於MVC,你們想了解更多的話能夠繼續在網上搜索。
模式是MVC,那麼Qt是怎麼實現的呢?我打開了Qt的官網介紹(http://qt.nokia.com/doc/4.6/model-view-programming.html)。我暈,看到了幾十個類,並且不少都有繼承關係。很容易讓人迷失啊。不過沒關係,通過2天的學習,我對它們作了總結,見下圖。Tx們能夠從我這幅圖開始,抓住該framework的主線,省得迷失在茫茫淚(類)海:-)
通常來講,Model裏面並無真正存儲數據(數據少的話也能夠直接存儲在Model裏),它的數據是從真正的「肉(raw)」裏取得,如一個disk file,或database的query result set等等。那麼這個model到底是幹什麼用的呢?說白了吧,它就是負責將「肉」數據獲取並提供給view,而後將view所作的對「肉」數據的修改更新至真正的「肉」中。因此,讀寫文件、操做數據庫、網絡通信等一系列與數據打交道的工做就在model中作了。有的時候「肉」可能真的很肥,因此model還有一項重要的工做就是把這些「肉」編號。這樣就出現了Model Index這個很是重要的類。通常來講,它使用一個2維的編號(row/colum)來對「肉」編號。但對於tree這種有層次結構的數據來講,又加上一個parent index做爲第3個編號。即一個父親下面的葉子也是從0,0開始編號,獲取model index的時候用遞歸來實現。OK,如今model已經有了一堆編好號碼的「肉」了,誰來買啊?「肉」便宜了。。。。。。
View適時出現,注意,不少view能夠同時來買同一塊「肉」。(汗,不開玩笑了,這篇blog快水了)。當view須要顯示某些數據時,它們經過model index從model中獲取數據(調用model的data函數,當model的data變化時,它也會自動發dataChanged signal給全部的view以便它們更新)。固然,在view中也能夠調用model的setData函數來設定某個model index所對應的數據。這裏要說明一下model中的數據,用QVarient來承載,能夠是全部Qt支持的類型,比較貼心的是,數據能夠分紅多個角色(role),例如Qt::DisplayRole專用於顯示,Qt::BackgroundRole用於顯示背景色等等。因此在model中,你不光能夠對「肉」進行編號,還能夠對「肉」進行「深加工」,使它們更「好看」或是更「美味」。View組織這些數據並顯示,但卻沒有作真正的顯示工做,真正的工做留給了delegate。
Delegate就是MVC中的C。view讓它顯示時它就在paint函數中顯示。固然,你能夠重載這個函數並實現你本身的顯示。你還能夠給一個view設定row delegate和colum delegate專用於row和colum。當用戶觸發了view的edit trigger時(如雙擊鼠標或回車),view開始in place edit (beginEditing)。Delegate會在合適的地方建立一個合適的widget(如line edit或combo box等)處理用戶的輸入,用戶輸入完成之後delegate獲取用戶的輸入並返回。這些輸入能夠經過調用model的setData函數保存到真正的「肉」中。因此Delegate其實就是負責最終顯示數據和處理用戶交互的。
既然由用戶交互,最重要的確定是用戶的選擇了。說一下selection model。View將用戶選擇的item index所有存入selection model中,顯示的時候根據selection model的內容顯示。另外,多個view能夠共享同一個selection model,這樣,當你選中其中的一個時,另外一個view中的相應item也會被選中。
最後總結一下,Smalltalk怎麼實現的MVC我不清楚,可是Qt model/view framework對MVC的實現徹底是基於C++的虛基類和虛函數特性。MVC各部分之間的聯通除了應用signal/slot以外,就全是虛函數了。Tx們若是有興趣的話,能夠看看model/view framework源代碼中那些QAbstract開頭的類,它們定義了統一的接口虛函數後,派生類只須要從新實現這些函數便可。這裏再重複一下學習這部分時Tx們應該關注的2條主線。
不管什麼view,做用都是把數據顯示出來,那麼第一條主線是數據怎樣從數據源顯示到view中。
顯示的數據可能被修改,因此第二條主線就是修改後的數據怎樣再更新至數據源中。若是你能把這2幅sequence map畫出來,相信你就已經徹底明白了。
我不知道Qt在這裏的設計是否是作了太多的decoupling。反正靈活性是足夠了(model/view/delegate均可以繼承),但沒法讓程序員快速掌握。雖然Qt已經弱化了delegate並並且爲view提供默認的delegate。但在作具體項目時,程序員大部分時候仍是要根據具體狀況重寫派生類數的。這對於那些想快速作界面並顯示的程序員來講,可能會難以接受。總之,這個輪子作的夠精細,但通常在使用以前你還要本身擰點螺絲,上點油。
http://blog.csdn.net/leo115/article/details/7532677
http://blog.csdn.net/leo115/article/details/7532682