鍍金池/ 教程/ C/ 自定義委托
Qt 容器和算法拾遺
自定義 model 之一
反走樣
Hello, world!
Qt 容器類之關(guān)聯(lián)存儲容器
QStringListModel
拖放技術(shù)之一
狀態(tài)欄
QTreeWidget
拖放技術(shù)之二
通用算法
event()
Qt 學(xué)習(xí)之路(18): Qt 標(biāo)準(zhǔn)對話框之 QInputDialog
Qt 容器類之遍歷器和隱式數(shù)據(jù)共享
QListWidget
Meta-Object 系統(tǒng)
事件接收與忽略
Qt 學(xué)習(xí)之路(tip): parent 參數(shù)
Qt 標(biāo)準(zhǔn)對話框之 QColorDialog
QPainter(續(xù))
國際化(下)
漸變填充
自定義委托
創(chuàng)建 shared library
model-view 架構(gòu)
Graphics View Framework
自定義拖放數(shù)據(jù)對象
QSortFilterProxyModel
國際化(上)
組件布局
自定義 Model 之三
事件過濾器
QDirModel
Hello, world!(續(xù))
Qt 標(biāo)準(zhǔn)對話框之 QFileDialog
自定義 model 之二
深入了解信號槽
坐標(biāo)變換
剪貼板操作
QTableWidget
QByteArray 和 QVariant
創(chuàng)建一個對話框(下)
Qt 學(xué)習(xí)之路(32): 一個簡易畫板的實現(xiàn)(Graphics View)
文本文件讀寫
自定義事件
編寫跨平臺的程序
MainWindow
初探信號槽
Qt 學(xué)習(xí)之路(17): Qt 標(biāo)準(zhǔn)對話框之 QMessageBox
繪圖設(shè)備
菜單和工具條(續(xù))
二進(jìn)制文件讀寫
QString
事件(event)
菜單和工具條
QPainter
Qt 容器類之順序存儲容器
進(jìn)程間交互
API 文檔的使用
創(chuàng)建一個對話框(上)
一個簡易畫板的實現(xiàn)(QWidget)

自定義委托

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

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

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

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

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

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

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

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

這是一個歌曲及其時間的例子。使用的是 QTableWidget,一共有兩列,第一列是歌曲名字,第二列是歌曲持續(xù)的時間。為了表示這個數(shù)據(jù),我們建立一個 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òu)造函數(shù)沒有做任何操作,只是把 title 和 duration 這兩個參數(shù)通過構(gòu)造函數(shù)初始化列表賦值給內(nèi)部的成員變量。注意,現(xiàn)在這兩個成員變量都是 public 的,在正式的程序中應(yīng)該聲明為private 的才對。然后來看 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); 
        } 
} 

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

最后寫一個使用的類:

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); 
} 

其實也并沒有很大的不同,只是我們使用 setItemDelegate()函數(shù)設(shè)置了一下 delegate。然后寫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(); 
} 

好了,運行一下看看效果吧!

http://wiki.jikexueyuan.com/project/learn-road-qt/images/80.png" alt="" />

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