Qt学习笔记外观篇(二):QStyle
来源:互联网 发布:mac照片和iphoto 编辑:程序博客网 时间:2024/05/29 06:34
1)子类化QStyle或者一个预定义的风格,这种方法很好用,Qt本身就是用这种方法为它所支持的不同平台提供基于平台的外观的
2)子类化个别的窗口部件类,并且重新实现它的绘制和鼠标事件处理器。
3)Qt 样式表
QStyle类包装应用程序外观,对于应用程序的每个窗口部件,都会使用QStyle进行绘制。
QStyle API包含绘制图形元素[drawPrimitive(),drawControl(),drawComplexControl()等]函数,样式查询函数[pixelMetrics(),styleHint(),hitTest()等]。QStyle成员函数典型的带有QStyleOption对象,它包含绘制窗口部件的通用信息(例如调色板)以及特有信息(例如按钮的文字)。
下面解析如何子类化QStyle来实现自定义外观:
子类化QStyle一般是通过重新实现几个虚函数来实现的。
1)polish(QPalette& palette):通常在此函数内指定配色方案,也即配置调色板
2)polish(QWidget*)&unpolish(QWidget*):当样式应用到窗口部件时,polish(QWidget*)就会调用,从而允许我们进行最后的定制,当动态改变样式的时候,unpolish就会调用,来撤销polish的影响。polish(QWidget*)一般用做窗口部件的事件过滤器。
3)styleHint返回关于样式外观的提示
4)pixelMetric返回像素值,来影响窗口部件的绘制
5)drawPrimitive(),drawControl(),drawComplexControl()具体的执行绘制。
如何实现自定义样式使得左侧窗口成为右侧窗口?
首先是颜色,各个窗口部件的背景色发生了变化;然后是QSpinBox的形状发生了变化,QCheckBox的选取标志发生了变化还有就是Save&Cancel按钮的位置发生了变化。即实现:带渐变背景的圆角按钮、非传统的微调框、夸张的选取标志和“棕色画刷”背景。
类声明如下:
#include <QWindowsStyle>class BronzeStyle : public QWindowsStyle{ Q_OBJECTpublic: BronzeStyle(){} void polish(QPalette& palette); void polish(QWidget* widget); void unpolish(QWidget* widget); int styleHint(StyleHint hint, const QStyleOption *opt, const QWidget *widget=0, QStyleHintReturn *returnData=0) const; int pixelMetric(PixelMetric pm, const QStyleOption *option, const QWidget *widget=0) const; void drawPrimitive(PrimitiveElement which,const QStyleOption* option,QPainter* painter,const QWidget* widget=0)const; void drawComplexControl(ComplexControl which,const QStyleOptionComplex* option,QPainter* painter,const QWidget* widget=0)const; QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, const QWidget *w=0) const;public slots: QIcon standardIconImplementation(StandardPixmap standardIcon, const QStyleOption *option, const QWidget *widget=0) const;private: void drawBronzeFrame(const QStyleOption* option,QPainter* painter)const; void drawBronzeBevel(const QStyleOption* option,QPainter* painter)const; void drawBronzeCheckBoxIndicator(const QStyleOption* option,QPainter* painter)const; void drawBronzeSpinBoxButton(SubControl which,const QStyleOptionComplex* option,QPainter* painter)const;};
现在开始实现第一步:颜色配置
void BronzeStyle::polish(QPalette &palette){ QPixmap backgroundImage(":/images/background.png"); QColor bronze(207,155,95); QColor veryLightBlue(239,239,247); QColor lightBlue(223,223,239); QColor darkBlue(95,95,191); palette=QPalette(bronze); palette.setBrush(QPalette::Window,backgroundImage); palette.setBrush(QPalette::BrightText, Qt::white); palette.setBrush(QPalette::Base, veryLightBlue); palette.setBrush(QPalette::AlternateBase, lightBlue); palette.setBrush(QPalette::Highlight, darkBlue); palette.setBrush(QPalette::Disabled, QPalette::Highlight,Qt::darkGray);}根据前篇中对QPalette的介绍,我们很容易明白这里的设置。
第二步,来完成查询函数,提供绘制窗口部件所需要的信息:
int BronzeStyle::styleHint(StyleHint which, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const{ switch(which){ case SH_DialogButtonLayout: return int(QDialogButtonBox::MacLayout); case SH_EtchDisabledText: return int(true); case SH_DialogButtonBox_ButtonsHaveIcons: return int(true); case SH_UnderlineShortcut: return int(false); default: return QWindowsStyle::styleHint(which,option,widget,returnData); }}int BronzeStyle::pixelMetric(PixelMetric which, const QStyleOption *option, const QWidget *widget) const{ switch(which){ case PM_ButtonDefaultIndicator: return 0; case PM_IndicatorHeight: case PM_IndicatorWidth: return 16; case PM_CheckBoxLabelSpacing: return 8; case PM_DefaultFrameWidth: return 2; default: return QWindowsStyle::pixelMetric(which,option,widget); }}
其中的styleHint函数完成的样式外观的提示,对于SH_DialogButtonLayout,其返回MacLayout,从而实现OK键在Cancel的右侧;pixelMetric函数返回关于大小的提示:按钮Indicator值为0,checkbox指示器为16*16的正方形,checkbox指示器与文本的距离为8,FramwWidth为2。
第三步实现绘制:
QStyle绘制函数的参数形式一般是
1)一个enum成员指定需要绘制的图形元素
2)一个QStyleOption指定这个图形元素怎么画和在哪儿画
3)一个QPainter
4)一个可选参数QWidget,指定了图形元素是在哪一个窗口部件上绘制
void BronzeStyle::drawPrimitive(PrimitiveElement which, const QStyleOption *option, QPainter *painter, const QWidget *widget) const{ switch(which){ case PE_IndicatorCheckBox: drawBronzeCheckBoxIndicator(option,painter); break; case PE_PanelButtonCommand: drawBronzeBevel(option,painter); break; case PE_Frame: drawBronzeFrame(option,painter); break; case PE_FrameDefaultButton: break; default: QWindowsStyle::drawPrimitive(which,option,painter,widget); }}
drawPrimitive()绘制“基本的”用户界面元素,这些元素会被几个窗口部件使用。此处,我们要实现夸张的选择指示器和渐变的按钮,所以要用自定义的绘制函数实现对PE_IndicatorCheckBox和PE_PanelButtonCommand的绘制;
void BronzeStyle::drawComplexControl(ComplexControl which, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const{ if(which==CC_SpinBox){ drawBronzeSpinBoxButton(SC_SpinBoxDown,option,painter); drawBronzeSpinBoxButton(SC_SpinBoxUp,option,painter); QRect rect=subControlRect(CC_SpinBox,option,SC_SpinBoxEditField).adjusted(-1,0,+1,0); painter->setPen(QPen(option->palette.mid(),1.0)); painter->drawLine(rect.topLeft(),rect.bottomLeft()); painter->drawLine(rect.topRight(),rect.bottomRight()); }else{ return QWindowsStyle::drawComplexControl(which,option,painter,widget); }}
因为drawComplexControl函数绘制多重辅助控制器窗口部件而且我们需要非传统的SpinBox,所以此处实现对CC_SpinBox的自定义绘制:首先绘制两个按钮,然后在文本框两侧绘制两条线。注意此函数中调用了一个叫subControlRect的函数,此函数用于确定辅助控制器窗口部件的位置(所谓的辅助控制器即为窗口部件的某一组成部分,例如checkbox的指示器)。处理鼠标事件的时候也用它查找被单击的辅助控制器窗口部件。
QRect BronzeStyle::subControlRect(ComplexControl whichControl, const QStyleOptionComplex *option, SubControl whichSubControl, const QWidget *widget) const{ if(whichControl==CC_SpinBox){ int frameWidth=pixelMetric(PM_DefaultFrameWidth,option,widget); int buttonWidth=16; switch(whichSubControl){ case SC_SpinBoxFrame: return option->rect; case SC_SpinBoxEditField: return option->rect.adjusted(+buttonWidth,+frameWidth,-buttonWidth,-frameWidth); case SC_SpinBoxDown: return visualRect(option->direction,option->rect, QRect(option->rect.x(),option->rect.y(), buttonWidth,option->rect.height())); case SC_SpinBoxUp: return visualRect(option->direction,option->rect, QRect(option->rect.right()-buttonWidth, option->rect.y(), buttonWidth,option->rect.height())); default: return QRect(); } }else{ return QWindowsStyle::subControlRect(whichControl,option,whichSubControl,widget); }}
此处重新实现了对SpinBox的各个子部件的定位:左侧Down按钮(16*height),中间EditField,右侧Up按钮(16*height)。
上述的绘制函数完成了绘制任务的分派,下面是具体的绘制过程:
void BronzeStyle::drawBronzeFrame(const QStyleOption *option, QPainter *painter) const{ painter->save(); painter->setRenderHint(QPainter::Antialiasing,true); painter->setPen(QPen(option->palette.foreground(),1.0)); painter->drawRect(option->rect.adjusted(+1,+1,-1,-1)); painter->restore();}void BronzeStyle::drawBronzeBevel(const QStyleOption *option, QPainter *painter) const{ QColor buttonColor=option->palette.button().color(); int coeff=(option->state&State_MouseOver)?115:105; QLinearGradient gradient(0,0,0,option->rect.height()); gradient.setColorAt(0.0,option->palette.light().color()); gradient.setColorAt(0.2,buttonColor.lighter(coeff)); gradient.setColorAt(0.8,buttonColor.darker(coeff)); gradient.setColorAt(1.0,option->palette.dark().color()); double penWidth=1.0; if(const QStyleOptionButton* buttonOpt=qstyleoption_cast<const QStyleOptionButton*>(option)){ if(buttonOpt->features&QStyleOptionButton::DefaultButton) penWidth=2.0; } QRect roundRect=option->rect.adjusted(+1,+1,-1,-1); if(!roundRect.isValid()) return; int diameter=12; int cx=100*diameter/roundRect.width(); int cy=100*diameter/roundRect.height(); painter->save(); painter->setPen(Qt::NoPen); painter->setBrush(gradient); painter->drawRoundRect(roundRect,cx,cy); if(option->state&(State_On|State_Sunken)){ QColor slightlyOpaqueBlack(0,0,0,63); painter->setBrush(slightlyOpaqueBlack); painter->drawRoundRect(roundRect,cx,cy); } painter->setRenderHint(QPainter::Antialiasing,true); painter->setPen(QPen(option->palette.foreground(),penWidth)); painter->setBrush(Qt::NoBrush); painter->drawRoundRect(roundRect,cx,cy); painter->restore();}
注意对按钮圆角和渐变效果的实现:首先获得需要圆角的绘制区域,然后使用渐变颜色填充绘制,最后绘制边框。其中涉及到的一些变化是:1)鼠标是否放在了按钮上?如果是,那么颜色*coeff。2)按钮是否被按下?如果是,那么使用slightlyOpaqueBlack填充;3)是否是默认按钮?如果是,那么边框加粗;
void BronzeStyle::drawBronzeSpinBoxButton(SubControl which, const QStyleOptionComplex *option, QPainter *painter) const{ PrimitiveElement arrow=PE_IndicatorArrowLeft; QRect buttonRect=option->rect; if((which==SC_SpinBoxUp)!=(option->direction==Qt::RightToLeft)){ arrow=PE_IndicatorArrowRight; buttonRect.translate(buttonRect.width()/2,0); } buttonRect.setWidth((buttonRect.width()+1)/2); QStyleOption buttonOpt(*option); painter->save(); painter->setClipRect(buttonRect,Qt::IntersectClip); if(!(option->activeSubControls&which)) buttonOpt.state&=~(State_MouseOver|State_On|State_Sunken); drawBronzeBevel(&buttonOpt,painter); QStyleOption arrowOpt(buttonOpt); arrowOpt.rect=subControlRect(CC_SpinBox,option,which).adjusted(+3,+3,-3,-3); if(arrowOpt.rect.isValid()) drawPrimitive(arrow,&arrowOpt,painter); painter->restore();}
此为绘制SpinBox按钮的过程:首先确定按钮arrow图像,然后先绘制好按钮的panel(渐变和圆角),然后再绘制arrow图像
void BronzeStyle::drawBronzeCheckBoxIndicator(const QStyleOption *option, QPainter *painter) const{ painter->save(); painter->setRenderHint(QPainter::Antialiasing,true); if(option->state&State_MouseOver){ painter->setBrush(option->palette.alternateBase()); }else{ painter->setBrush(option->palette.base()); } painter->drawRoundRect(option->rect.adjusted(+1,+1,-1,-1)); if(option->state&(State_On|State_NoChange)){ QPixmap pixmap; if(!(option->state&State_Enabled)){ pixmap.load(":/images/checkmark-disabled.png"); }else if(option->state&State_NoChange){ pixmap.load(":/images/checkmark-partial.png"); }else{ pixmap.load(":/images/checkmark.png"); } QRect pixmapRect=pixmap.rect().translated(option->rect.topLeft()).translated(+2,-6); QRect painterRect=visualRect(option->direction,option->rect,pixmapRect); if(option->direction==Qt::RightToLeft){ painter->scale(-1.0,+1.0); painterRect.moveLeft(-painterRect.right()-1); } painter->drawPixmap(painterRect,pixmap); } painter->restore();}
此为checkbox的指示器的绘制过程,首先绘制指示器的边框,然后根据checkbox是否选中,绘制选中的图像(此处为“对勾号”)。
QStyle绘制的整体流程框架就是如此,下面是一些补充:
void BronzeStyle::polish(QWidget *widget){ if(qobject_cast<QAbstractButton*>(widget)||qobject_cast<QAbstractSpinBox*>(widget)) widget->setAttribute(Qt::WA_Hover,true);}void BronzeStyle::unpolish(QWidget *widget){ if(qobject_cast<QAbstractButton*>(widget)||qobject_cast<QAbstractSpinBox*>(widget)) widget->setAttribute(Qt::WA_Hover,false);}
使得鼠标进出窗口部件都产生绘制事件。
应用:QApplication::setStyle(new BronzeStyle);
这里的代码约300行,然而要开发一种功能齐全的自定义样式是一项很大的工程,大约需要3000~5000行代码,所以对于轻量级的改动,我们一般不使用此方法,但是此框架是Qt窗口部件的基本的绘制原理。
- Qt学习笔记外观篇(二):QStyle
- 自定义QT窗口部件外观之QStyle
- Qt学习笔记外观篇(三):Qt Style Sheet
- Qt学习笔记外观篇(一):QPalette调色板
- Qt学习笔记外观篇(六):QLabel
- Qt学习笔记外观篇(七):QCheckBox
- Qt学习笔记外观篇(八):QComboBox
- Qt学习笔记外观篇(四):Qt 样式表实例
- Qt学习笔记外观篇(五):子类化窗口部件类
- Qt学习笔记(二)
- 使用QStyle定制QSlider外观
- Qt学习笔记(二)
- Qt学习笔记二
- qt学习笔记(二) QString
- Qt Model/View 学习笔记(二)
- QT学习笔记(二):QWSServer class
- qt学习笔记(二) QString
- Qt学习笔记(二)布局管理
- 用MATLAB信号处理工具箱进行FIR滤波器设计的三种方法
- ios程序启动原理与窗口、控制加载、控制器view加载、导航控制器加载
- Caused by: java.lang.OutOfMemoryError: Java heap space错误原因及解决方法
- iOS学习之UINavigationController详解与使用(三)ToolBar
- C语言中Const的用法
- Qt学习笔记外观篇(二):QStyle
- JAVA基础(11) 系统日志
- new balance 574In various strategies
- 11个实用的Apache .htaccess配置
- C++学习之道:glog
- 为网页中的图片添加水印的效果
- 移动网络趋势概析
- 微信分享到朋友圈,分享给朋友JS代码
- chmod 改变文件的权限