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

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情
三种方法来重新定义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_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窗口部件的基本的绘制原理。



解析Qt自带的Style示例 在之前的QStyle类参考一文中我们介绍到实现自定义样式有两种方法:静态方法和动态方法。我们先介绍静态方法:也就是继承已经存在的类,不是QStyle,通常是QCommonStyle或者是QWindowsStyle等等。然后实现其中的虚函数,重写自己需要修改的部分代码。至于选择哪个基类来继承完全取决用户,通常选择和自己所期望的最相近的类来继承。这里贴一个图,主要是继承层次的图: 阅读详情

相关推荐

Qt——自定义界面QStyle

1. Qt控件结构简介

QtCompany的博客 1686

QtQStyle的使用代码

利用QStyle函数设置不同风格的界面

Qt学习笔记外观(七):QCheckBox

QCheckBox作为一个常用的窗口部件,

wangyanphp的专栏 1万+

QStyle实现自绘界面项目实战(一)

通过项目实战来理解前几提到的内容

danshiming的博客 3192

QT笔记——Qt动态属性 之 unpolish()polish()

动态改变样式 即动态属性,如点击按钮可以让按钮的样式改变:从红色->蓝色->绿色 我们来了解一下这个函数: bool QObject::setProperty(const char *name, const QVariant &value) 这个函数是干什么? 对于一个基于QObject的控件来讲,可以通过setProperty来设置属性 它的参数是什么? const char *name:即自定义的属性名称,注意不要和控件的默认属性名称相同 const QVariant &

lion_cxq的博客 5192

Qt学习笔记外观(八):QComboBox

QComboBox是Qt中的下拉表单,其功能

wangyanphp的专栏 1万+

Qt QStyle详解

QStyle类是 Qt 框架中用于控制应用程序界面元素外观的一个抽象基类。这个类提供了一种方式来定制窗口部件(widgets)的绘制和行为,可以通过改变主题或风格来更改应用程序的外观,而无需修改窗口部件本身的代码。Qt包含一组QStyle子类,这些子类模仿Qt支持的不同平台的样式(QWindowsStyle、QMacStyle等)。默认情况下,这些样式内置在Qt GUI模块中。样式也可以作为插件提供。Qt的内置widget使用QStyle来执行几乎所有的绘制,确保它们看起来与等效的本地小部件完全相同。

希望能帮助大家,希望大家多多支持,你们的支持是我前进的动力。 4811

QT界面美化之QStyle

 转贴: http://www.cppblog.com/umbcom/QT中的QStyle类提供了对QT应用程序界面的封装,QT本身也是利用它应对不同平台下的界面外观的。例如 在windowxp下 你所写的程序的默认外观是通过QT自身调用QWindowsXPStyle来实现的.既然它提供了这么方便的类用来绘制独立于应用程序功能框架的程序外观,那想对自己程序的界面进行美化人,你确实很有必要去

achellies's Blog 8812

Qt QStyle

QStyle Class QStyle类是一个抽象基类,它封装了GUI外观Qt 包含一组 QStyle 子类,这些子类可以模拟Qt支持不同的平台样式(QWindowsStyle,QMacStyle等)。默认情况下,这些样式内置在 Qt GUI 模块中,样式也可以作为插件使用。 Qt 的内置小窗口部件使用 QStyle 来执行几乎所有的绘图,从而确保它们看起来完全等同于等效的本级窗口小部件。下图展示了九中不同样式的 QCombox。 Setting a Style (设定风格) 可以使用 QAppli

暖暖_的_纠结的博客 6259

QTQStyle的使用

QStyle介绍:   Qt是一个跨平台的类库,相同的界面组件在不同的操作系统上显示效果是不一样的,QStyle封装了GUI界面组件外观的抽 象类,QT内置的界面组件都使用QStyle进行绘制,以保证他们与运行平台的界面效果一致。 运行结果图: 一.相关类和函数介绍: QStyleFactory类管理着Qt的内置样式。 QStringList QStyleFactory::keys():返回平台支持的QStyle的名称列表 QStyle *QStyleFactory::create(const QStr

Vivi的城 9847

Qt学习笔记外观(六):QLabel

QLabel非常简单,且非常实用。

wangyanphp的专栏 1万+

QStyle设置界面外观和QCommonStyle继承关系图讲解和使用

简 述: 本文章讲解类容如下 讲述绘画自定义风格的 Style 的框架结构; 使用 QStyle 设置界面外观 QCommonStyle 继承关系图 如何继承 QCommonStyle 类来创建自己的自定义的类 讲解如下函数:polish(),unpolish(),drawPrimitive(),drawControl(),subElementRect(),drawCom...

7035

QT开发教程】自定义QtQStyle:改变控件外观

Qt中,QStyle类用于定义控件的外观和行为。通过自定义QStyle,可以实现独特的控件外观,增强用户界面的美观和一致性。本文将介绍如何自定义QStyle,以改变控件的外观,并展示一些实际的例子。

科技改变人类,技术成就未来 1012

Qt开发教程】样式和外观管理QStyle类详解及实战应用

QStyle 类通过提供丰富的属性和方法,使得开发者可以对控件的外观和行为进行深度定制。通过合理使用 QStyle 或 QProxyStyle 进行样式重载,可以实现多样化和美观的界面效果。结合 QSS 和样式类的功能,可以为用户提供一致且美观的用户界面体验,提高应用程序的视觉吸引力和使用体验。

科技改变人类,技术成就未来 1168

QStyle

一、描述 QStyle 类是一个抽象基类,它封装了 GUI外观。样式也可以作为插件使用。 Qt 的内置小部件使用 QStyle 来执行几乎所有的绘图,确保它们看起来与等效的原生小部件完全一样。下图显示了九种不同样式的 QComboBox。 1.1、设置样式 可以使用 QApplication::setStyle() 函数设置整个应用程序的样式。 如果没有指定样式,Qt 会为用户的平台或桌面环境选择最合适的样式。 也可以使用 QWidget::setStyle() 函数在单个小部件上设置样

友善啊,朋友的博客 4023

Qt自定义外观详解

Qt自定义外观计数方法摘要 Qt提供了三种主要方式自定义控件外观: 子类化QStyle:继承QStyle类重写绘图函数,实现全局风格定制 使用样式表:类似CSS的声明式语法快速修改外观 直接重绘控件:继承控件类重写paintEvent方法 重点说明子类化QStyle方法: 需重写drawControl/drawPrimitive等核心绘图方法 通过QStyleOption获取控件状态信息 可控制像素级细节,支持DPI适配 示例展示了完整的BronzeStyle实现,包括调色板设置、路径绘制等 高级技巧包括样

本博客分享软硬件一体化开发实战。硬件涵盖 PCB 设计、STM32 等 MCU 底层开发;软件包含 C++、Rust,Qt/QML、PyQt、Slint、TouchGFX、LVGL 各类 GUI 框架。方便软硬件开发者查阅学习。欢迎关注! 1347

QTQStyle类的使用

详细说明 QStyle类是一个抽象基类,它封装了GUI外观Qt包含一组QStyle子类,这些子类可模拟Qt支持的不同平台的样式(QWindowsStyle,QMacStyle等)。 默认情况下,这些样式内置在Qt GUI模块中。 样式也可以作为插件使用。 Qt的内置窗口小部件使用QStyle来执行几乎所有的绘图,从而确保它们看起来完全等同于等效的本机窗口小部件。 下图显示了九种不同样式的QComboBox。 主题: 设定风格 可以使用QApplication :: setStyle()函数设置整个应

希望我的博客,能帮上你解决学习中工作中所遇到的问题 2112

Qt 界面外观

Qt 界面外观

青春有梦,且行且学习! 1137

QT学习(20):QStyle和自定义样式

样式(继承自QStyle类)代表控件的绘制并封装GUI外观QStyle是一个封装了GUI外观的抽象基类。Qt使用QStyle去执行几乎所有的内置控件的绘制,确保控件外观和原生控件风格风格相同。为了自定义现有样式,需要继承QProxyStyle并重新实现所需的虚函数。QProxyStyle允许指定某个基本样式,当基本样式未指定时,将自动使用应用程序样式。前者提供对基本样式的完全控制,并且在自定义需要特定样式行为时效果最佳,而后者提供一种与平台无关的方式来自定义默认为本机平台样式的应用程序样式。

qq_45088122的博客 2772
上一篇: Qt学习笔记外观篇(一):QPalette调色板
下一篇: Qt学习笔记外观篇(三):Qt Style Sheet
wangyanphp
博客等级 码龄14年 90粉丝 51原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值