#(47):視圖選擇
選擇是視圖中常用的一個操作。在列表、樹或者表格中,通過鼠標點擊可以選中某一項,被選中項會變成高亮或者反色。在 Qt 中,選擇也是使用了一種模型。在 model/view 架構中,這種選擇模型提供了一種更通用的對選擇操作的描述。對于一般應用而言,Qt 內置的選擇模型已經足夠,但是,Qt 還是允許你創建自己的選擇模型,來實現一些特殊的操作。
Qt 使用`QItemSelectionModel`類獲取視圖中項目的選擇情況。這個模型保持有項目的索引,并且獨立于任何視圖。這意味著,我們可以讓不同的視圖共享同一個選擇模型,從來達到一種同步操作的目的。選擇由選擇區域組成。模型只將選區的開始和結束的索引位置記錄下來,以保證對于很大的選區也有很好的性能。非連續選區則由多個連續選擇組成。
選擇會直接應用于選擇模型所維護的那些被選中的索引上面。最新的選擇就是當前選擇。這意味著,即便界面上沒有顯示有任何項目被選擇,如果通過某些命令對選區進行操作,同樣會有作用。
在視圖中,始終存在一個當前項和被選擇項(即便從界面上看不到有任何選擇)。與通常所想的不同,當前項和選擇項是相互獨立的兩個狀態。一個項目可以即是當前項又是選擇項。下表是當前項和選擇項的區別:
| 當前項 | 選擇項 |
| --- | --- |
| 只能有一個當前項。 | 可以有多個選擇項。 |
| 使用鍵盤或者鼠標點擊可以改變當前項。 | 選擇項使用兩種狀態:選擇和未選擇,這取決于項目之前的狀態和其它一些設置,例如,單選或多選。只有在用戶進行交互的時候,這種狀態才會發生改變。 |
| 當前項可以使用 F2 或者鼠標雙擊進行編輯(前提是程序允許)。 | 當前項可以結合另外一個錨點指定被選擇或者去除選擇的一塊選區(或二者的結合)。 |
| 當前項通常會有一個焦點框進行標識。 | 選擇項使用選區顏色進行標識。 |
在處理選擇的時候,我們可以將`QItemSelectionModel`當成數據模型中所有數據項的選擇狀態的一個記錄。一旦選擇模型創建好,這些數據項就可以在不知道哪些項被選擇的情況下進行選擇、取消選擇或者改變選擇狀態的操作。所有被選擇項的索引都在可隨時更改,其它組件也可以通過信號槽機制修改這些選擇的信息。
標準視圖類(`QListView`、`QTreeView`以及`QTableView`)已經提供了默認的選擇模型,足以滿足大多數應用程序的需求。某一個視圖的選擇模型可以通過`selectionModel()`函數獲取,然后使用`setSelectionModel()`提供給其它視圖共享,因此,一般沒有必要新建選擇模型。
如果需要創建一個選區,我們需要指定一個模型以及一對索引,使用這些數據創建一個`QItemSelection`對象。這兩個索引應該指向給定的模型中的數據,并且作為一個塊狀選區的左上角和右下角的索引。為了將選區應用到模型上,需要將選區提交到選擇模型。這種操作有多種實現,對于現有選擇模型有著不同的影響。
下面我們來看一些代碼片段。首選構建一個總數 32 個數據項的表格模型,然后將其設置為一個表格視圖的數據:
~~~
QTableWidget tableWidget(8, 4);
QItemSelectionModel *selectionModel = tableWidget.selectionModel();
~~~
在代碼的最后,我們獲得`QTableView`的選擇模型,以備以后使用。現在,我們沒有修改模型中的數據,而是選擇表格左上角的一些單元格。下面我們來看看代碼如何實現:
~~~
QModelIndex topLeft = tableWidget.model()->index(0, 0, QModelIndex());
QModelIndex bottomRight = tableWidget.model()->index(5, 2, QModelIndex());
~~~
接下來,我們將獲得的兩個索引定義為選區。為達這一目的,我們首先構造一個`QItemSelection`對象,然后將其賦值給我們獲取的選擇模型:
~~~
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);
~~~
正如前面我們說的,首先利用左上角和右下角的坐標構建一個`QItemSelection`對象,然后將這個對象設置為選擇模型的選擇區。`select()`函數的第一個參數就是需要選擇的選區,第二個參數是選區的標志位。Qt 提供了很多不同的操作,可以參考下`QItemSelectionModel::SelectionFlags`的文檔。在本例中,我們使用了`QItemSelectionModel::Select`,這意味著選區中所包含的所有單元格都會被選擇。
下面就是我們的運行結果:
[](http://files.devbean.net/images/2013/03/view-selection-demo.png)
現在我們知道如何設置選區。下面來看看如何獲取選區。獲取選區需要使用`selectedIndexes()`函數。該函數返回一個無序列表。我們可以通過遍歷這個列表獲得哪些被選擇:
~~~
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()`信號,在選區改變時檢查哪個項目發生了變化。這個信號有兩個參數:第一個是新選擇的項目,第二個是剛剛被取消選擇的項目。在下面的示例中,我們通過`selectionChanged()`信號,將所有新選擇的項目填充字符串,將所有被取消選擇的部分清空:
~~~
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()`,我們可以追蹤當前有焦點的項。同`selectionChanged()`信號類似,這個信號也有兩個參數:第一個是新的當前項,第二個是上一個當前項。下面的代碼則是該信號的使用:
~~~
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()`函數中使用過的第二個參數嗎?當我們替換這個參數,就可以獲得不同的組合方式。最常用的就是`QItemSelectionModel::Select`,它的作用是將所有指定的選區都選擇上。`QItemSelectionModel::Toggle`則是一種取反的操作:如果指定的部分原來已經被選擇,則取消選擇,否則則選擇上。`QItemSelectionModel::Deselect`則是取消指定的已選擇的部分。在下面的例子中,我們使用`QItemSelectionModel::Toggle`對前面的示例作進一步的操作:
~~~
QItemSelection toggleSelection;
topLeft = tableWidget.model()->index(2, 1, QModelIndex());
bottomRight = tableWidget.model()->index(7, 3, QModelIndex());
toggleSelection.select(topLeft, bottomRight);
selectionModel->select(toggleSelection, QItemSelectionModel::Toggle);
~~~
運行結果將如下所示:

默認情況下,選擇操作會只會影響到指定的模型索引。但是,我們也可以改變這一設置。例如,只選擇整行或者整列:
~~~
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);
~~~
上面的代碼,我們依然使用兩個索引設置了一個區域,但是,在選擇的使用我們使用了`QItemSelectionModel::Rows`和`QItemSelectionModel::Columns`這兩個參數,因此只會選擇這兩個區域中指定的行或者列:
[](http://files.devbean.net/images/2013/03/view-selection-flags.png)
使用`QItemSelectionModel::Current`參數可以將當前選區替換為新的選區;使用`QItemSelectionModel::Clear`則會將原來已有的選區全部取消。為了進行全選,我們可以設置選區為左上角和右下角兩個索引:
~~~
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);
~~~
- (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(續)