Qt学习笔记外观篇(二):QStyle

来源:互联网 发布:mac照片和iphoto 编辑:程序博客网 时间:2024/05/29 06:34
三种方法来重新定义Qt内置窗口部件的外观:
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窗口部件的基本的绘制原理。



0 0
原创粉丝点击