实现自定义委托
委托(delegate) 用来渲染和编辑视图中不同的项。在大多数情况下,视图中默认的委托已经足够了。如果想更好地控制有关项的显示,通常可以通过使用自定义模型很简单地实现我们所想要的:在 data() 重新实现中,我们可以处理Qt::FontRole、Qt:: TextAlignmentRole、Qt::TextColorRole、Qt::BackgroundColorRole,并且它们会被默认的委托使用。例如,在之前所显示的城市和汇率例子
中,我们已经为了得到向右对齐的数字的效果处理了Qt::TextAlignmentRole。
Track Editor
如果想得到更多的控制,则可以创建自己的委托类并且把它设置在我们想要使用它的视图中。下面显示的音轨编辑器对话框就使用了一个自定义委托,它显示了音轨的标题和持续时间。模型中保存的数据是非常简单的 QString(标题)和 int(秒),但是持续时间将会被分隔成分钟和秒两部分,并且会通过 QTimeEdit 来让它变得可以编辑。
音轨编辑器对话框使用了一个 QTableWidget,这是一个可以在 QTableWidgetItem 上操作的方便的项视图子类。
TrackEditor.h
#ifndef TRACKEDITOR_H
#define TRACKEDITOR_H
#include <QDialog>
#include <QList>
class QDialogButtonBox;
class QTableWidget;
class Track
{
public:
Track(const QString &title = "", int duration = 0);
QString title;
int duration;
};
class TrackEditor : public QDialog
{
Q_OBJECT
public:
TrackEditor(QList<Track> *tracks, QWidget *parent = 0);
void done(int result);
private slots:
void addTrack();
private:
QTableWidget *tableWidget;
QDialogButtonBox *buttonBox;
QList<Track> *tracks;
};
#endif
提供的数据是一个Track列表。
TrackEditor.cpp
#include <QtGui>
#include "trackdelegate.h"
#include "trackeditor.h"
Track::Track(const QString &title, int duration)
{
this->title = title;
this->duration = duration;
}
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);
}
tableWidget->resizeColumnToContents(0);
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
| QDialogButtonBox::Cancel);
QPushButton *addTrackButton = buttonBox->addButton(tr("&Add Track"),
QDialogButtonBox::ActionRole);
connect(addTrackButton, SIGNAL(clicked()), this, SLOT(addTrack()));
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(tableWidget);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setWindowTitle(tr("Track Editor"));
}
void TrackEditor::done(int result)
{
if (result == QDialog::Accepted) {
tracks->clear();
for (int row = 0; row < tableWidget->rowCount(); ++row) {
QString title = tableWidget->item(row, 0)->text();
QTableWidgetItem *item = tableWidget->item(row, 1);
int duration = item ? item->text().toInt() : 0;
tracks->append(Track(title, duration));
}
}
QDialog::done(result);
}
void TrackEditor::addTrack()
{
tableWidget->insertRow(tableWidget->rowCount());
}
TraskEditor()
这个构造函数创建于一个表窗口部件,并没有简单地使用默认的委托,而是设置了自定义的TrackDelegate,传递保存时间数据的列作为参数。我们由设置列的表头开始,然后遍历数据,利用每一个音轨的名称和持续时间组装各行。
TrackDelegate.h
#ifndef TRACKDELEGATE_H
#define TRACKDELEGATE_H
#include <QItemDelegate>
class TrackDelegate : public QItemDelegate
{
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
我们使用 QltemDelegate 作为基类,所以可以从默认的委托实现中获益。如果想从头开始做,则可以使用 AbstractItemDelegate 作为基类。为了提供一个可以编辑数据的委托必须实现 createEditor()、setEditorData()和 setModelData()。还要实现 paint(),它用于改变持续时间这一列的显示。
TrackDelegate.cpp
#include <QtGui>
#include "trackdelegate.h"
TrackDelegate::TrackDelegate(int durationColumn, QObject *parent)
: QItemDelegate(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'));
QStyleOptionViewItem myOption = option;
myOption.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
drawDisplay(painter, myOption, myOption.rect, text);
drawFocus(painter, myOption, myOption.rect);
} else{
QItemDelegate::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 QItemDelegate::createEditor(parent, option, index);
}
}
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 {
QItemDelegate::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 {
QItemDelegate::setModelData(editor, model, index);
}
}
void TrackDelegate::commitAndCloseEditor()
{
QTimeEdit *editor = qobject_cast<QTimeEdit *>(sender());
emit commitData(editor);
emit closeEditor(editor);
}
TrackDelegate()
构造函数中的 durationColumn 参数告诉这个委托,哪一列保存的是音轨的持续时间。
paint()
因为我们想使用"分钟:秒"的格式显示持续时间,所以需要重新实现这个 paint()函数。两个arg() 调用中的参数分别为:要显示为字符串的整数、需要的字符串有多少个字符、整数的基数(10 代表十进制)和填充的字符。
为了右对齐文本,我们复制当前的风格选项并且覆盖默认的对齐方式。然后调用 QltemDelegate::drawDisplay()绘制这个文本,然后跟着的是 QltemDelegate::drawFocus(),如果当前项具有焦点,它就绘制一个焦点矩形,否则什么也不做。使用 drawDisplay()非常方便,特别是当使用了自定义的风格选项的时候。还可以使用绘图器直接绘制。
createEditor()
我们只想控制音轨持续时间的编辑,所以把音轨名称的编辑留给了默认的委托。这是通过检查要求委托提供一个编辑器的是哪一列实现这一点的。如果是持续时间所在的列,就创建一个QTimeEdit,设置正确的显示格式,并且把它的 editingFinished()信号和commitAndÇloseEditor()槽连接起来。对于其他任何一列,都把有关编辑的处理传递给默认的委托。
commitAndCloseEditor()
如果用户按下 Eriter 键或者把焦点移动到这个QTimeEdit 之外(但不是按下 Esc 键), editinggFinshed()信号就会被发射并且就会调用 commitAndCloseEditor()槽。这个槽会发射conmitData()信号,通知视图用被编辑的数据替换已经存在的数据。它还发射closeEditor()信号,通知视图已经不再需要这个编辑器了,这时模型将会把它删除。编辑器可以使用QObject::sender()获得,这个函数返回发射了触发这个槽的信号的对象。如果用户取消编辑(按下 Esc 键),视图将会简单地删除编辑器。
setEditorData()
当用户初始化编辑的时候,视图会调用 createEditor()创建一个编辑器,然后利用这个项的当前数据调用 setEditorData()来初始化编辑器。如果编辑器是用于持续时间列的,就按秒提取这个音轨的持续时间,并且设置 QTimeEdit 的时间为相应的分钟数和秒数;否则,就让默认的委托处理这个初始化。
setModelData()
如果用户完成了编辑(例如,在这个编辑器窗口部件外面按下鼠标左键,或者按下了 Enter或Esc 键) ,而不是取消编辑,模型就必须使用编辑器的数据进行更新。如果持续时间被编辑了,就从 QTimeEdit 中提取出分钟数和秒数,并且设置数据为相应的秒数。
main.cpp
#include <QApplication>
#include "trackeditor.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QList<Track> tracks;
tracks << Track("The Flying Dutchman: Overture", 630)
<< Track("The Flying Dutchman: Wie aus der Fern laengst "
"vergangner Zeiten", 374)
<< Track("The Flying Dutchman: Steuermann, lass die Wacht",
152)
<< Track("Die Walkuere: Ride of the Valkyries", 286)
<< Track("Tannhaeuser: Freudig begruessen wir die edle "
"Halle", 384)
<< Track("Tannhaeuser: Wie Todesahnung - O du mein holder "
"Abendstern", 257)
<< Track("Lohengrin: Treulich gefuert ziehet dahnin", 294)
<< Track("Lohengrin: In fernem Land", 383)
<< Track("Die Meistersinger von Nuernberg: Overture", 543)
<< Track("Die Meistersinger von Nuernberg: Verachtet mir "
"die Meister nicht", 200)
<< Track("Die Meistersinger von Nuernberg: Ehrt eure "
"deutschen Meister", 112)
<< Track("Goetterdaemmerung: Funeral Music", 469)
<< Track("Tristan und Isolde: Mild und leise, wie er "
"laechelt", 375);
TrackEditor editor(&tracks);
editor.resize(600, 300);
editor.show();
return app.exec();
}
我们完全有可能创建一个可以很好地控制模型中任何一个项的编辑和显示的自定义委托,尽管在这种情况下不是必需的。我们已经选择控制特定的列,但是因为 QModelIndex 会被传递给重新实现的所有 QltemDelegate 的函数,所以可以按照行、列、矩形区域、父对象或者它们中的任意组合进行控制,如果需要还可以控制每一个单独的项。