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_OBJECT
public:
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窗口部件的基本的绘制原理。
本文详细介绍了如何通过子类化QStyle实现Qt应用程序的自定义外观。内容涵盖QStyle的API,包括drawPrimitive(), drawControl(), drawComplexControl()等绘制函数,以及polish()和unpolish()的使用。文章还展示了如何改变窗口部件的颜色、形状和布局,如QSpinBox和QCheckBox,并提供了实现自定义样式的步骤和注意事项。最后,提到了在实际应用中设置自定义样式的代码示例。"
78246320,7206068,理解散列(Hash)表:原理与构造方法,"['查找算法', '数据结构', '算法设计']
774

被折叠的 条评论
为什么被折叠?



