前面咱們介紹了模型的概念。下面則是另一個基本元素:視圖。在 model/view 架構中,視圖是數據從模型到最終用戶的途徑。數據經過視圖向用戶進行顯示。此時,這種顯示方式沒必要須同模型的存儲結構相一致。實際上,不少狀況下,數據的顯示同底層數據的存儲是徹底不一樣的。架構
咱們使用QAbstractItemModel
提供標準的模型接口,使用 QAbstractItemView
提供標準的視圖接口,而結合這二者,就能夠將數據同表現層分離,在視圖中利用前面所說的模型索引。視圖管理來自模型的數據的佈局:既能夠直接渲染數據自己,也能夠經過委託渲染和編輯數據。編輯器
視圖不只僅用於展現數據,還用於在數據項之間的導航以及數據項的選擇。另外,視圖也須要支持不少基本的用戶界面的特性,例如右鍵菜單以及拖放。視圖能夠提供數據編輯功能,也能夠將這種編輯功能交由某個委託完成。視圖能夠脫離模型建立,可是在其進行顯示以前,必須存在一個模型。也就是說,視圖的顯示是徹底基於模型的,這是不能脫離模型存在的。對於用戶的選擇,多個視圖能夠相互獨立,也能夠進行共享。函數
某些視圖,例如QTableView
和QTreeView
,不只顯示數據,還會顯示列頭或者表頭。這些是由QHeaderView
視圖類提供的。在《QFileSystemModel》一章的最後,咱們曾經提到過這個問題。表頭一般訪問視圖所包含的同一模型。它們使用QAbstractItemModel::headerData()
函數從模型中獲取數據,而後將其以標籤 label 的形式顯示出來。咱們能夠經過繼承QHeaderView
類,實現某些更特殊的功能。佈局
正如前面的章節介紹的,咱們一般會爲視圖提供一個模型。拿前面咱們曾經見過的一個例子來看:性能
QStringList data; data << "0" << "1" << "2"; model = new QStringListModel(this); model->setStringList(data); listView = new QListView(this); listView->setModel(model); QPushButton *btnShow = new QPushButton(tr("Show Model"), this); connect(btnShow, SIGNAL(clicked()), this, SLOT(showModel())); QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addWidget(btnShow); QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(listView); layout->addLayout(buttonLayout); setLayout(layout);
運行一下程序,這個界面十分簡單:this
跟咱們前面的演示幾乎如出一轍。如今咱們有一個問題:若是咱們雙擊某一行,列表會容許咱們進行編輯。可是,咱們沒辦法控制用戶只能輸入數字——固然,咱們能夠在提交數據時進行檢測,這也是一種辦法,不過,更友好的方法是,根本不容許用戶輸入非法字符。爲了達到這一目的,咱們使用了委託。下面,咱們增長一個委託:code
class SpinBoxDelegate : public QStyledItemDelegate { Q_OBJECT public: SpinBoxDelegate(QObject *parent = 0) : QStyledItemDelegate(parent) {} 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; };
正如前面所說,委託就是供視圖實現某種高級的編輯功能。不一樣於經典的 Model-View-Controller(MVC)模式,model/view 沒有將用戶交互部分徹底分離。通常地,視圖將數據向用戶進行展現而且處理通用的輸入。可是,對於某些特殊要求(好比這裏的要求必須輸入數字),則交予委託完成。這些組件提供輸入功能,同時也能渲染某些特殊數據項。委託的接口由QAbstractItemDelegate
定義。在這個類中,委託經過paint()
和sizeHint()
兩個函數渲染用戶內容(也就是說,你必須本身將渲染器繪製出來)。爲使用方便,從 4.4 開始,Qt 提供了另外的基於組件的子類:QItemDelegate
和QStyledItemDelegate
。默認的委託是QStyledItemDelegate
。兩者的區別在於繪製和向視圖提供編輯器的方式。QStyledItemDelegate
使用當前樣式繪製,而且可以使用 Qt Style Sheet(咱們會在後面的章節對 QSS 進行介紹),所以咱們推薦在自定義委託時,使用QStyledItemDelegate
做爲基類。不過,除非自定義委託須要本身進行繪製,不然,兩者的代碼實際上是同樣的。繼承
繼承QStyledItemDelegate
須要實現如下幾個函數:索引
createEditor()
:返回一個組件。該組件會被做爲用戶編輯數據時所使用的編輯器,從模型中接受數據,返回用戶修改的數據。setEditorData()
:提供上述組件在顯示時所須要的默認值。updateEditorGeometry()
:確保上述組件做爲編輯器時可以完整地顯示出來。setModelData()
:返回給模型用戶修改過的數據。下面依次看看各函數的實現:接口
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; }
在createEditor()
函數中,parent 參數會做爲新的編輯器的父組件。
void SpinBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { int value = index.model()->data(index, Qt::EditRole).toInt(); QSpinBox *spinBox = static_cast<QSpinBox*>(editor); spinBox->setValue(value); }
setEditorData()
函數從模型中獲取須要編輯的數據(具備Qt::EditRole
角色)。因爲咱們知道它就是一個整型,所以能夠放心地調用toInt()
函數。editor 就是所生成的編輯器實例,咱們將其強制轉換成QSpinBox
實例,設置其數據做爲默認值。
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, Qt::EditRole); }
在用戶編輯完數據後,委託會調用setModelData()
函數將新的數據保存到模型中。所以,在這裏咱們首先獲取QSpinBox
實例,獲得用戶輸入值,而後設置到模型相應的位置。標準的QStyledItemDelegate
類會在完成編輯時發出closeEditor()
信號,視圖會保證編輯器已經關閉,可是並不會銷燬,所以須要另外對內存進行管理。因爲咱們的處理很簡單,無需發出closeEditor()
信號,可是在複雜的實現中,記得能夠在這裏發出這個信號。針對數據的任何操做都必須提交給QAbstractItemModel
,這使得委託獨立於特定的視圖。固然,在真實應用中,咱們須要檢測用戶的輸入是否合法,是否可以存入模型。
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { editor->setGeometry(option.rect); }
最後,因爲咱們的編輯器只有一個數字輸入框,因此只是簡單將這個輸入框的大小設置爲單元格的大小(由option.rect
提供)。若是是複雜的編輯器,咱們須要根據單元格參數(由option
提供)、數據(由index
提供)結合編輯器(由editor
提供)計算編輯器的顯示位置和大小。
如今,咱們的委託已經編寫完畢。接下來須要將這個委託設置爲QListView
所使用的委託:
listView->setItemDelegate(new SpinBoxDelegate(listView));
值得注意的是,new 操做符並不會真的建立編輯器實例。相反,只有在真正須要時,Qt 纔會生成一個編輯器實例。這保證了程序運行時的性能。
而後咱們運行下程序: