QT风格(QStyle):绘制一个自定义QSpinBox(2)

按上一篇绘制自定义QSpinBox的过程,再来绘制一个QSpinBox。

设计图:

把按钮放上面

在这之前先看一下成品:

上一篇说了,绘制自定义QSpinBox实际上就是给QSpinBox中的这些原始组成元素指定好位置并绘制出来。

设计这些元素的尺寸如下:

即确定子控件位置的subControlRect()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
QRect mySpinboxStyle::subControlRect(ComplexControl whichControl,const QStyleOptionComplex *option,SubControl whichSubControl,const QWidget *widget) const
{
    if (whichControl == CC_SpinBox)
    {
        switch (whichSubControl)
        {
            case SC_SpinBoxFrame:
                return option->rect;
            case SC_SpinBoxEditField:
                return QRect(option->rect.x(), option->rect.height() * 0.4 , option->rect.width(), option->rect.height() * 0.6).adjusted(+10, +10, -10, -10);
            case SC_SpinBoxDown:
                return QRect(option->rect.width()/2,0,option->rect.width()/2,option->rect.height()*0.4);
            case SC_SpinBoxUp:
                return QRect(0,0,option->rect.width()/2,option->rect.height()*0.4);
            default:
                return QRect();
        }
    }
    else
    {
        return QProxyStyle::subControlRect(whichControl, option,whichSubControl, widget);
    }
}

然后开始绘制:

绘制从drawComplexControl()函数开始,首先绘制上下按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void mySpinboxStyle::drawBronzeSpinBoxButton(SubControl which, const QStyleOptionComplex *option,QPainter *painter,const QWidget * widget) const
{
    PrimitiveElement element;
    QRect buttonRect = option->rect;

    if (which == SC_SpinBoxUp)//上按钮
    {
        buttonRect.translate(0, 0);//translate矩形移到指定位置
        element = PE_IndicatorSpinPlus;
    }
    else if(which == SC_SpinBoxDown)
    {
        buttonRect.translate(buttonRect.width() / 2, 0);
        element = PE_IndicatorSpinMinus;
    }
    buttonRect.setWidth(buttonRect.width() / 2);
    buttonRect.setHeight(buttonRect.width() * 0.4);
    QStyleOption buttonOpt(*option);
    buttonOpt.rect = buttonRect;

    //绘制背景开始
    painter->save();
    painter->setClipRect(buttonRect);//在此范围内绘制背景
    if (option->activeSubControls != which)//不是当前活动的子控件
    {
        buttonOpt.state &= ~(State_MouseOver/*在鼠标下面*/ | State_On/*按下*/ | State_Sunken/*凹陷*/);
    }
    QLinearGradient gradient(0, 0, 0, buttonOpt.rect.height());//y轴使用渐变
    gradient.setColorAt(0.0, QColor("#5ee7df"));
    gradient.setColorAt(1.0, QColor("#b490ca"));
    painter->setPen(Qt::NoPen);
    painter->setBrush(gradient);
    QRect roundRect = buttonOpt.rect.adjusted(+1, +1, -1, -1);
    int diameter = 12;
    int cx = 100 * diameter / buttonOpt.rect.width();
    int cy = 100 * diameter / buttonOpt.rect.height();

    painter->drawRoundRect(roundRect, cx, cy);//绘制圆角矩形
    if (buttonOpt.state & (State_On | State_Sunken))//按下时,绘制一层暗色
    {
        QColor slightlyOpaqueBlack(0, 0, 0, 63);
        painter->setBrush(slightlyOpaqueBlack);
        painter->drawRoundRect(roundRect, cx, cy);//绘制圆角矩形
    }
    //绘制背景结束
    painter->restore();

    //绘制图标
    QStyleOption arrowOpt(buttonOpt);
    QRect subRect = subControlRect(CC_SpinBox, option, which);
    arrowOpt.rect = subRect.adjusted(+subRect.width() * 0.3, +subRect.height() * 0.3,
                                     -subRect.width() * 0.3, -subRect.height() * 0.3);
    drawPrimitive(element, &arrowOpt, painter);
}

这个函数用来绘制上下按钮的背景和按钮上面的图标

背景使用了线性渐变,如果没有好的渐变配色方案可以参考这里:收藏 | 四个免费的渐变配色网站!

这里绘制的时候使用了adjusted(+1, +1, -1, -1),即给四周留下了一点空间显得不拥挤。绘制完上下按钮的背景时的效果:

然后是绘制按钮的图标,

1
2
3
    QRect subRect = subControlRect(CC_SpinBox, option, which);
    arrowOpt.rect = subRect.adjusted(+subRect.width() * 0.3, +subRect.height() * 0.3,
                                     -subRect.width() * 0.3, -subRect.height() * 0.3);

这里从subControlRect()获取上下按钮的范围之后再次压缩了范围

在此范围内绘制图标

这里设置了上下按钮分别使用PE_IndicatorSpinPlus / PE_IndicatorSpinMinus,默认是代表加减号的意思。

如果使用默认设置,在drawPrimitive()中直接调用 QProxyStyle::drawPrimitive(which, option, painter, widget);效果:

不太好看,我们按设计图给它画上三角形。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void mySpinboxStyle::drawPrimitive(PrimitiveElement which, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
{
    switch (which)
    {
        case PE_IndicatorSpinPlus:
        {
            painter->save();
            painter->translate(option->rect.x(),option->rect.y());
            QPainterPath drawtriangle;  //画三角形
            drawtriangle.moveTo(0, option->rect.height());//左下角
            drawtriangle.lineTo(option->rect.width()/2, 0);//第二点坐标为(width/2,width/2)
            drawtriangle.lineTo(option->rect.width(), option->rect.height());//右下角,第三坐标(width, height),移动到右下角结束点,整体形成一个闭合路径
            drawtriangle.lineTo(0, option->rect.height());
            painter->setPen(QPen(QColor("#128bf1"), 2));
            painter->drawPath(drawtriangle);  //绘制出图形
            painter->restore();
        }
        break;
        case PE_IndicatorSpinMinus:
        {
            painter->save();
            painter->translate(option->rect.x(),option->rect.y());
            QPainterPath drawtriangle;  //画三角形
            drawtriangle.moveTo(0,0);
            drawtriangle.lineTo(option->rect.width()/2,option->rect.height());
            drawtriangle.lineTo(option->rect.width(),0);
            drawtriangle.lineTo(0,0);
            painter->setPen(QPen(QColor("#128bf1"), 2));
            painter->drawPath(drawtriangle);  //绘制出图形
            painter->restore();
        }
        break;
        default:
            QProxyStyle::drawPrimitive(which, option, painter, widget);
    }
}

这里注意:

painter指针是绘制一开始起从drawComplexControl()函数传过来的,每次绘制前设置painter->save();保存设置绘制完成painter->restore();恢复设置,到drawPrimitive()绘制的原点还是在上下按钮的(0,0)点,设置painter->translate(option->rect.x(),option->rect.y());是将绘制的原点设为绘制图标的原点,就比较方便

下一步是给SC_SpinBoxEditField加上边框:

1
2
3
4
5
6
7
8
9
10
11
12
13
        QRect rect = subControlRect(CC_SpinBox, option,SC_SpinBoxEditField).adjusted(-10, -10, +10, +10);
        painter->save();
        QLinearGradient gradient(0, 0, rect.width(), rect.height());//x轴使用渐变
        gradient.setColorAt(0.0, QColor("#fa709a"));
        gradient.setColorAt(1.0, QColor("#fee140"));
        painter->setPen(Qt::NoPen);
        painter->setBrush(gradient);
        QRect roundRect = rect.adjusted(+1, +1, -1, -1);
        int diameter = 12;
        int cx = 100 * diameter / rect.width();
        int cy = 100 * diameter / rect.height();
        painter->drawRoundRect(roundRect, cx, cy);//绘制圆角矩形
        painter->restore();

此时的效果:

到这基本完成,然而编辑框里的数字太小了,这里可以设置它的字体并设置文本居中:

1
2
3
4
    ui->spinBox->setAlignment(Qt::AlignCenter);
    QFont f;
    f.setPixelSize(24);
    ui->spinBox->setFont(f);

最终效果:

样式完整代码:

.h文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef MYSPINBOXSTYLE_H
#define MYSPINBOXSTYLE_H

#include <QProxyStyle>

class mySpinboxStyle : public QProxyStyle
{
public:
    mySpinboxStyle();
    void drawComplexControl(ComplexControl which,const QStyleOptionComplex *option,QPainter *painter,const QWidget *widget = nullptr) const override;
    void drawBronzeSpinBoxButton(SubControl which, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const;
    QRect subControlRect(ComplexControl whichControl,const QStyleOptionComplex *option,SubControl whichSubControl,const QWidget *widget = nullptr) const override;
    void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = nullptr) const override;
};

#endif // MYSPINBOXSTYLE_H

.cpp文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#include "myspinboxstyle.h"
#include <QPainter>
#include <QStyleOptionComplex>
#include <QDebug>

mySpinboxStyle::mySpinboxStyle()
{

}

QRect mySpinboxStyle::subControlRect(ComplexControl whichControl,const QStyleOptionComplex *option,SubControl whichSubControl,const QWidget *widget) const
{
    if (whichControl == CC_SpinBox)
    {
        switch (whichSubControl)
        {
            case SC_SpinBoxFrame:
                return option->rect;
            case SC_SpinBoxEditField:
                return QRect(option->rect.x(), option->rect.height() * 0.4 , option->rect.width(), option->rect.height() * 0.6).adjusted(+10, +10, -10, -10);
            case SC_SpinBoxDown:
                return QRect(option->rect.width()/2,0,option->rect.width()/2,option->rect.height()*0.4);
            case SC_SpinBoxUp:
                return QRect(0,0,option->rect.width()/2,option->rect.height()*0.4);
            default:
                return QRect();
        }
    }
    else
    {
        return QProxyStyle::subControlRect(whichControl, option,whichSubControl, widget);
    }
}

void mySpinboxStyle::drawComplexControl(ComplexControl which,const QStyleOptionComplex *option,QPainter *painter,const QWidget *widget) const
{
    if (which == CC_SpinBox)
    {
        drawBronzeSpinBoxButton(SC_SpinBoxDown, option, painter,widget);
        drawBronzeSpinBoxButton(SC_SpinBoxUp, option, painter,widget);

        QRect rect = subControlRect(CC_SpinBox, option,SC_SpinBoxEditField).adjusted(-10, -10, +10, +10);
        painter->save();
        QLinearGradient gradient(0, 0, rect.width(), rect.height());//x轴使用渐变
        gradient.setColorAt(0.0, QColor("#fa709a"));
        gradient.setColorAt(1.0, QColor("#fee140"));
        painter->setPen(Qt::NoPen);
        painter->setBrush(gradient);
        QRect roundRect = rect.adjusted(+1, +1, -1, -1);
        int diameter = 12;
        int cx = 100 * diameter / rect.width();
        int cy = 100 * diameter / rect.height();
        painter->drawRoundRect(roundRect, cx, cy);//绘制圆角矩形
        painter->restore();
    }
    else
    {
        return QProxyStyle::drawComplexControl(which, option, painter,widget);
    }
}

void mySpinboxStyle::drawBronzeSpinBoxButton(SubControl which, const QStyleOptionComplex *option,QPainter *painter,const QWidget * widget) const
{
    PrimitiveElement element;
    QRect buttonRect = option->rect;

    if (which == SC_SpinBoxUp)//上按钮
    {
        buttonRect.translate(0, 0);//translate矩形移到指定位置
        element = PE_IndicatorSpinPlus;//PE_IndicatorSpinPlus
    }
    else if(which == SC_SpinBoxDown)
    {
        buttonRect.translate(buttonRect.width() / 2, 0);
        element = PE_IndicatorSpinMinus;
    }
    buttonRect.setWidth(buttonRect.width() / 2);
    buttonRect.setHeight(buttonRect.width() * 0.4);
    QStyleOption buttonOpt(*option);
    buttonOpt.rect = buttonRect;

    //绘制背景开始
    painter->save();
    painter->setClipRect(buttonRect);//在此范围内绘制背景
    if (option->activeSubControls != which)//不是当前活动的子控件
    {
        buttonOpt.state &= ~(State_MouseOver/*在鼠标下面*/ | State_On/*按下*/ | State_Sunken/*凹陷*/);
    }
    QLinearGradient gradient(0, 0, 0, buttonOpt.rect.height());//y轴使用渐变
    gradient.setColorAt(0.0, QColor("#5ee7df"));
    gradient.setColorAt(1.0, QColor("#b490ca"));
    painter->setPen(Qt::NoPen);
    painter->setBrush(gradient);
    QRect roundRect = buttonOpt.rect.adjusted(+1, +1, -1, -1);
    int diameter = 12;
    int cx = 100 * diameter / buttonOpt.rect.width();
    int cy = 100 * diameter / buttonOpt.rect.height();

    painter->drawRoundRect(roundRect, cx, cy);//绘制圆角矩形
    if (buttonOpt.state & (State_On | State_Sunken))//按下时,绘制一层暗色
    {
        QColor slightlyOpaqueBlack(0, 0, 0, 63);
        painter->setBrush(slightlyOpaqueBlack);
        painter->drawRoundRect(roundRect, cx, cy);//绘制圆角矩形
    }
    //绘制背景结束
    painter->restore();

    //绘制图标
    QStyleOption arrowOpt(buttonOpt);
    QRect subRect = subControlRect(CC_SpinBox, option, which);
    arrowOpt.rect = subRect.adjusted(+subRect.width() * 0.3, +subRect.height() * 0.3,
                                     -subRect.width() * 0.3, -subRect.height() * 0.3);
    drawPrimitive(element, &arrowOpt, painter);
}

void mySpinboxStyle::drawPrimitive(PrimitiveElement which, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
{
    switch (which)
    {
        case PE_IndicatorSpinPlus:
        {
            painter->save();
            painter->translate(option->rect.x(),option->rect.y());
            QPainterPath drawtriangle;  //画三角形
            drawtriangle.moveTo(0, option->rect.height());//左下角
            drawtriangle.lineTo(option->rect.width()/2, 0);//第二点坐标为(width/2,width/2)
            drawtriangle.lineTo(option->rect.width(), option->rect.height());//右下角,第三坐标(width, height),移动到右下角结束点,整体形成一个闭合路径
            drawtriangle.lineTo(0, option->rect.height());
            painter->setPen(QPen(QColor("#128bf1"), 2));
            painter->drawPath(drawtriangle);  //绘制出图形
            painter->restore();
        }
        break;
        case PE_IndicatorSpinMinus:
        {
            painter->save();
            painter->translate(option->rect.x(),option->rect.y());
            QPainterPath drawtriangle;  //画三角形
            drawtriangle.moveTo(0,0);
            drawtriangle.lineTo(option->rect.width()/2,option->rect.height());
            drawtriangle.lineTo(option->rect.width(),0);
            drawtriangle.lineTo(0,0);
            painter->setPen(QPen(QColor("#128bf1"), 2));
            painter->drawPath(drawtriangle);  //绘制出图形
            painter->restore();
        }
        break;
        default:
            QProxyStyle::drawPrimitive(which, option, painter, widget);
    }
}