视图中单个条目的渲染和编辑是通过代理来进行的。 在大多数情况下,由视图所提供的缺省的代理已经足够。 如果我们希望更好地控制条目的渲染,简单地通过使用自定义模型,我们就能经常达到要求。 在我们重新实现的data()中,我们可以持有Qt::FontRole, Qt::TextAlignmentRole, Qt::TextColorRole, 和Qt::BackgroundColorRole,并且它们被缺省的代理使用。 类如,在先前的城市和货币的例子中,我们持有Qt::TextAlignmentRole来取得右对齐的数字。
如果我们想要更强的控制,我们可以创建自己的代理类并把它置于我们使用的视图上。 下面的Track Editor对话框使用了一个自定义的代理。 它显示音乐曲目的标题和持续时间。 模型持有的数据将是简单的QStrings(标题)和ints(秒数),但是持续时间将被分成分钟数和秒数并且可以通过QTimeEdit进行编辑。
Track Editor对话框使用一个QTableWidget,它是一个操作在QTableWidgetItem上的便捷条目视图(convenience item view)子类。数据作为一个tracks列表被提供。
class Track { public: Track(const QString &title = "", int duration = 0); QString title; int duration; };
这些是构造函数的摘要,显示的是表格组件的创建和填充。
TrackEditor::TrackEditor(QList
构造函数创建了一个表格组件,并没有简单地使用缺省的代理,我们将代理设置为我们自定义的TRackDelegate,传递持有时间数据的列给它。 我们先设置列表头,然后遍历数据,用每条曲目名称和持续时间来填充行。
构造函数的其余部分和TRackEditor的其余部分平淡无奇,所以我们将看看掌控数据渲染和编辑的trackDelegate。
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(),目的是为了改变持续时间列的渲染效果。
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()调用时携有一个渲染为文本的整数,字符串应该有的字符数,整数的基(10是十进制),和填充字符。
为了使文本右对齐,我们拷贝当前风格选项并覆盖缺省的对齐方式。 我们然后调用QItemDelegate::drawDisplay()来绘制文本,然后调用QItemDelegate::drawFocus()绘制焦点矩形如果条目拥有焦点并且将不做其他的事。 使用drawDisplay()是非常方便的,尤其是当使用我们自己的风格选项。 我们还可以直接使用画笔来绘制。
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); } }
我们只希望控制曲目持续时间的编辑,而将曲目名称的编辑留给缺省的代理。 我们通过检查代理被要求为哪一列提供编辑器而实现这个目的。 如果是持续时间的那一列,我们创建一个QTimeEdit,设置适当的显示格式,然后连接editingFinished()信号和commitAndCloseEditor()槽。 对于任何其他的列,我们将编辑操作传给缺省的代理。
void TrackDelegate::commitAndCloseEditor() { QTimeEdit *editor = qobject_cast(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(editor); timeEdit->setTime(QTime(0, secs / 60, secs % 60)); } else { QItemDelegate::setEditorData(editor, index); } }
当用户开始编辑,视图就调用createEditor()来创建一个编辑器,然后通过setEditorData()用条目的当前数据来初始化编辑器。 如果是持续时间列的编辑器,我们提取曲目的持续秒数并设置QTimeEdit的时间为相应的分钟和秒数;否则,我们用缺省的代理来进行初始化。
void TrackDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { if (index.column() == durationColumn) { QTimeEdit *timeEdit = qobject_cast(editor); QTime time = timeEdit->time(); int secs = (time.minute() * 60) + time.second(); model->setData(index, secs); } else { QItemDelegate::setModelData(editor, model, index); } }
如果用户完成了编辑(类如,通过点击编辑器组件以外的地方,或者按下回车或制表键)而不是取消编辑,模型一定会被更新用编辑器中的数据。 如果持续时间被编辑,我们从QTimeEdit获取分钟和秒数,并转换为相应的秒数。
尽管在这个例子中是没有必要的,但是创建一个自定义的代理来精细地控制模型中任何条目的编辑和渲染是完全有可能的。 我们已经选择控制某一列,但是既然QModelIndex会被传递给QItemDelegate中所有被我们重新实现的函数,那么我们就可以控制列,行,矩形区域,父,或这些的任意组合,如果需要甚至可以是单个的条目。
在这一章,我们介绍了Qt的模型/视图架构的概貌。 我们已经展示了如何使用视图便捷子类,如何使用Qt的预定义模型,和如何创建自定义模型和自定义代理。 然而模型/视图架构是如此的丰富以至于我们没有空间去涵盖所有它可能做到的事。 类如,我们可以创建一个自定义视图,它不会将它的条目渲染成列表,表格或者是树。 Chart例子做的就是这个,这个例子在Qt的examples/itemviews/chart目录下,它展示的是将模型数据渲染成饼状图表的自定义视图。
也可以使用多视图来查看同样的模型而没有任何的拘束。 通过任何一个视图进行的编辑都将会自动并立即反映在其他视图中。 这项功能尤其有用对于查看大数据集中逻辑距离相距很远的几块数据。 这个架构还支持选择: 两个或更多的视图使用同一个模型,每个视图能被设置为拥有独立的选择,或被设置为选择可以在视图间共享。
Qt的在线文档提供涵盖广泛的条目浏览方面的编程和实现的类。 浏览http://doc.trolltech.com/4.1/model-view.html相关的类的列表,和http://doc.trolltech.com/4.1/model-view-programming.html一些附加信息和包含在Qt中的相关例子的连接。