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
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);
}
}
第三步实现绘制:
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);
}
}
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);
}
}
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);
}
}
上述的绘制函数完成了绘制任务的分派,下面是具体的绘制过程:
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(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();
}
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();
}
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();
}
QStyle绘制的整体流程框架就是如此,下面是一些补充:
void BronzeStyle::polish(QWidget *widget)
{
if(qobject_cast(widget)||qobject_cast(widget))
widget->setAttribute(Qt::WA_Hover,true);
}
void BronzeStyle::unpolish(QWidget *widget)
{
if(qobject_cast(widget)||qobject_cast(widget))
widget->setAttribute(Qt::WA_Hover,false);
}
应用:QApplication::setStyle(new BronzeStyle);
这里的代码约300行,然而要开发一种功能齐全的自定义样式是一项很大的工程,大约需要3000~5000行代码,所以对于轻量级的改动,我们一般不使用此方法,但是此框架是Qt窗口部件的基本的绘制原理。