Qt Model/View/Delegate 機制學習

Qt Model/View 學習筆記 (一)

介紹

Qt 4推出了一組新的item view類,它們使用model/view結構來管理數據與表示層的關係。這種結構帶來的
功能上的分離給了開發人員更大的彈性來定製數據項的表示,它也提供一個標準的model接口,使得更多的
數據源可以被這些item view使用。這裏對model/view的結構進行了描述,結構中的每個組件都進行了解釋,
給出了一些例子說明了提供的這些類如何使用。

Model/View  結構

Model-View-Controller(MVC), 是從Smalltalk發展而來的一種設計模式,常被用於構建用戶界面。經典設計模式的著作中有這樣的描述:

MVC 由三種對象組成。Model是應用程序對象,View是它的屏幕表示,Controller定義了用戶界面如何對用戶輸入進行響應。在MVC之前,用戶界面設計傾向於三者揉合在一起,MVC對它們進行了解耦,提高了靈活性與重用性。

假如把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  提供本地文件系統中的文件與目錄信息。
QSqlQueryModelQSqlTableModel,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()函數,QTableViewQTreeView都提供了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 Model/View 學習筆記 (二)

介紹
Qt提供了兩個標準的models:QStandardItemModelQDirModelQStandardItemModel是一個多用途的
model,可用於表示list,table,tree views所需要的各種不同的數據結構。這個model也持有數據。QDirModel
維護相關的目錄內容的信息,它本身不持有數據,僅是對本地文件系統中的文件與目錄的描述。
QDirModel是一個現成的model,很容易進行配置以用於現存的數據,使用這個model,可以很好地展示如何
給一個現成的view設定model,研究如何用model indexes來操縱數據。

model與views的搭配使用

QListViewQTreeView很適合與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 = new QDirModel;
    //從缺省目錄創建數據
     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()));
     //配置一個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();
 }

Qt Model/View 學習筆記 (三)


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之外,還要指定角色。

 

Qt Model/View 學習筆記 (四)


創建新的Models

介紹
model/view組件之間功能的分離,允許創建model利用現成的views。這也可以使用標準的功能 圖形用戶接口組件像QListView,QTableView和QTreeView來顯示來自各種數據源的數據爲。
QAbstractListModel類提供了非常靈活的接口,允許數據源以層次結構的形式來管理信息,也允許以某種
方式對數據進行插入、刪除、修改和存儲。它也提供了對拖拽操作的支持。
QAbstractListModelQAbstractTableModel爲簡單的非層次結構的數據提供了接口,對於比較簡單的list和table models來說,這是不錯的一個開始點。

設計一個Model
當我們爲存在的數據結構新建一個model時,首先要考慮的問題是應該選用哪種model來爲這些數據提供接口。
假如數據結構可以用數據項的列表或表來表示,那麼可以考慮子類化QAbstractListModelQAbstractTableModel
,既然這些類已經合理地對許多功能提供缺省實現。
然而,假如底層的數據結構只能表示成具有層次結構的樹型結構,那麼必須得子類化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) {}

     int rowCount(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;

     return QAbstractItemModel::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;
 }

Qt Model/View 學習筆記 (五)

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,如QTableViewQTreeView,除數據項之外也可顯示標題(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 = new StringListModel(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中表現出
來的可能會是幾個高亮的數據項片斷的組合。

Qt Model/View 學習筆記 (六)

在views中選擇數據項

概念

用於新的view類中的選擇模型比Qt3中的模型有了很大的改進。它爲基於model/view架構的選擇提供了更爲全面的描述。儘管對提供了的views來說,負責操縱選擇的標準類已經足以應付,但是你也可以創建特定的選擇模型來滿足你特殊的需求。
關於在view被選擇的數據項的信息保持在QItemSelectionModel類的實例中。它也爲每個獨立的model中的數據項維護model indexes信息,與任何views都關聯關係。既然一個model可用於多個views,那麼在多個views之間共享選擇信息也是可以做到的,這使得多個views可以以一致的方式進行顯示。
選擇由多個選擇範圍組成。通過僅僅記錄開始model indexes與結束model indexes,最大化地記錄了可以選擇的範圍。非連續選擇數據項由多個選擇範圍來描述。選擇模型記錄model indexes的集合來描述一個選擇。最近選擇的數據項被稱爲current selection。應用程序可以通過使用某種類型的選擇命令來修改選擇的效果。
在進行選擇操作時,可以把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 &current,
     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()函數來決定給定項是否是其它項的父項。

Qt Model/View 學習筆記(七) 

Qt Model/View 學習筆記 2010-06-28 12:29:10 閱讀28 評論0   字號: 訂閱

Qt Model/View 學習筆記 

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, const QStyleOptionViewItem &option,
                           const QModelIndex &index) const;

     void setEditorData(QWidget *editor, const QModelIndex &index) const;
     void setModelData(QWidget *editor, QAbstractItemModel *model,
                       const QModelIndex &index) const;

     void updateEditorGeometry(QWidget *editor,
         const QStyleOptionViewItem &option, const QModelIndex &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關閉與銷燬。本例中我們只提供簡單的編輯功能,因此不需要發送個信號。

更新編輯器幾何佈局

delegate負責管理編輯器的幾何佈局。這些幾何佈局信息在編輯創建時或view的尺寸位置發生改變時,
都應當被提供。幸運的是,view通過一個view option可以提供這些必要的信息。
 void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
     const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
 {
     editor->setGeometry(option.rect);
 }
編輯提示
編輯完成後,delegate會給別的組件提供有關於編輯處理結果的提示,也提供用於後續編輯操作的一些提示。
這可以通過發射帶有某種hint的closeEditor()信號完成。這些信號會被安裝在spin box上的缺省的QItemDelegate事件過濾器捕獲。對這個缺省的事件過濾來講,當用戶按下回車鍵,delegate會對model中的數據進行提交,並關閉spin box。
我們可以安裝自己的事件過濾器以迎合我們的需要,例如,我們可以發射帶有EditNextItem hint的
closeEditor()信號來實現自動開始編輯view中的下一項。


if ($ != jQuery) { $ = jQuery.noConflict(); }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章