Qt 自定義委托

2018-10-08 09:46 更新

自定義委托

好久沒(méi)有來(lái)寫(xiě)文章了,由于家里面寬帶斷了,所以一直沒(méi)能更新,今天現(xiàn)在寫(xiě)上一篇。

還是繼續(xù)前面的內(nèi)容。前面我們分三次把自定義 model 說(shuō)完了,其實(shí)主要還是那三個(gè)實(shí)例。在 model/view 架構(gòu)中,與 model 同等重要的就是 view。

我們知道,在經(jīng)典的 MVC 模型中,view 用于向用戶展示 model 的數(shù)據(jù)。但是,Qt 提供的不是 MVC 三層架構(gòu),而是一個(gè) model/view 設(shè)計(jì)。這種設(shè)計(jì)并沒(méi)有包含一個(gè)完整而獨(dú)立的組件用于管理用戶的交互。一般來(lái)說(shuō),view 僅僅是用作對(duì) model 數(shù)據(jù)的展示和對(duì)用戶輸入的處理,而不應(yīng)該去做其他的工作。在這種結(jié)構(gòu)中,為了獲得對(duì)用戶輸入控制的靈活性,這種交互工作交給了 delegate,也就是“委托”,去完成。簡(jiǎn)單來(lái)說(shuō),就像它們的名字一樣,view 將用戶輸入委托給 delegate 處理,而自己不去處理這種輸入。這些組件提供一種輸入能力,并且能夠在某些 view 中提供這種交互情形下的渲染,比如在 table 中通過(guò)雙擊單元格即可編輯內(nèi)容等。對(duì)這種控制委托的標(biāo)準(zhǔn)接口被定義在 QAbstractItemDelegate 類中。

delegate 可以用于渲染內(nèi)容,這是通過(guò) paint() 和 sizeHint() 函數(shù)來(lái)完成的。但是,對(duì)于一些簡(jiǎn)單的基于組件的 delegate,可以通過(guò)繼承 QItemDelegate 或者 QStyledItemDelegate 來(lái)實(shí)現(xiàn)。這樣就可以避免要完全重寫(xiě) QAbstractItemDelegate 中所需要的所有函數(shù)。對(duì)于一些相對(duì)比較通用的函數(shù),在這兩個(gè)類中已經(jīng)有了一個(gè)默認(rèn)的實(shí)現(xiàn)。

Qt 提供的標(biāo)準(zhǔn)組件使用 QItemDelegate 提供編輯功能的支持。這種默認(rèn)的實(shí)現(xiàn)被用在 QListView,QTableView 和 QTreeView 之中。view 實(shí)用的 delegate 可以通過(guò) itemDelegate() 函數(shù)獲得。setItemDelegate() 函數(shù)則可以為一個(gè)標(biāo)準(zhǔn)組件設(shè)置自定義的 delegate。

Qt 4.4版本之后提供了兩個(gè)可以被繼承的 delegate 類:QItemDelegate 和 QStyledItemDelegate。默認(rèn)的 delegate 是 QStyledItemDelegate。這兩個(gè)類可以被相互替代,用于給 view 組件提供繪制和編輯的功能。它們之間的主要區(qū)別在于,QStyledItemDelegate 使用當(dāng)前的風(fēng)格(style)去繪制組件。所以,在自定義 delegate 或者需要使用 Qt style sheets 時(shí),建議使用 QStyledItemDelegate 作為父類。使用這兩個(gè)類的代碼通常是一樣的,除了需要使用 style進(jìn)行繪制的部份。如果你希望為 view item 自定義繪制函數(shù),最好實(shí)現(xiàn)一個(gè)自定義的 style。這個(gè)你可以通過(guò) QStyle 類來(lái)實(shí)現(xiàn)。

如果 delegate 沒(méi)有支持為你的數(shù)據(jù)類型進(jìn)行繪制,或者你希望自己繪制 item,那么就可以繼承 QStyledItemDelegate 類,并且重寫(xiě) paint() 或者還需要重寫(xiě) sizeHint() 函數(shù)。paint() 函數(shù)會(huì)被每一個(gè) item 獨(dú)立調(diào)用,而 sizeHint()函數(shù)則可以定義每一個(gè) item 的大小。在重寫(xiě) paint() 函數(shù)的時(shí)候,通常需要用 if 語(yǔ)句找到你需要進(jìn)行渲染的數(shù)據(jù)類型并進(jìn)行繪制,其他的數(shù)據(jù)類型需要調(diào)用父類的實(shí)現(xiàn)進(jìn)行繪制。

一個(gè)自定義的 delegate 也可以直接提供一個(gè)編輯器,而不是使用內(nèi)置的編輯器工廠(editor item factory)。如果你需要這種功能,那么需要實(shí)現(xiàn)一下幾個(gè)函數(shù):createEditor(): 返回修改數(shù)據(jù)的組件;setEditorData(): 為 editor 提供編輯的原始數(shù)據(jù);updateEditorGeometry(): 保證 editor 顯示在 item view 的合適位置以及大??;setModelData(): 根據(jù) editor 的數(shù)據(jù)更新 model 的數(shù)據(jù)。好了,這就是一個(gè)自定義 delegate 的實(shí)現(xiàn)了。下面來(lái)看一個(gè)例子。

這是一個(gè)歌曲及其時(shí)間的例子。使用的是 QTableWidget,一共有兩列,第一列是歌曲名字,第二列是歌曲持續(xù)的時(shí)間。為了表示這個(gè)數(shù)據(jù),我們建立一個(gè) Track 類:

track.h


 #ifndef TRACK_H 
#define TRACK_H 

#include <QtCore> 

class Track 
{ 
public: 
        Track(const QString &title = "", int duration = 0); 

        QString title; 
        int duration; 
}; 

#endif // TRACK_H

track.cpp


#include "track.h" 

Track::Track(const QString &title, int duration) 
        : title(title), duration(duration) 
{ 
}

這個(gè)類的構(gòu)造函數(shù)沒(méi)有做任何操作,只是把 title 和 duration 這兩個(gè)參數(shù)通過(guò)構(gòu)造函數(shù)初始化列表賦值給內(nèi)部的成員變量。注意,現(xiàn)在這兩個(gè)成員變量都是 public 的,在正式的程序中應(yīng)該聲明為private 的才對(duì)。然后來(lái)看 TrackDelegate 類:

trackdelegate.h


#ifndef TRACKDELEGATE_H 
#define TRACKDELEGATE_H 

#include <QtGui> 

class TrackDelegate : public QStyledItemDelegate 
{ 
        Q_OBJECT 

public: 
        TrackDelegate(int durationColumn, QObject *parent = 0); 

        void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; 
        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; 

private slots: 
        void commitAndCloseEditor(); 

private: 
        int durationColumn; 
}; 

#endif // TRACKDELEGATE_H 

trackdelegate.cpp


#include "trackdelegate.h" 

TrackDelegate::TrackDelegate(int durationColumn, QObject *parent) 
        : QStyledItemDelegate(parent) 
{ 
        this->durationColumn = durationColumn; 
} 

void TrackDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 
{ 
        if (index.column() == durationColumn) { 
                int secs = index.model()->data(index, Qt::DisplayRole).toInt(); 
                QString text = QString("%1:%2").arg(secs / 60, 2, 10, QChar('0')).arg(secs % 60, 2, 10, QChar('0')); 
                QTextOption o(Qt::AlignRight | Qt::AlignVCenter); 
                painter->drawText(option.rect, text, o); 
        } else { 
                QStyledItemDelegate::paint(painter, option, index); 
        } 
} 

QWidget *TrackDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const 
{ 
        if (index.column() == durationColumn) { 
                QTimeEdit *timeEdit = new QTimeEdit(parent); 
                timeEdit->setDisplayFormat("mm:ss"); 
                connect(timeEdit, SIGNAL(editingFinished()), this, SLOT(commitAndCloseEditor())); 
                return timeEdit; 
        } else { 
                return QStyledItemDelegate::createEditor(parent, option, index); 
        } 
} 

void TrackDelegate::commitAndCloseEditor() 
{ 
        QTimeEdit *editor = qobject_cast<QTimeEdit *>(sender()); 
        emit commitData(editor); 
        emit closeEditor(editor); 
} 

void TrackDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const 
{ 
        if (index.column() == durationColumn) { 
                int secs = index.model()->data(index, Qt::DisplayRole).toInt(); 
                QTimeEdit *timeEdit = qobject_cast<QTimeEdit *>(editor); 
                timeEdit->setTime(QTime(0, secs / 60, secs % 60)); 
        } else { 
                QStyledItemDelegate::setEditorData(editor, index); 
        } 
} 

void TrackDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const 
{ 
        if (index.column() == durationColumn) { 
                QTimeEdit *timeEdit = qobject_cast<QTimeEdit *>(editor); 
                QTime time = timeEdit->time(); 
                int secs = (time.minute() * 60) + time.second(); 
                model->setData(index, secs); 
        } else { 
                QStyledItemDelegate::setModelData(editor, model, index); 
        } 
} 

正如前面所說(shuō)的,這個(gè)類繼承了 QStyledItemDelegate,覆蓋了其中的四個(gè)函數(shù)。通過(guò)前面的講解,我們已經(jīng)了解到這些函數(shù)的作用。至于實(shí)現(xiàn),我們前面也說(shuō)過(guò),需要通過(guò) QModelIndex 選擇我們需要進(jìn)行渲染的列,然后剩下的數(shù)據(jù)類型仍然需要顯式地調(diào)用父類的相應(yīng)函數(shù)。由于我們?cè)?Track 里面存儲(chǔ)的是歌曲的秒數(shù),所以在 paint()里面需要用除法計(jì)算出分鐘數(shù),用%60計(jì)算秒數(shù)。其他的函數(shù)都比較清楚,請(qǐng)注意代碼。

最后寫(xiě)一個(gè)使用的類:

trackeditor.h


#ifndef TRACKEDITOR_H 
#define TRACKEDITOR_H 

#include <QtGui> 
#include "track.h" 

class TrackEditor : public QDialog 
{ 
        Q_OBJECT 

public: 
        TrackEditor(QList<Track> *tracks, QWidget *parent); 

private: 
        QList<Track> *tracks; 
        QTableWidget *tableWidget; 
}; 

#endif // TRACKEDITOR_H

trackeditor.cpp


#include "trackeditor.h" 
#include "trackdelegate.h" 

TrackEditor::TrackEditor(QList<Track> *tracks, QWidget *parent) 
        : QDialog(parent) 
{ 
        this->tracks = tracks; 

        tableWidget = new QTableWidget(tracks->count(), 2); 
        tableWidget->setItemDelegate(new TrackDelegate(1)); 
        tableWidget->setHorizontalHeaderLabels(QStringList() << tr("Track") << tr("Duration")); 

        for (int row = 0; row < tracks->count(); ++row) { 
                Track track = tracks->at(row); 

                QTableWidgetItem *item0 = new QTableWidgetItem(track.title); 
                tableWidget->setItem(row, 0, item0); 

                QTableWidgetItem *item1 = new QTableWidgetItem(QString::number(track.duration)); 
                item1->setTextAlignment(Qt::AlignRight); 
                tableWidget->setItem(row, 1, item1); 
        } 

        QVBoxLayout *mainLayout = new QVBoxLayout; 
        mainLayout->addWidget(tableWidget); 
        this->setLayout(mainLayout); 
} 

其實(shí)也并沒(méi)有很大的不同,只是我們使用 setItemDelegate()函數(shù)設(shè)置了一下 delegate。然后寫(xiě)main()函數(shù):


#include <QtGui> 
#include "trackeditor.h" 

int main(int argc, char *argv[]) 
{ 
        QApplication a(argc, argv); 
        QList<Track> tracks; 
        Track t1("Song 1", 200); 
        Track t2("Song 2", 150); 
        Track t3("Song 3", 120); 
        Track t4("Song 4", 210); 
        tracks << t1 << t2 << t3 << t4; 
        TrackEditor te(&tracks, NULL); 
        te.show(); 
        return a.exec(); 
} 

好了,運(yùn)行一下看看效果吧!

本文出自 “豆子空間” 博客,請(qǐng)務(wù)必保留此出處 http://devbean.blog.51cto.com/448512/193918

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)