1. 程式人生 > >QT-模型檢視之自定義委託

QT-模型檢視之自定義委託

檢視委託(Delegate)簡介

由於模型負責組織資料,而檢視負責顯示資料,所以當用戶想修改顯示的資料時,就要通過檢視中的委託來完成

檢視委託類似於傳統的MVC設計模式裡的Controller(控制器)角色

  • Model(模型) - 負責資料組織
  • View(檢視) - 負責資料顯示
  • Controller(控制器) - 負責使用者輸入,並處理資料

初探自定義委託類 

  • 委託屬於檢視的子功能
  • 檢視主要負責組織具體資料項的顯示方式(是列表方式,還是樹形方式,還是表格方式)
  • 委託主要負責具體資料項的顯示和編輯,比如使用者需要編輯某個資料時,則需要彈出編輯框
  • 檢視可以通過 itemDelegate() ,setItemDelegate ( )成員函式來 獲得/設定當前委託物件
  • QAbstractItemDelegate類是所有委託的父類,用來 負責提供通用介面
  • 在模型檢視中,會預設提供一個QStyledItemDelegate類,供使用者編輯資料
  • 也可以通過繼承QItemDelegate父類,實現自定義委託功能

 

QAbstractItemDelegate類中的關鍵虛擬函式

QWidget * createEditor( QWidget * parent, QStyleOptionViewItem & option, QModelIndex & index ) ;
//建立編輯器,並返回該編輯器, option包含了該資料項的具體資訊(比如:資料項視窗大小,字型格式,對齊方式,圖示位於字型的哪個位置等)、index 包含了該資料項的內容(比如:text資訊,背景色等)

void updateEditorGeometry ( QWidget * editor, QStyleOptionViewItem & option, QModelIndex &index );
//該函式裡,可以通過editor->setGeometry()更新編輯元件大小,保證editor顯示的位置及大小
//大小可以通過option.rect獲取資料項視窗大小

void setEditorData ( QWidget * editor, const QModelIndex & index );
//通過索引值,將模型裡的資料提取到編輯器內容裡

void  setModelData ( QWidget * editor, QAbstractItemModel * model, QModelIndex & index );
//通過索引值, 根據editor 的資料更新model的資料。

void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) ;
//複製繪畫資料項的顯示和編輯

 

QAbstractItemDelegate類中的關鍵訊號

void  closeEditor ( QWidget * editor, QAbstractItemDelegate::EndEditHint hint = NoHint );
//當用戶關閉編輯器後,就會發出這個訊號。
// hint 引數用來指定當使用者完成編輯後,應該顯示什麼標記,用來提示使用者已完成編輯

void   commitData ( QWidget * editor ) ;
//當完成編輯資料後,傳送該訊號,表示有新資料提交到模型中

 

我們以編輯某個資料項為例:

  • 檢視首先會呼叫createEditor()函式生成編輯器
  • 呼叫updateEditorGeometry()函式設定編輯器元件大小
  • 呼叫setEditorData()函式,將模型裡的資料提取到編輯器中
  • 等待使用者編輯... ...
  • 當用戶編輯完成後, 系統將會發送commitData訊號函式
  • 然後呼叫setModelData()函式,設定模型資料,以及setEditorData()函式,更新編輯器
  • 檢視最後傳送closeEditor()訊號函式,表示已關閉編輯器

 

接下來,我們重寫上面函式,來自定義一個QCostomizedDelegate委託類

效果如下

 

QCustomizedDelegate.h:

#ifndef QCUSTOMIZEDDELEGATE_H

#define QCUSTOMIZEDDELEGATE_H

#include <QItemDelegate>

#include <QtGui>

class QCustomizedDelegate : public QItemDelegate

{

    Q_OBJECT

public:

    explicit QCustomizedDelegate(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;

 

};

#endif // QCUSTOMIZEDDELEGATE_H

 

QCustomizedDelegate.cpp:

#include "QCustomizedDelegate.h"

QCustomizedDelegate::QCustomizedDelegate(QObject *parent) :
    QItemDelegate(parent)
{
}

QWidget* QCustomizedDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if(index.column()==1)           //第1列 班級
    {
        QComboBox   *Cbox = new QComboBox(parent);
        Cbox->addItems(QStringList()<<"1班"<<"2班"<<"3班"<<"4班"<<"5班");
        return Cbox;
    }
    else if(index.column()==2)           //第2列 分數
    {
        QSpinBox    *Sbox = new QSpinBox(parent);
        Sbox->setRange(0,150);
        return Sbox;
    }

    return QItemDelegate::createEditor(parent, option, index);      //第0列,則選擇預設編輯器
}

void QCustomizedDelegate::setEditorData ( QWidget * editor, const QModelIndex & index ) const
{
    if(index.column()==1)           //第1列 班級
    {
        QComboBox   *Cbox = dynamic_cast<QComboBox*>(editor);
        Cbox->setCurrentIndex(Cbox->findText( index.data(Qt::DisplayRole).toString()));
    }
    else if(index.column()==2)           //第2列 分數
    {
        QSpinBox    *Sbox = dynamic_cast<QSpinBox*>(editor);
        Sbox->setValue(index.data(Qt::DisplayRole).toInt());
    }
    else
     QItemDelegate::setEditorData(editor,  index);       
}

void QCustomizedDelegate::setModelData ( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) const
{
    if(index.column()==1)           //第1列 班級
    {
        QComboBox   *Cbox = dynamic_cast<QComboBox*>(editor);
        model->setData(index,Cbox->currentText(),Qt::DisplayRole);
    }
    else if(index.column()==2)           //第2列 分數
    {
        QSpinBox    *Sbox = dynamic_cast<QSpinBox*>(editor);
        model->setData(index,Sbox->value(),Qt::DisplayRole);
    }
    else
        QItemDelegate::setModelData(editor, model, index);      
}

void QCustomizedDelegate::updateEditorGeometry ( QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
    editor->setGeometry(option.rect);
}

 

然後,再通過檢視的setItemDelegate(QAbstractItemDelegate * delegate )成員函式設定我們自定義的委託類物件即可

 

深入自定義委託類

之前我們寫的自定義委託,每次都需要雙擊某個資料項,才能彈出編輯器

那如何讓委託一直呈現在檢視顯示上呢?

步驟如下:

  • 重寫委託類的paint成員函式
  • 在paint()中,通過QApplication::style()->drawControl()來自定義資料顯示方式,比如繪製按鈕
  • 重寫委託類的editorEvent成員函式
  • 在editorEvent中處理互動事件,比如判斷滑鼠是否雙擊,以及更改模型資料等

 

其中QApplication::style()->drawControl()函式引數如下所示:

QApplication::style()->drawControl (ControlElement element,
                       constQStyleOption * option, 
                      QPainter *painter, const QWidget * widget = 0 ) ;

//繪畫元件
// element: 元素,用來指定控制元件樣式,比如: QStyle::CE_CheckBox 表示繪畫的widget是一個text文字的複選框

// option:選項,用來繪製控制元件所需的所有引數比如option.rect(設定元件大小位置), option.state(設定元件狀態)

//其中option. state成員值常見的有:
  QStyle::State_Enabled                 //表示該元件是啟用的,可以被使用者操作
  QStyle::State_On                         //表示該元件樣式是被選上的
  QStyle::State_Off                         //表示該元件樣式是未被選中的
  QStyle::State_MouseOver            //表示表示該元件樣式是:滑鼠停留在元件上面的樣子
  QStyle::State_Sunken                      //表示該元件樣式是:滑鼠按壓下的元件樣子
  QStyle::State_HasEditFocus          //表示該元件是否有編輯焦點

// painter:誰來繪畫

// widget = 0:如果該widget為0,則表示使用QT自帶的風格

 

示例-自定義一個QCostomizedDelegate委託類

效果如下

程式碼如下

QCustomizedDelegate.h:

#ifndef QCUSTOMIZEDDELEGATE_H
#define QCUSTOMIZEDDELEGATE_H

#include <QItemDelegate>
#include <QtGui>
#include "ProgressBar.h"

class QCustomizedDelegate : public QItemDelegate
{
    Q_OBJECT
    //m_bar:溫度臺的當前溫度進度條
    QScopedPointer<QProgressBar>  m_bar ;

public:
    explicit QCustomizedDelegate(QObject *parent = 0);
    void       paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
    bool       editorEvent ( QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index );

};

#endif // QCUSTOMIZEDDELEGATE_H

 

QCustomizedDelegate.cpp:

#include "QCustomizedDelegate.h"
#include "ProgressBar.h"

QCustomizedDelegate::QCustomizedDelegate(QObject *parent) :
    QItemDelegate(parent),
    m_bar(new QProgressBar())
{
     m_bar->setStyleSheet(qApp->styleSheet());       //設定風格            
}

void  QCustomizedDelegate::paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
    if(index.column()==1 && index.data().type() == QVariant::Int)     //判斷列數,並判斷索引值是否int型(溫度是通過int型值表示的)
    {
        int radio=14;
        int top = option.rect.top() + radio;
        int left = option.rect.left() + radio;
        int width = option.rect.width() - 2 * radio;
        int height = option.rect.height() - 2 * radio;

        QStyleOptionProgressBar bar;                   //設定引數
        bar.rect.setRect(left, top, width, height); 
        bar.state  = QStyle::State_Enabled;
        bar.progress = index.data().toInt();
        bar.maximum = 100;
        bar.minimum = 0;
        bar.textVisible = true;
        bar.text    =   QString("當前溫度:%1°").arg(bar.progress);
        bar.textAlignment = Qt::AlignCenter;

        QApplication::style()->drawControl(QStyle::CE_ProgressBar,&bar,painter, m_bar.data());
    }
    else
    {
        QItemDelegate::paint(painter,option,index);
    }
}

bool    QCustomizedDelegate::editorEvent ( QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index )
{
    if(event->type() == QEvent::MouseButtonDblClick)     //禁止雙擊編輯
    {
            return true;
    }
    return QItemDelegate::editorEvent(event,model,option,index);
}