【Qt筆記】視圖和委託

前面咱們介紹了模型的概念。下面則是另一個基本元素:視圖。在 model/view 架構中,視圖是數據從模型到最終用戶的途徑。數據經過視圖向用戶進行顯示。此時,這種顯示方式沒必要須同模型的存儲結構相一致。實際上,不少狀況下,數據的顯示同底層數據的存儲是徹底不一樣的。架構

咱們使用QAbstractItemModel提供標準的模型接口,使用 QAbstractItemView提供標準的視圖接口,而結合這二者,就能夠將數據同表現層分離,在視圖中利用前面所說的模型索引。視圖管理來自模型的數據的佈局:既能夠直接渲染數據自己,也能夠經過委託渲染和編輯數據。編輯器

 

視圖不只僅用於展現數據,還用於在數據項之間的導航以及數據項的選擇。另外,視圖也須要支持不少基本的用戶界面的特性,例如右鍵菜單以及拖放。視圖能夠提供數據編輯功能,也能夠將這種編輯功能交由某個委託完成。視圖能夠脫離模型建立,可是在其進行顯示以前,必須存在一個模型。也就是說,視圖的顯示是徹底基於模型的,這是不能脫離模型存在的。對於用戶的選擇,多個視圖能夠相互獨立,也能夠進行共享。函數

某些視圖,例如QTableViewQTreeView,不只顯示數據,還會顯示列頭或者表頭。這些是由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 提供了另外的基於組件的子類:QItemDelegateQStyledItemDelegate。默認的委託是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 纔會生成一個編輯器實例。這保證了程序運行時的性能。

而後咱們運行下程序:

相關文章
相關標籤/搜索