Qt模型/视图原理(3):自定义委托
若对C++语法不熟悉,建议参阅《C++语法详解》一书,电子工业出版社出版,该书语法示例短小精悍,对查阅C++知识点相当方便,并对语法原理作了详细讲解。
8.5.1 QAbstractItemDelegate基本原理
QAbstractItemDelegate类继承自QObject。委托用于显示视图中的单个项目,并处理模型数据的编辑。QAbstractDelegate的子类QItemDelegate和QStyleItemDelegate是Qt提供的对QAbstractDelegate类的默认实现。
若需要以自定义方式渲染项目,则必须重新实现paint()和sizeHint()函数。可使用如下两种方法实现自定义的编辑:
1)、方法1:创建一个编辑器部件,并将其设置为项目的编辑器,此方法必须重新实现createEditor()函数,并使用setEditorData()函数从模型中获取数据用于编辑器,使用setModelData()把编辑器的内容写入模型中。
2)、方法2:重新实现editorEvent()函数,直接处理用户事件。
8.5.2 QAbstractItemDelegate类中的函数
注意:QAbstractItemDelegate类中的函数都是虚函数,这些函数的参数都附带有必要的信息,比如对于paint() 函数的index参数,就是表示需要绘制的模型的索引,且index.data()函数包含有来自模型的数据。在重新实现这些虚函数时,应合理使用这些参数所附带的信息。
1、QAbstractItemDelegate(QObject* parne = Q_NULLPTR); //构造函数
2、纯虚函数(自定义渲染)
1)、virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const =0 ; //纯虚函数
若要提供自定义的渲染,则必须重新实现此函数,使用painter和外观选项option来渲染索引index所指的项目。若重新实现该函数,还必须重新实现sizeHint()函数。
2)、virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const=0 ; //纯虚函数
若要提供自定义的渲染,则必须重新实现此函数,若重新实现该函数,还必须重新实现paint()函数。
3、编辑器
3)、virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; //虚拟的
返回用于编辑索引index所指数据项的编辑器,注意:index包含正在使用的模型的信息。parent表示编辑器的父部件,项目选项由option指定。默认实现返回0,若要使用自定义的编辑器,需要重新实现该函数。
4)、virtual void setEditorData(QWidget *editor, const QModelIndex &index) const; //虚拟的
将编辑器editor的内容设置为索引index所指项目的数据。默认实现什么也不做。若要使用自定义的编辑器,则需要重新实现此函数。其原理见图8-32
5)、virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; //虚拟的
将模型model中由索引index所指项目的数据设置为编辑器editor的内容。默认实现什么也不做。若要使用自定义的编辑器,则需要重新实现此函数。原理见图8-33
6)、virtual void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const; //虚拟的
根据option中的矩形,更新索引index所指项目的编辑器的几何尺寸,该函数决定编辑器在视图中的位置和大小,因此是比较重要的,默认实现什么也不做。若要使用自定义的编辑器,则需要重新实现此函数。原理见图8-34
7)、virtual void destroyEditor(QWidget *editor, const QModelIndex &index) const; //虚拟的,qt5.0
当编辑器editor不再需要编辑索引index所指的数据项且应该被销毁时调用,也就是说,该函数用于销毁编辑器editor。默认行为是调用编辑器editor的QObject::deleteLater()函数(该函数用于销毁对象)。重新实现该函数可以避免使用默认行为。
4、事件
8)、virtual bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) ; //虚拟的
在开始编辑项目时,会调用这个函数,event为触发该编辑的事件,model、index 分别为被编辑项目的模型和索引,option为渲染项目的选项。即使鼠标事件没有开始编辑该项目,也会被发送给editorEvent(),比如当在项目上按下鼠标右键试图打开一个上下文菜单时。默认实现返回false(即什么也没做)。
9)、virtual bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) ; //虚拟的
当发生帮助事件时,会调用这个函数,若委托处理该事件则返回true,否则返回false。对于成功处理的QEvent::ToolTip和QEvent::WhatsThis事件,根据系统的配置可能会显示相关的弹出窗口。
5、信号
1)、void closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint = NoHint); //信号
当用户使用编辑器editor完成对项目的编辑时,发送此信号。hint为委托提供了编辑完成后影响模型和视图行为的方式,可以指示组件接下来执行什么操作,比如hint为EidtNextItem则视图应使用委托打开下一个项目的编辑器。枚举EndEditHint见表8-17
2)、void commitData(QWidget *editor); //信号
当编辑器editor编辑完数据并且要将其写回模型中时,必须发送此信号。
3)、void sizeHintChanged( const QModelIndex &index) ; //信号
当索引index的sizeHint()变化时,必须发送此信号,视图自动连接到此信号,并根据需要重新布局项目。
8.5.3 QStyleOptionViewItem类
QStyleOptionViewItem类继承自QStyleOption。
1、Qt绘制控件基本原理
Qt内置的部件的外观几乎都是由QStyle类的成员函数进行绘制的,使用这些函数绘制部件时需要向函数提供一些所需绘制图形元素的信息,而这些信息是由QStyleOption类及其子类进行描述的,QStyleOption类的不同子类描述了不同图形元素所需的信息,比如QStyleOptionButton描述了绘制按钮所需的有关信息等。因此模型/视图结构中的QStyleOptionViewItem类描述了绘制数据项所需的有关信息
2、QStyleOptionViewItem类中的函数
QStyleOptionViewItem(); //构造函数
QStyleOptionViewItem(const QStyleOptionViewItem& other); //复制构造函数
3、QStyleOptionViewItem类中的成员变量
注:项目的装饰(decoration):通常是指图标,与角色Qt::DecorationRole是相对应的。
1)、QBrush backgroundBrush //用于绘制项目背景的画刷
2)、Qt::CheckState checkState
若项目是可选中的(即变量features包含ViewItemFeature::HasCheckIndicator),若该项目被选中,则为true,否则为false。
3)、Qt::Alignment decorationAlignment //项目装饰的对齐方式,默认为Qt::AlignLeft。
4)、Position decorationPosition //项目装饰的位置,默认为Left。枚举Position见表8-18
5)、QSize decorationSize
项目装饰的大小,默认为QSize(−1 , −1),即无效大小。若要在项目上绘制图标需设置此变量的大小。
6)、Qt::Alignment displayAlignment
项目显示值的对齐方式,默认值为Qt::AlignLeft。通常用于设置与角色Qt::DisplayRole对应的值的对齐方式。
7)、ViewItemFeatures features
描述项目的特征,即该项目可以包含哪些类型的数据,枚举ViewItemFeature见表8-19
8)、QFont font //项目的字体,默认为应用程序的默认字体
9)、QIcon icon //绘制在项目中的图标
10)、QModelIndex index //需要绘制的模型的索引
11)、bool showDecorationSelected //当项目被选择时,是否突出显示装饰。默认为false。
12)、QString text //在项目中绘制的文本。
13)、Qt::TextElideMode textElideMode
当项目文本太长时,省略号应出现的位置,默认为Qt::ElideMiddle,即省略号位于文本中间。
14)、ViewItemPosition viewItemPosition
该项目相对于其他项目的位置,ViewItemPosition枚举见表8-20
4、除以上成员变量外,从QStyleOption继承而来的成员变量对项目的绘制也有影响,其中有两个比较重要,如下
1)、QRect QStyleOption::rect
表示绘制的项目的区域,也就是说,图形是在rect所指区域中绘制的。默认为空矩形。
2)、QStyle::State QStyleOption::state
绘制控件时的样式状态。state用于指示控件是否已启用,是否被选择等状态。枚举QStyle::State请参阅QStyle类(第13章)
示例8.9:使用委托绘制自定义的项目
//m.h文件的内容
#ifndef M_H
#define M_H
#include<QtWidgets>
#include<QDebug>
//本示例仅重新实现了QAbstractItemDelegate类中的两个纯虚函数
class T:public QAbstractItemDelegate{
public: T(QObject *parent = 0):QAbstractItemDelegate(parent){} //构造函数
//重新实现paint函数,以绘制每个单元格(项目)
void paint(QPainter *painter,const QStyleOptionViewItem &p1,
const QModelIndex &index) const{
//为第1行1列的项目绘制一个进度条
if (index.row()==1&&index.column() == 1) {
int i = index.data().toInt();
QStyleOptionProgressBar p; //该类用于设置绘制进度条时所需的信息
p.rect = p1.rect; /*设置绘制进度条的区域(使用Qt自动获取的区域)。此项必须设置,否则图形不知绘制于何处。*/
p.minimum = 0; //进度条的最小值
p.maximum = 100; //进度条的最大值
p.progress = i; //进度条的当前进度(来自模型中的数据)
p.text = QString::number(i) + "%"; //进度条当前显示的值
p.textVisible = true;
/*使用QStyle::drawControl()成员函数绘制进度条,由此可见,项目上具体需要显示什么,完全可以由程序员自行绘制。*/
QApplication::style()->drawControl(
QStyle::CE_ProgressBar, //该参数表示绘制进度条
&p,painter); }
//绘制第2行1列的项目,该项目会绘制一个图标和一个文本
else if(index.row()==2 && index.column() == 1){
QStyleOptionViewItem p; //该类用于设置绘制模型/视图的数据项时所需的信息
p.index=index; //设置需要绘制的项目的索引,此项不是必须设置项
p.rect=p1.rect; /*绘制项目的区域(使用Qt自动获取的区域),此项必须设置,否则图形不知绘制于何处。*/
//要显示图标,以下选项必须设置
p.features=QStyleOptionViewItem::HasDecoration //要显示图标,必须包含该特征
|QStyleOptionViewItem::HasDisplay;
p.decorationSize=QSize(55,55); /*设置装饰(通常为图标)的大小,若不设置该选项,则因装饰大小为无效大小(-1,-1)而无法绘制。*/
p.icon=index.data(Qt::DecorationRole).value<QIcon>(); //需要绘制的图标
//要使项目被选择时高亮显示,需设置以下选项
p.state=p1.state; //设置项目的样式标志(即项目是否被选择,是否启用等)
qDebug()<<p1.state; //读者可观察项目样式标志的变化情况
p.showDecorationSelected=true; //设置为true。
if (p1.state & QStyle::State_Selected) //若项目被选择,则高亮绘制其矩形
painter->fillRect(p1.rect, p1.palette.highlight());
//其他一些设置
p.decorationPosition=QStyleOptionViewItem::Bottom; //装饰(图标)显示在文字下方
p.displayAlignment=Qt::AlignLeft|Qt::AlignHCenter; //设置项目文本的对齐方式
p.text=index.data().toString(); //设置项目需要显示的文本
//绘制项目
QApplication::style()->drawControl(
QStyle::CE_ItemViewItem, //该参数表示,绘制模型/视图的项目
&p, painter); }
//绘制第2行0列的项目,本示例将该项目绘制于其他地方(即该项目未位于视图的单元格之内)
else if(index.row()==2&&index.column() == 0){
QStyleOptionViewItem p;
p.features=QStyleOptionViewItem::HasDisplay
|QStyleOptionViewItem::HasCheckIndicator; //该项目可被选中
p.rect=QRect(199,144,111,44); //该项目位于此处
p.state=p1.state;
p.showDecorationSelected=true;
p.text=index.data().toString();
p.backgroundBrush=QBrush(QColor(1,111,1)); //设置背景色(绿色)
QFont f; f.setPixelSize(22); p.font=f; //设置字体
p.displayAlignment=Qt::AlignLeft|Qt::AlignVCenter; //设置对齐方式
QApplication::style()->drawControl( QStyle::CE_ItemViewItem, &p,painter);}
//绘制其他项目
else { QStyleOptionViewItem p;
p.features=QStyleOptionViewItem::HasDisplay;
p.index=index; p.rect=p1.rect; p.state=p1.state;
p.showDecorationSelected=true;/*设置为true,否则无内容的项目被选择时不会高亮显示。*/
p.text=index.data().toString();
QApplication::style()->drawControl(QStyle::CE_ItemViewItem,&p, painter);
} } //函数paint结束
//此函数对本例没有影响,可返回任意值。
QSize sizeHint(const QStyleOptionViewItem &option,const QModelIndex &index) const
{ return QSize(0,0); }
}; //类T结束
class B:public QWidget{ Q_OBJECT
public: QStandardItemModel *d; QTableView *pv2; T *pt;
B(QWidget *p=0):QWidget(p){
pv2=new QTableView(this); pv2->move(22,22); pv2->resize(333,222);
d=new QStandardItemModel(3,3,this);
//向模型中添加数据
d->setData(d->index(0,0,QModelIndex()),"111",Qt::DisplayRole);
d->setData(d->index(1,0,QModelIndex()),222);
d->setData(d->index(1,1,QModelIndex()),33);
d->setData(d->index(2,0,QModelIndex()),"XXXX");
d->setData(d->index(2,1,QModelIndex()),QIcon("F:/1i.png"),Qt::DecorationRole);
d->setData(d->index(2,1,QModelIndex()),11);
d->setData(d->index(2,1,QModelIndex()),11,Qt::ToolTipRole);
d->setData(d->index(2,2,QModelIndex()),333);
pv2->setModel(d); //设置模型
//设置视图的委托为自定义委托
pt=new T(this); pv2->setItemDelegate(pt); }};
#endif // M_H
//m.cpp文件的内容
#include "m.h"
int main(int argc, char *argv[]){
QApplication aa(argc,argv);
B w; w.resize(444,355);
w.show();return aa.exec();}
运行结果及说明见图8-35
示例8.10:使用自定义的编辑器
//本示例只需把以下代码直接复制到示例8.9的类T之中即可。
//重新实现createEditor以便为每个单元格创建各自的编辑器
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const {
//创建两种类型的编辑器,以用于不同数据类型
QLineEdit *pe=new QLineEdit(parent); QSpinBox *ps=new QSpinBox(parent);
pe->setObjectName("EEE"); ps->setObjectName("SSS");
//若单元格的数据是文本或无效数据,则使用pe编辑器进行编辑
if(index.data().type()==QMetaType::QString||index.data().isNull()){
pe->setFocusPolicy(Qt::TabFocus); return pe; }
//否则使用ps编辑器进行编辑
else { ps->setMaximum(1000); return ps; }
}
//重新实现setEditorData以便把单元格的数据读入到编辑器中
void setEditorData(QWidget *e, const QModelIndex &index) const{
//若单元格之前是文本或无效数据,则把模型中的数据读入到QLineEdit类型的编辑器中
if(index.data().type()==QMetaType::QString||index.data().isNull()){
((QLineEdit*)e)->setText(index.data().toString()); }
//否则把模型中的数据读入到QSpinBox类型的编辑器中
else { ((QSpinBox*)e)->setValue(index.data().toInt());}
}
//重新实现setModelData以便把编辑器中的数据写入到单元格(即模型)
void setModelData(QWidget *e, QAbstractItemModel *m,const QModelIndex &index) const{
//若单元格之前是文本或无效数据,则把QLineEdit类型编辑器的数据写入模型
if(index.data().type()==QMetaType::QString||index.data().isNull())
{ m->setData(index,((QLineEdit*)e)->text()); }
//否则把QSpinBox类型编辑器的数据写入模型
else { m->setData(index,((QSpinBox*)e)->value());}
}
//设置编辑器的几何尺寸
void updateEditorGeometry(QWidget*e,const QStyleOptionViewItem &option,
const QModelIndex &index)const{
e->setGeometry(option.rect); //只需把编辑器的尺寸(位置和大小)设置为单元格的尺寸即可。
}
//该函数可以不需重新实现,可使用默认的实现方式
void destroyEditor(QWidget *e, const QModelIndex &index) const{
qDebug()<<e->objectName(); //查看删除的编辑器
delete e; } //使用delete删除编辑器,当然,你也可以不删除编辑器。
运行结果及说明见图8-36
本文作者:黄邦勇帅(原名:黄勇)