rainbowfart
rainbowfart

Reputation: 21

Calling update() doesn't trigger paintEvent()

I am trying to draw a line at the center of a widget, so I tried to call paintEvent() by using update().

However, paintEvent() does not get called. Why is that?

test_paint.h:

#ifndef TEST_PAINT_H
#define TEST_PAINT_H

#include <QPainter>
#include <QWidget>

class test_paint:public QWidget{
    Q_OBJECT
public:
    test_paint(QWidget* device){paper = device;}

protected:
    void paintEvent(QPaintEvent* e){
        QPainter p(paper);
        QPen myPen;
        myPen.setColor(Qt::red);
        myPen.setWidth(5);
        p.setPen(myPen);
        QPoint p1;
        QPoint p2;
        p1.setX(10);
        p1.setY(10);
        p2.setX(100);
        p2.setY(10);
        p.drawLine(p1,p2);
    };
    void try_painting(){
        update();
    }
public slots:
    void call_paint(){
        try_painting();
    }

private:
    QWidget* paper;
};

#endif // TEST_PAINT_H

main.cpp:

#include <QApplication>
#include <QPushButton>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QWidget>
#include <QVBoxLayout>
#include <QObject>
#include "test_paint.h"

int main(int argc, char* argv[]){
    QApplication a(argc,argv);
    QMainWindow window;
    window.setFixedSize(500, 600);

    QMenu menu("Menu");
    window.menuBar()->addMenu("Menu");

    QWidget *center = new QWidget();
    window.setCentralWidget(center);
    window.show();

    QPushButton* push = new QPushButton("test_move");
    QVBoxLayout* vlayout = new QVBoxLayout();
    vlayout -> addWidget(push);
    center->setLayout(vlayout);

    test_paint* d = new test_paint(center);

    QObject::connect(push,SIGNAL(clicked()),d,SLOT(call_paint()));
    return a.exec();
}

Upvotes: 2

Views: 172

Answers (1)

user17726418
user17726418

Reputation: 2365

You have 3 problems with your approach:

Initializing the custom widget:

You have this:

QWidget *center = new QWidget();

window.setCentralWidget(center);
window.show();

QPushButton* push = new QPushButton("test_move");
QVBoxLayout* vlayout = new QVBoxLayout();
vlayout -> addWidget(push);
center->setLayout(vlayout);

test_paint* d = new test_paint(center);

Where you initialize d with center as its parent, and you do not include it in any layout. And that is after you make window (center's parent) visible, which is a problem because a parent widget will only make its children visible if added before it itself is made visible (see my answer on How to make a child widget added in a slot visible?).

To fix that, you could either move the initialization before the parent's show:

QWidget *center = new QWidget();

window.setCentralWidget(center);

test_paint* d = new test_paint(center);

window.show();

QPushButton* push = new QPushButton("test_move");
QVBoxLayout* vlayout = new QVBoxLayout();
vlayout -> addWidget(push);
center->setLayout(vlayout);

Or, you could call d.show();.

However, this still won't work:

  • If you use the first solution, you still won't get anything visible.
  • If you go with the second, d will become a second window.

And that is because of the second problem:

Constructor:

You have the below as your constructor:

test_paint(QWidget* device) { paper = device; }

Which does not initialize the base class with the parent widget that you pass as device, thus not enabling you to benefit from parent/child relations between widgets.

This leads to improper events handling and propagation, and ultimately paintEvent not getting triggered, or causing a second window, all of which is mentioned in QWidget Documentation:

QWidget *parent = nullptr is the parent of the new widget. If it is nullptr (the default), the new widget will be a window. If not, it will be a child of parent, and be constrained by parent's geometry (unless you specify Qt::Window as window flag).

So you fix that as follows:

test_paint(QWidget* device) : QWidget(device) { paper = device; }
  base class initialization ->^^^^^^^^^^^^^^^

And again, this will turn up another problem:

Trying to draw a widget outside its paint event:

In test_paint::paintEvent, you have this:

QPainter p(paper);

Which means you want to draw/draw in top of paper, however the current paintEvent is that of d, not paper, that will cause you the following warnings:

QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setPen: Painter not active

To fix that, you should use the current widget and draw on it:

QPainter p(this);

Here's a simplified, minimal example:

#include <QApplication>
#include <QtWidgets>

class test_paint : public QWidget
{
public:
    test_paint(QWidget* parent) : QWidget(parent) {}

protected:
    void paintEvent(QPaintEvent* e) override
    {
        qDebug()<<"painting"<<size();
        QPainter p(this);

        p.fillRect(rect(), Qt::white);
    }
};

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

    QWidget w;
    test_paint d(&w);
    QPushButton push("test");
    QVBoxLayout vlayout;

    vlayout.addWidget(&push);
    w.setLayout(&vlayout);

    QObject::connect(&push, &QPushButton::clicked, [&d]()
    {
        qDebug()<<"updating";
        d.update();
    });

    w.show();

    return a.exec();
}

Result:

Screenshot of a window with a button "test" and a white rectangle

Some of the output:

painting QSize(100, 30)
painting QSize(100, 30)
painting QSize(100, 30)
updating
painting QSize(100, 30)
painting QSize(100, 30)
painting QSize(100, 30)
updating
painting QSize(100, 30)
painting QSize(100, 30)
painting QSize(100, 30)

Upvotes: 1

Related Questions