如何在使用样式表时将 QLinearGradient 应用于圆形 QWidget 边框?

huangapple go评论74阅读模式
英文:

How to apply QLinearGradient on a round QWidget border using style sheet?

问题

以下是翻译好的代码部分:

#include <QApplication>
#include <QPushButton>
#include <QTimer>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QPushButton *btn = new QPushButton("动画按钮");

    float A = 0.99, b = 0.01;
    QTimer *t = new QTimer();
    int border = 1;

    btn->setMinimumSize(200, 50);

    btn->connect(t, &QTimer::timeout, [btn, &A, &b, &border]() {
        if (border == 2 || border == 3) {
            if (b + 0.01 > 1) {
                b = 0;
                border = (border + 1) % 4 + 1;
            }
            b += 0.01;
        } else {
            if (A - 0.01 < 0.01) {
                A = 1;
                border = (border % 4) + 1;
            }
            A -= 0.01;
        }

        switch (border) {
            case 1:
                btn->setStyleSheet(QString("border: 2px solid white;"
                                           "border-top: 2px solid qlineargradient(x0:0, x2:1,"
                                           "stop: 0 green,"
                                           "stop: %1 green,"
                                           "stop: %2 white,"
                                           "stop: 1 white);"
                                           "background: black;").arg(A).arg(A + 0.01));
                break;
            case 2:
                btn->setStyleSheet(QString("border: 2px solid white;"
                                           "border-left: 2px solid qlineargradient(y0:0, y2:1,"
                                           "stop: 0 white,"
                                           "stop: %1 white,"
                                           "stop: %2 green,"
                                           "stop: 1 green);"
                                           "background: black;").arg(b).arg(b + 0.01));

                break;
            case 3:
                btn->setStyleSheet(QString("border: 2px solid white;"
                                           "border-bottom: 2px solid qlineargradient(x0:0, x2:1,"
                                           "stop: 0 white,"
                                           "stop: %1 white,"
                                           "stop: %2 green,"
                                           "stop: 1 green);"
                                           "background: black;").arg(b).arg(b + 0.01));
                break;
            case 4:
                btn->setStyleSheet(QString("border: 2px solid white;"
                                           "border-right: 2px solid qlineargradient(y0:0, y2:1,"
                                           "stop: 0 green,"
                                           "stop: %1 green,"
                                           "stop: %2 white,"
                                           "stop: 1 white);"
                                           "background: black;").arg(A).arg(A + 0.01));
                break;
        }
    });

    t->start(50);

    btn->show();

    return a.exec();
}

如果您需要进一步的帮助,请随时提出。

英文:

Context:

I am trying to animate a timer progress on a QPushButton border, using style sheet and QLinearGradient.

Here's how I'm doing it:

#include &lt;QApplication&gt;
#include &lt;QPushButton&gt;
#include &lt;QTimer&gt;
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton *btn = new QPushButton(&quot;Animation Button&quot;);
float A=0.99,b=0.01;
QTimer *t = new QTimer();
int border=1;
btn-&gt;setMinimumSize(200,50);
btn-&gt;connect(t,&amp;QTimer::timeout,[btn,&amp;A,&amp;b,&amp;border](){
if(border==2 || border==3)
{
if(b+0.01&gt;1)
{
b=0;
border=border++%4+1;
}
b+=0.01;
}
else
{
if(A-0.01&lt;0.01)
{
A=1;
border=border%4+1;
}
A-=0.01;
}
switch (border)
{
case 1:
btn-&gt;setStyleSheet(QString(&quot;border: 2px solid white;&quot;
//&quot;border-radius: 5px;&quot;
&quot;border-top: 2px solid qlineargradient(x0:0, x2:1,&quot;
&quot;stop: 0 green,&quot;
&quot;stop: %1 green,&quot;
&quot;stop: %2 white,&quot;
&quot;stop: 1 white);&quot;
&quot;background: black;&quot;).arg(A).arg(A+0.01));
break;
case 2:
btn-&gt;setStyleSheet(QString(&quot;border: 2px solid white;&quot;
//&quot;border-radius: 5px;&quot;
&quot;border-left: 2px solid qlineargradient(y0:0, y2:1,&quot;
&quot;stop: 0 white,&quot;
&quot;stop: %1 white,&quot;
&quot;stop: %2 green,&quot;
&quot;stop: 1 green);&quot;
&quot;background: black;&quot;).arg(b).arg(b+0.01));
break;
case 3:
btn-&gt;setStyleSheet(QString(&quot;border: 2px solid white;&quot;
//&quot;border-radius: 5px;&quot;
&quot;border-bottom: 2px solid qlineargradient(x0:0, x2:1,&quot;
&quot;stop: 0 white,&quot;
&quot;stop: %1 white,&quot;
&quot;stop: %2 green,&quot;
&quot;stop: 1 green);&quot;
&quot;background: black;&quot;).arg(b).arg(b+0.01));
break;
case 4:
btn-&gt;setStyleSheet(QString(&quot;border: 2px solid white;&quot;
//&quot;border-radius: 5px;&quot;
&quot;border-right: 2px solid qlineargradient(y0:0, y2:1,&quot;
&quot;stop: 0 green,&quot;
&quot;stop: %1 green,&quot;
&quot;stop: %2 white,&quot;
&quot;stop: 1 white);&quot;
&quot;background: black;&quot;).arg(A).arg(A+0.01));
break;
}
});
t-&gt;start(50);
btn-&gt;show();
return a.exec();
}

Here's the logic I came up with, to create this animation:
>A and b are values that control green and white color range in QLinearGradient, green is the progress, and white is the actual border color. The basic idea, is that I'm making the white recede, and the green advance, or vice versa. There is a small fraction between their values to avoid the gradient effect, hence why I 'm using b and b+0.1 for example. If A or b reach the limit of a range (1 or 0), they reset to the default . border is a variable that allows me to iterate through the 4 borders in my switch case, just a simple circular counter. All of this loops using a QTimer.

Here's the result:

如何在使用样式表时将 QLinearGradient 应用于圆形 QWidget 边框?

Problem:

I need round borders, but when I use them, it looks like this:

如何在使用样式表时将 QLinearGradient 应用于圆形 QWidget 边框?

The borders seem to form another side of the button on their own, as if with round borders, the button now has 8 sides, because the animation on the round borders seem to be synced with the border that's currently being animated, instead of being animated when the animation reaches them. It also looks like a mirror effect.

Note: This is just an observation to explain how it looks, not what actually happens, here's how it looks if I apply a green color without QLinearGradient while using round borders:

如何在使用样式表时将 QLinearGradient 应用于圆形 QWidget 边框?

How do I animate the round borders the same way as the not round ones?

I'm open to alternatives to the way I'm currently animating the borders using C++, and Qt Widgets, if my current one is too messy or there is a much simpler one to achieve the same result.

答案1

得分: 0

Sure, here's the translated content:

由于使用样式表来设置边框半径会产生意外效果,您可以在不使用样式表的情况下使您的小部件边框变圆,并改用paintEvent,以下是方法:

您可以绘制一个圆角矩形,颜色与小部件父级的背景相同,这就好像它隐藏了那些锐利的角落,并使它们看起来圆润。

这种方法的一个缺点是它限制了角落可以有多圆,因为如果绘图器绘制的圆角矩形太圆,它将遮蔽小部件本身或导致锐利的边缘。

通过增加边框尺寸可以在一定程度上解决这个问题,以获得更多空间,从而使在使用绘图器时增加边框半径成为可能,而不会破坏小部件边缘。

使用这种方法有一个好处,它可以防止小部件背景突出边框。

以下是一个从QPushButton派生的示例子类,具有自定义的paintEvent

class button : public QPushButton
{
    Q_OBJECT

public:
    button (QWidget *parent = nullptr) : QPushButton(parent) {}

protected:
    void paintEvent(QPaintEvent *event) override
    {
        QPushButton::paintEvent(event);

        QRectF r = rect();
        r.adjust(-1,-1,+1,+1);

        QPainter p(this);
        QColor color = parentWidget()->palette().brush(QPalette::Window).color();
        p.setPen(QPen(color,2));
        p.setRenderHint(QPainter::Antialiasing);
        p.drawRoundedRect(r, 6, 6);
    }
};

以下是结果,其中底部按钮是普通的QPushButton,没有边框半径,只是为了增加区别的可见性:

如何在使用样式表时将 QLinearGradient 应用于圆形 QWidget 边框?

英文:

Since setting border radius using stylesheets has unwanted effects, you could make your widget borders round without it, and use paintEvent instead, here's how:

You could draw a rounded rect with a color same as the widget parent's background, this acts as if it's hiding those sharp corners, and make them look round.

One downside of this method is that it limits how round the corners can get, because if the painter draws a too rounded rect, it will mask the widget itself or cause sharp edges.

It is possible to slightly get around that by increasing the border size, to get more space, thus, making it possible to increase the border radius when using a painter without ruining the widget edges.

There is an upside for using this method. It prevents the widget background from poking out the border.

Here's an example of subclass derived from QPushButton with a custom paintEvent:

class button : public QPushButton
{
Q_OBJECT
public:
button (QWidget *parent = nullptr) : QPushButton(parent) {}
protected:
void paintEvent(QPaintEvent *event) override
{
QPushButton::paintEvent(event);
QRectF r = rect();
r.adjust(-1,-1,+1,+1);
QPainter p(this);
QColor color = parentWidget()-&gt;palette().brush(QPalette::Window).color();
p.setPen(QPen(color,2));
p.setRenderHint(QPainter::Antialiasing);
p.drawRoundedRect(r, 6, 6);
}
};

Here's the result, where the bottom button is a normal QPushButton with no border radius, just to increase the difference visibility:

如何在使用样式表时将 QLinearGradient 应用于圆形 QWidget 边框?

答案2

得分: -1

以下是您提供的代码的翻译:

对于这些类型的特性,QML提供了一个非常灵活的工具集

我在QML中实现了我认为你在寻找的功能

Canvas {
    id: border_animation
    property real progress: 0
    onProgressChanged: {
        border_animation.requestPaint()
    }
    anchors.centerIn: parent
    height: parent.height*0.10
    width: parent.width*0.65
    z: 2
    Text {
        text: "启动定时器"
        color: "white"
        font.bold: true
        font.pointSize: 15
        anchors.centerIn: parent
    }
    NumberAnimation on progress {
        id: startTimer
        from: 0
        to: 1
        running: false
        duration: 1000
        onFinished: {
            startTimer.start()
        }
    }
    onPaint: {
        var ctx = border_animation.getContext('2d')

        ctx.strokeStyle = "green"
        ctx.fillStyle = "black"
        ctx.lineWidth = 2

        ctx.beginPath()
        ctx.rect(0,0,width,height)
        ctx.fill()

        var Lcenterx = width*0.25
        var Lcentery = height*0.50
        var Rcenterx = width*0.75
        var Rcentery = height*0.50
        var radius = height*0.40

        //每个部分占边框的25%,所以我们按这种方式拆分
        if(progress<=0.25){
            var topProgress = progress/0.25
            var rightProgress = 0
            var bottomProgress = 0
            var leftProgress = 0
        }else if(progress<=0.50){
            topProgress = 1
            rightProgress = (progress-0.25)/0.25
            bottomProgress = 0
            leftProgress = 0
        }else if(progress<=0.75){
            topProgress = 1
            rightProgress = 1
            bottomProgress = (progress-0.50)/0.25
            leftProgress = 0
        }else{
            topProgress = 1
            rightProgress = 1
            bottomProgress = 1
            leftProgress = (progress-0.75)/0.25
        }

        //顶部线
        ctx.strokeStyle = "orange"
        ctx.beginPath();
        ctx.moveTo(Lcenterx, Lcentery-radius);
        ctx.lineTo(Lcenterx + (Rcenterx - Lcenterx)*topProgress, Rcentery-radius);
        ctx.stroke();

        ctx.strokeStyle = "yellow"
        ctx.beginPath();
        ctx.arc(Rcenterx, Rcentery, radius, 1.5*Math.PI, 1.5*Math.PI + Math.PI * rightProgress);
        ctx.stroke();

        //底部线
        ctx.strokeStyle = "cyan"
        ctx.beginPath();
        ctx.moveTo(Rcenterx, Lcentery+radius);
        ctx.lineTo(Rcenterx - (Rcenterx - Lcenterx)*bottomProgress, Rcentery+radius);
        ctx.stroke();

        ctx.strokeStyle = "green"
        ctx.beginPath();
        ctx.arc(Lcenterx, Lcentery, radius, 0.5*Math.PI, 0.5*Math.PI + Math.PI * leftProgress);
        ctx.stroke();
    }
    MouseArea {
        anchors.fill: parent
        onClicked: {
            startTimer.start()
        }
    }
}

请注意,我只翻译了您提供的QML代码部分。如果您有其他需要,请随时告诉我。

英文:

For these types of features, QML provides a very flexible tool set.

I've achieved what I think you are looking for in QML:

Canvas {
id: border_animation
property real progress: 0
onProgressChanged: {
border_animation.requestPaint()
}
anchors.centerIn: parent
height: parent.height*0.10
width: parent.width*0.65
z: 2
Text {
text: &quot;start timer&quot;
color: &quot;white&quot;
font.bold: true
font.pointSize: 15
anchors.centerIn: parent
}
NumberAnimation on progress {
id: startTimer
from: 0
to: 1
running: false
duration: 1000
onFinished: {
startTimer.start()
}
}
onPaint: {
var ctx = border_animation.getContext(&#39;2d&#39;)
ctx.strokeStyle = &quot;green&quot;
ctx.fillStyle = &quot;black&quot;
ctx.lineWidth = 2
ctx.beginPath()
ctx.rect(0,0,width,height)
ctx.fill()
var Lcenterx = width*0.25
var Lcentery = height*0.50
var Rcenterx = width*0.75
var Rcentery = height*0.50
var radius = height*0.40
//each section is 25% of the border so we break it up that way
if(progress&lt;=0.25){
var topProgress = progress/0.25
var rightProgress = 0
var bottomProgress = 0
var leftProgress = 0
}else if(progress&lt;=0.50){
topProgress = 1
rightProgress = (progress-0.25)/0.25
bottomProgress = 0
leftProgress = 0
}else if(progress&lt;=0.75){
topProgress = 1
rightProgress = 1
bottomProgress = (progress-0.50)/0.25
leftProgress = 0
}else{
topProgress = 1
rightProgress = 1
bottomProgress = 1
leftProgress = (progress-0.75)/0.25
}
//top line
ctx.strokeStyle = &quot;orange&quot;
ctx.beginPath();
ctx.moveTo(Lcenterx, Lcentery-radius);
ctx.lineTo(Lcenterx + (Rcenterx - Lcenterx)*topProgress, Rcentery-radius);
ctx.stroke();
ctx.strokeStyle = &quot;yellow&quot;
ctx.beginPath();
ctx.arc(Rcenterx, Rcentery, radius, 1.5*Math.PI, 1.5*Math.PI + Math.PI * rightProgress);
ctx.stroke();
//bottom line
ctx.strokeStyle = &quot;cyan&quot;
ctx.beginPath();
ctx.moveTo(Rcenterx, Lcentery+radius);
ctx.lineTo(Rcenterx - (Rcenterx - Lcenterx)*bottomProgress, Rcentery+radius);
ctx.stroke();
ctx.strokeStyle = &quot;green&quot;
ctx.beginPath();
ctx.arc(Lcenterx, Lcentery, radius, 0.5*Math.PI, 0.5*Math.PI + Math.PI * leftProgress);
ctx.stroke();
}
MouseArea {
anchors.fill: parent
onClicked: {
startTimer.start()
}
}
}

如何在使用样式表时将 QLinearGradient 应用于圆形 QWidget 边框?

如何在使用样式表时将 QLinearGradient 应用于圆形 QWidget 边框?

huangapple
  • 本文由 发表于 2023年5月15日 00:12:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76248479.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定