使用代表來顯示並編輯視圖中的各個項。大多數情況下,視圖所提供的代表就足夠了。如果我們想更好控制顯示項,我們通常只用一個自定義模型就可以實現:在我們的data()重新實現中我們處理Qt::FontRole, Qt::TextAlignmentRole, Qt::TextColorRole, 及 Qt::BackgroundColorRole,這些都被默認的代表使用。如,在前面的Cities 和 Currencies例子中,我們處理Qt::TextAlignmentRole來獲得右對齊的數字。
如果我們想進一步控制,我們要創建自己的代表類,並把它設置在想使用它的視圖上。Track Editor對話框就使用了自定義代表。它顯示了音樂帶的標題及持續時間。模型中保存的數據只是QStrings (標題) and ints (秒),但持續時間被分成分和秒並用QTimeEdit來編輯。
Figure 10.15. The Track Editor dialog
Track Editor對話框使用了一個QTableWidget,它是簡單的項目視圖子類,可以操作QTableWidgetItem。
class Track
{
public:
Track(const QString &title = "", int duration = 0);
QString title;
int 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);
}
...
}
構造函數創建了一個表控件,並沒有只用默認代表,我們設置了自定義的TRackDelegate,把它傳給保存時間數據的列。我們開始設置列頭,然後遍歷數據,生成每個track的行:名字和持續時間。
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;
};
用QItemDelegate作爲基類,這樣可以用默認代表的實現。如果想從頭開始設計我們就可以使用QAbstractItemDelegate作基類。爲了提供一個可以編輯數據的代表,我們必須實現createEditor(), setEditorData(), 及 setModelData().也要實現paint()來改變duration列的顯示。
TrackDelegate::TrackDelegate(int durationColumn, QObject *parent)
: QItemDelegate(parent)
{
this->durationColumn = 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);
}
}
由於想以"minutes :seconds"格式顯示持續時間,我們在paint()函數中重新實現。arg()調用接收的參數是:一個整數來作爲顯示的串,然後是串的字符數,底數及填充符。
爲了文本右對齊,我們拷貝當前樣式選項覆蓋了默認對齊方式。然後調用QItemDelegate::drawDisplay()來繪製文本,接着調用QItemDelegate::drawFocus(),其功能是當一個項有焦點時繪製焦點矩形。我們也可以直接使用painter來繪製。
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);
}
}
我們只想編輯磁帶持續時間,磁帶名的編輯由默認代表來處理。如果是duration列,就創建一個QTimeEdit,設置恰當的顯示格式,連接editingFinished()信號與commitAndCloseEditor()槽。對其它列,傳遞編輯處理句柄給默認代表。
void TrackDelegate::commitAndCloseEditor()
{
QTimeEdit *editor = qobject_cast<QTimeEdit *>(sender());
emit commitData(editor);
emit closeEditor(editor);
}
如果用戶按回車鍵或把焦點移到QTimeEdit外了(但如果按Esc不會),將發送editingFinished()信號,commitAndCloseEditor()槽會被調用。這個槽發出commitData()信號通知視圖有已經編輯好的數據要替換現存數據,也發出closeEditor()信號通知視圖不再需要該編輯器,這時模型會刪除它。可以用QObject::sender()來獲得編輯器,它返回一個對象,該對象發送了信號來觸發該槽。如果用戶按Esc來取消,視圖只是刪除編輯器。
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);
}
}
當用戶開始編輯時,視圖調用createEditor()來創建一個編輯器,然後setEditorData()用項的當前數據來初始化編輯器。如果編輯器是用於duration列,我們取出以秒爲單位的持續時間,設置QTimeEdit的時間爲相應的分秒,其它就交給默認代表來初始化。
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);
}
}
如果用戶完成編輯(如,左擊編輯器外部分或敲回車或Tab鍵)而不是取消編輯,那麼必須用編輯器的數據來更新模型。如果持續時間被編輯了,我們從QTimeEdit中取出分秒然後設置數據爲相應的秒數。
儘管這種情況沒必要,完全可能創建一個自定義的代表很好控制模型中任何項的編輯和顯示.我們選擇性地控制某個列,但由於將QModelIndex傳給重新實現的所有的QItemDelegate函數,我們就可以用列、行、矩形區、父親或它們的任意組合來控制。
也可能用多個視圖來瀏覽同一個模型。一個視圖的任何編輯可以自動影響其它視圖。對於瀏覽那種邏輯上相差很遠的片斷數據這種功能特別有用。這種結構也支持選擇:兩個及以上視圖共用一個模型,每個視圖可有獨立的選擇,或選擇可在視圖中共享。