Qt學習之路(48): 自定義委託

 Qt學習之路(48): 自定義委託
2010-01-27 20:36:34
標籤:Qt C++ 學習 教程 QT教程
原創作品,允許轉載,轉載時請務必以超鏈接形式標明文章 原始出處 、作者信息和本聲明。否則將追究法律責任。http://devbean.blog.51cto.com/448512/271255
好久沒有來寫文章了,由於家裏面寬帶斷了,所以一直沒能更新,今天現在寫上一篇。
 
還是繼續前面的內容。前面我們分三次把自定義model說完了,其實主要還是那三個實例。在 model/view 架構中,與model同等重要的就是 view。
 
我們知道,在經典的 MVC 模型中,view用於向用戶展示 model 的數據。但是,Qt提供的不是 MVC 三層架構,而是一個 model/view 設計。這種設計並沒有包含一個完整而獨立的組件用於管理用戶的交互。一般來說,view僅僅是用作對model數據的展示和對用戶輸入的處理,而不應該去做其他的工作。在這種結構中,爲了獲得對用戶輸入控制的靈活性,這種交互工作交給了delegate,也就是“委託”,去完成。簡單來說,就像它們的名字一樣,view 將用戶輸入委託給 delegate 處理,而自己不去處理這種輸入。這些組件提供一種輸入能力,並且能夠在某些 view 中提供這種交互情形下的渲染,比如在 table 中通過雙擊單元格即可編輯內容等。對這種控制委託的標準接口被定義在 QAbstractItemDelegate 類中。
 
delegate 可以用於渲染內容,這是通過 paint() 和 sizeHint() 函數來完成的。但是,對於一些簡單的基於組件的delegate,可以通過繼承 QItemDelegate 或者 QStyledItemDelegate 來實現。這樣就可以避免要完全重寫 QAbstractItemDelegate 中所需要的所有函數。對於一些相對比較通用的函數,在這兩個類中已經有了一個默認的實現。
 
Qt提供的標準組件使用 QItemDelegate 提供編輯功能的支持。這種默認的實現被用在 QListView,QTableView 和 QTreeView 之中。view 實用的delegate可以通過 itemDelegate() 函數獲得。setItemDelegate() 函數則可以爲一個標準組件設置自定義的 delegate。
 
Qt 4.4版本之後提供了兩個可以被繼承的delegate類:QItemDelegate 和 QStyledItemDelegate。默認的delegate是 QStyledItemDelegate。這兩個類可以被相互替代,用於給view 組件提供繪製和編輯的功能。它們之間的主要區別在於,QStyledItemDelegate 使用當前的風格(style)去繪製組件。所以,在自定義delegate或者需要使用 Qt style sheets 時,建議使用 QStyledItemDelegate 作爲父類。使用這兩個類的代碼通常是一樣的,除了需要使用style進行繪製的部份。如果你希望爲view item自定義繪製函數,最好實現一個自定義的style。這個你可以通過QStyle類來實現。
 
如果delegate沒有支持爲你的數據類型進行繪製,或者你希望自己繪製item,那麼就可以繼承 QStyledItemDelegate 類,並且重寫 paint() 或者還需要重寫 sizeHint() 函數。paint() 函數會被每一個item獨立調用,而sizeHint()函數則可以定義每一個item 的大小。在重寫 paint() 函數的時候,通常需要用 if 語句找到你需要進行渲染的數據類型並進行繪製,其他的數據類型需要調用父類的實現進行繪製。
 
一個自定義的delegate也可以直接提供一個編輯器,而不是使用內置的編輯器工廠(editor item factory)。如果你需要這種功能,那麼需要實現一下幾個函數:
  • createEditor(): 返回修改數據的組件;
  • setEditorData(): 爲editor提供編輯的原始數據;
  • updateEditorGeometry(): 保證editor顯示在 item view 的合適位置以及大小;
  • setModelData(): 根據editor 的數據更新model的數據。
好了,這就是一個自定義delegate的實現了。下面來看一個例子。
 
這是一個歌曲及其時間的例子。使用的是QTableWidget,一共有兩列,第一列是歌曲名字,第二列是歌曲持續的時間。爲了表示這個數據,我們建立一個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) 

}
 
這個類的構造函數沒有做任何操作,只是把title和duration這兩個參數通過構造函數初始化列表賦值給內部的成員變量。注意,現在這兩個成員變量都是public的,在正式的程序中應該聲明爲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,覆蓋了其中的四個函數。通過前面的講解,我們已經瞭解到這些函數的作用。至於實現,我們前面也說過,需要通過QModelIndex選擇我們需要進行渲染的列,然後剩下的數據類型仍然需要顯式地調用父類的相應函數。由於我們在Track裏面存儲的是歌曲的秒數,所以在paint()裏面需要用除法計算出分鐘數,用%60計算秒數。其他的函數都比較清楚,請注意代碼。
 
最後寫一個使用的類:
 
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()函數設置了一下delegate。然後寫main()函數:
 
#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://devbean.blog.51cto.com/448512/271255

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章