QStyle(2):子类化QStyle基础
QStyle(2):子类化QStyle基础
若对C++语法不熟悉,建议参阅《C++语法详解》一书,电子工业出版社出版,该书语法示例短小精悍,对查阅C++知识点相当方便,并对语法原理进行了透彻、深入详细的讲解,可确保读者彻底弄懂C++的原理,彻底解惑C++,使其知其然更知其所以然。此书是一本全面了解C++不可多得的案头必备图书。
把绘制自定义部件外观的步骤大致分为三大板块,如下:
①、样式元素:即指定需要绘制的图形元素(比如焦点框、按钮,工具栏等)。样式元素使用QStyle类中的一系列枚举(共有11个枚举)进行描述。
②、样式选项:包含了需要绘制的图形元素的所有信息,比如包含了图形元素的文本、调色板等,样式选项使用QStyleOption及其子类进行描述。
③、样式绘制函数:即绘制图形元素的函数,这些函数是QStyle类的成员函数,比如drawControl()等(在前文已见过)
13.4.1 样式元素
样式元素是指需要绘制的图形元素,一个部件由多个样式元素组成,比如,当样式接收到绘制按钮的请求时,会绘制标签(文本和图标)、按钮斜面(Bevel)和焦点框等,这些都是样式元素,图13-6为一个按钮样式元素的组成概念图。
共有3种样式元素,如下
①、原始元素:由QStyle::PrimitiveElement枚举(见表13-5)描述,使用前缀“PE_”标识,原始元素通常由部件使用,包括框架、按钮斜面、旋转框、滚动条、组合框的箭头等,原始元素不能单独存在,是其他界面元素(如复杂控件和控件元素)的组成部分,原始元素不参与用户的交互,只是被动的绘制和显示。
②、控件元素:由QStyle::ControlElement枚举(见表13-3)描述,使用前缀“CE_”标识。控件元素执行某种操作或向用户显示一些信息,控件元素会与用户交互,可以是单独的部件(比如QPushButton),也可以是其他部件的一部分(比如QScrollBar的滑块)。控件元素还可以由多个子元素组成,子元素仅用于计算边界区域,不能被绘制,然后便可在计算出的边界区域中绘制子元素,子元素使用QStyle::SubElement枚举描述(使用前缀SE_标识,见表13-7)。
③、复杂控件元素:由QStyle::ComplexControl枚举(见表13-4)描述,使用前缀“CC_”标识。复杂控件元素包含子控件,比如QSpinBox、QScrollBar、QToolButton等。复杂控件元素的子控件由QStyle::SubControl枚举定义(使用前缀SC_标识,见表13-6)。
④、因为部件通常都是由矩形组成的,因此各种控件元素其实描述的就是一个矩形区域。
表13-3~表13-8列出了描述各种样式元素的枚举及元素的状态标志,各种样式元素之间的关系,会在后文讲解,此处暂不讲解
注:以下表仅列出部分内容,完整的内容可参阅Qt帮助文档。
13.4.2 样式绘制函数
为讲解需要,此处先列出需要使用到的QStyle类中的部分函数,其余函数见后文(以下的表请查阅帮助文档)
1、virtual void QStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget = Q_NULLPTR) const = 0; //纯虚函数
该函数用于绘制复杂控件元素。表示使用painter,样式选项option绘制控件control。
widget参数是可选的,可用作绘制控件的辅助工具。
参数option是指向QStyleOptionComplex对象的指针,可使用qstyleoption_cast()函数把该对象转换为正确的子类型。
option的rect成员变量必须位于逻辑坐标中,重新实现此函数,在调用QStyle::drawPrimitive()或QStyle::drawControl()函数之前,应使用QStyle::visualRect()把逻辑坐标转换为屏幕坐标。
表13-9为复杂控件元素及其关联的样式选项子类,以及使用的相应的状态标志。
2、virtual void QStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = Q_NULLPTR) const = 0; //纯虚函数
该函数用于绘制控件元素。表示使用painter,样式选项option绘制元素element。
widget参数是可选的,可用作绘制控件的辅助工具。
参数option是指向QStyleOption对象的指针,可使用qstyleoption_cast()函数把该对象转换为正确的子类型。
表13-10为控件元素及其关联的样式选项子类,以及使用的相应的状态标志。
3、virtual void QStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter,const QWidget *widget = Q_NULLPTR) const = 0; //纯虚函数
该函数用于绘制原始元素。表示使用painter,样式选项option绘制元素element。
widget参数是可选的,可用作绘制的辅助工具。
表13-11为原始元素及其关联的样式选项子类,以及使用的相应的状态标志。
4、virtual void QStyle::drawItemPixmap(QPainter *painter, const QRect &rectangle, int alignment, const QPixmap &pixmap) const; //虚函数
表示使用painter根据对齐方式alignment,在矩形rectangle中绘制像素图pixmap
5、virtual void QStyle::drawItemText(QPainter *painter, const QRect &rectangle, int alignment, const QPalette &palette, bool enabled, const QString &text, QPalette::ColorRole textRole = QPalette::NoRole) const; //虚函数
表示使用painter,根据对齐方式alignment,调色板palette,在矩形rectangle中绘制文本text。若指定了颜色角色textRole,则使用该颜色角色的调色板颜色绘制文本,参数enabled用于指示是否启用该项。重新实现此函数时,enabled参数应影响项目的绘制方式。
6、virtual void QStyle::polish(QWidget *widget); //虚函数
virtual void QStyle::polish(QApplication *application); //虚函数
virtual void QStyle::polish(QPalette &palette); //虚函数
以上函数用于初始化部件的外观,会在部件创建完成之后,在第一次显示之前被调用,默认实现什么也不做。子类化QStyle时,可利用以上函数的调用时机,对部件的一些属性进行初始化。具体使用方法见示例13.5。
7、virtual void QStyle::unpolish(QWidget *widget); //虚函数
virtual void QStyle::unpolish(QApplication *application); //虚函数
以上函数与polish()函数相对应,需要注意的是,只有在部件被销毁时才会被调用。
8、virtual QRect subControlRect(ComplexControl control, const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget = Q_NULLPTR) const = 0; //纯虚函数
返回复杂控件control的子控件subControl的矩形
9、virtual QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget = Q_NULLPTR) const = 0; //纯虚函数
返回由样式选项option所描述的控件的子元素element的矩形,重新实现该函数的示例见下一小节
13.4.3 子类化QStyle类的方法
子类化QStyle类自定义样式时,其父类通常应选择QStyle的子类而不是QStyle,因为QStyle的子类实现了QStyle类中的虚函数,子类化其子类可以减少重新实现虚函数所需要的工作量,否则其工作量是比较庞大的。
QStyle共有两个子类,分别为QCommonStyle及QCommonStyle的子类QProxyStyle,在子类化时可根据情况进行选择,其中QCommonStyle类实现了部件的共同界面外观,因此该类实现的界面并不一定完整,而QProxyStyle类则实现了一个QStyle(通常是默认的系统样式),因此该类的实现比较完整。
自定义部件外观样式时,可以根据需要绘制的部件而选择drawControl()或drawComplexControl()或drawPrimitive()函数进行绘制,下面以示例对此方法进行说明
示例13.5:子类化QCommonStyle实现自定义按钮样式(效果见图13-7)
//m.h文件的内容
#ifndef M_H
#define M_H
#include<QtWidgets>
class B:public QCommonStyle{ Q_OBJECT //子类化QCommonStyle类
public:B(){}
//重新实现drawControl()函数以绘制控件的自定义外观(本示例用于绘制一个QPushButton)
void drawControl(ControlElement e, const QStyleOption *op,
QPainter *pr, const QWidget *w = Q_NULLPTR) const{
QPen pn(QColor(111,1,1)); //红色
QBrush bs(QColor(111,111,111)); //灰色
QBrush bs1(QColor(1,111,1)); //绿色
QBrush bs2(QColor(222,222,222)); //白色
//参数op,携带了绘制部件时的状态及其他一些信息。本示例绘制的是按钮,因此把op转换为
//QStyleOptionButton类型。
const QStyleOptionButton *pb=qstyleoption_cast<const QStyleOptionButton*>(op);
QRect r=pb->rect; //获取设置的按钮的大小
//若鼠标进入按钮则使用绿色填充其背景
if(pb->state&QStyle::State_MouseOver) { pr->fillRect(r,bs1); }
//若按钮处于凸起状态(通常为未选中,未被按下状态),则使用灰色填充其背景。
//注意:凸起状态和鼠标进入按钮的状态,二者只能存在其一。
else if(pb->state&QStyle::State_Raised) { pr->fillRect(r,bs); }
//若按钮被按下,则使用白色填充其背景
if(pb->state&QStyle::State_Sunken) { pr->fillRect(r,bs2); }
//当按钮具有焦点时,绘制按钮的焦点边框(红色)
if(pb->state&QStyle::State_HasFocus) {
pr->save(); pn.setWidth(4); pn.setStyle(Qt::DashLine); pr->setPen(pn);
pr->drawRect(r.adjusted(1,1,-2,-2)); pr->restore(); }
//绘制按钮显示的文本。
pr->drawText(r,Qt::AlignCenter,pb->text); }
void polish(QWidget *w){
//设置Qt::WA_Hover属性后,将使鼠标在进入或离开部件时产生绘制事件,若不设置此属性,
//则鼠标进入或离开部件时,部件不会更新。
w->setAttribute(Qt::WA_Hover,true); }
void unpolish(QWidget *w){ w->setAttribute(Qt::WA_Hover,false); } };
#endif // M_H
//m.cpp文件的内容
#include "m.h"
int main(int argc, char *argv[]){ QApplication aa(argc,argv);
QWidget w;
QPushButton *pb1=new QPushButton("AAA",&w); pb1->move(22,22);pb1->resize(221,22);
QPushButton *pb2=new QPushButton("BBB",&w); pb2->move(22,88);
pb1->setStyle(new B()); //按钮pb1使用自定义的样式
w.resize(444,333); w.show(); return aa.exec(); }
示例13.6:子类化QCommonStyle实现自定义微调按钮样式(效果见图13-8)
//m.h文件的内容
#ifndef M_H
#define M_H
#include<QtWidgets>
class B:public QCommonStyle{ Q_OBJECT
public: B(){}
//本示例需重新实现drawComplexControl()函数,因为微调按钮是复杂控件元素
void drawComplexControl(ComplexControl c,const QStyleOptionComplex *op,
QPainter *pr, const QWidget *w = Q_NULLPTR) const{
//参数op,携带了绘制部件时的状态及其他一些信息。本示例绘制的是微调按钮,
//因此把op转换为QStyleOptionSpinBox类型。
const QStyleOptionSpinBox *pb=qstyleoption_cast<const QStyleOptionSpinBox*>(op);
qDebug()<<pb->state; //可输出pb以查看微调按钮的状态值
//获取微调按钮向上/向下箭头和文本编辑区域所占据的矩形
QRectF r1=subControlRect(c,op,QStyle::SC_SpinBoxUp,w);
QRectF r2=subControlRect(c,op,QStyle::SC_SpinBoxDown,w);
QRect r3=subControlRect(c,op,QStyle::SC_SpinBoxEditField,w);
//创建一些画刷和画笔
QBrush bs(QColor(111,111,111)); //灰色
QBrush bs1(QColor(111,1,1)); //红色
QPen pn(QColor(1,111,1)); //绿色
//填充微调按钮箭头区域(含向上/向下箭头)的背景色为灰色
pr->fillRect(r1.x(), r1.y(), r1.width(), r1.height()+r2.height(),bs);
//创建向上箭头(一个指向上的三角形)的路径
QPainterPath ph1;
ph1.moveTo(r1.x()+r1.width()/2,r1.y()); ph1.lineTo(r1.bottomLeft());
ph1.lineTo(r1.bottomRight()); ph1.closeSubpath();
pr->drawPath(ph1);
//创建向下箭头(一个指向下的三角形)的路径
QPainterPath ph2;
ph2.moveTo(r2.x(),r2.y()); ph2.lineTo(r2.topRight());
ph2.lineTo(r2.bottomLeft().x()+r2.width()/2,r1.height()+r2.height());
ph2.closeSubpath(); pr->drawPath(ph2);
//绘制鼠标按下箭头时的外观
if(pb->state&QStyle::State_Sunken) {
//把当前鼠标的坐标转换为相对于微调按钮的坐标
QPoint pt1=w->mapFromGlobal(QCursor::pos());
pr->save(); //在绘制前保存画刷
pr->setBrush(bs1);
//若鼠标按下的是向上箭头则使用画刷bs1(红色)填充其路径ph1,否则填充ph2。
if(pt1.y()<r1.height()){ pr->drawPath(ph1); }
else{ pr->drawPath(ph2); }
//恢复画刷,若不恢复画刷,则前面设置的画刷bs1会继续作用于之后的绘制。
pr->restore(); }
//绘制当微调按钮获得焦点时的焦点边框
if(pb->state&QStyle::State_HasFocus){
pr->save();
pr->setPen(pn); //绿色画笔
pr->drawRect(r3.adjusted(-1,-1,r1.width(),1));
pr->restore(); } }};
#endif // M_H
//m.cpp文件的内容
#include "m.h"
int main(int argc, char *argv[]){ QApplication aa(argc,argv);
QWidget w;
QPushButton *pb1=new QPushButton("AAA",&w); pb1->move(22,22);pb1->resize(221,22);
QSpinBox *px=new QSpinBox(&w); px->move(55,55); px->resize(221,44);
px->setStyle(new B()); //微调按钮使用自定义样式
w.resize(444,333); w.show(); return aa.exec(); }
以上示例使用drawControl()或drawComplexControl()函数直接对整个部件的外观进行绘制,若使用drawPrimitive()函数,则还可以绘制部件的某个元素,比如可绘制按钮的PE_PanelButtonCommand元素、PE_FrameDefaultButton元素等,大至代码如下
void drawPrimitive(PrimitiveElement e, const QStyleOption *op, QPainter *pr,
const QWidget *w = Q_NULLPTR) const
{ ……
switch(e){……
case PE_PanelButtonCommand:
//绘制元素的代码
case PE_FrameDefaultButton:
//绘制元素的代码
……
}
……
}
13.5.1 QStyle::PixMetric枚举的使用
本文作者:黄邦勇帅(原名:黄勇)