user3863360
user3863360

Reputation: 198

Why the application crashes when QPushButton::paintEvent is followed by QPainter::fillRect?

I am implementing a round button by subclassing QPushButton and handling the paintEvent. I want to show the text set by the user and then draw a circle.

The application crashes at QPainter::fillRect after calling the QPushButton::paintEvent method. If QPushButton::paintEvent is not called it does not crash, but the button text is not shown.

Here is my code:

class CRoundAnimatingBtn : public QPushButton
{
    Q_OBJECT
public:
    explicit CRoundAnimatingBtn(QWidget *parent = nullptr)  : QPushButton(parent) {}

protected:
    void resizeEvent(QResizeEvent *) { setMask(QRegion(rect(), QRegion::Ellipse)); }
    void paintEvent(QPaintEvent * e) {
        QPainter painter(this);
        QPointF center(width()/2, height()/2);
        QRadialGradient radialGradient(center, qMin(width(), height())/2, center);

        QPushButton::paintEvent(e); // Application crashes if this is called

        if (isDown()) {
            radialGradient.setColorAt(0.0,Qt::transparent);
            radialGradient.setColorAt(0.79, Qt::transparent);
            radialGradient.setColorAt(0.80, Qt::gray);
            radialGradient.setColorAt(0.95, Qt::black);
            radialGradient.setColorAt(0.90, Qt::gray);
            radialGradient.setColorAt(0.91, Qt::transparent);
        } else {
            radialGradient.setColorAt(0.0,Qt::transparent);
            radialGradient.setColorAt(0.84, Qt::transparent);
            radialGradient.setColorAt(0.85, Qt::gray);
            radialGradient.setColorAt(0.90, Qt::black);
            radialGradient.setColorAt(0.95, Qt::gray);
            radialGradient.setColorAt(0.96, Qt::transparent);
        }

        painter.fillRect(rect(), radialGradient); // Application crashes here
    }
};

How to fix the crash?

Upvotes: 1

Views: 475

Answers (1)

scopchanov
scopchanov

Reputation: 8399

Cause

You first create a painter, passing a QPaintDevice *device to the constructor of QPainter, which calls QPainter::begin:

QPainter painter(this);

Then you call the base class implementation of paintEvent:

QPushButton::paintEvent(e);

which creates a new painter QStylePainter p on the same paint device, before you are done with the first one:

void QPushButton::paintEvent(QPaintEvent *)
{
    QStylePainter p(this);
    QStyleOptionButton option;
    initStyleOption(&option);
    p.drawControl(QStyle::CE_PushButton, option);
}

Finally, you try to draw with the first painter QPainter painter using:

painter.fillRect(rectangle, radialGradient);

Important: Such approach is not allowed, as the documentation of QPainter::begin clearly says:

Warning: A paint device can only be painted by one painter at a time.

Solution

Having this in mind, I would suggest you to avoid having two active painters at the same time by moving QPushButton::paintEvent(e); to the very beginning of CRoundAnimatingBtn::paintEvent (before everything else in this event handler).

Note: If you put QPushButton::paintEvent(e); at the very end of CRoundAnimatingBtn::paintEvent, the default implementation will overpaint your custom drawing and it would not be visible.

Example

Here is how the CRoundAnimatingBtn::paintEvent might look like:

void paintEvent(QPaintEvent * e) {
        QPushButton::paintEvent(e);

        QPainter painter(this);
        QPointF center(width()/2, height()/2);
        QRadialGradient radialGradient(center, qMin(width(), height())/2, center);

        ...

        painter.fillRect(rect(), radialGradient);
    }

The example produces the following result:

Window with a round highlighted button

As you see, the text is shown together with your custom drawing.

Upvotes: 2

Related Questions