Can't use QPainter in paintEvent of custom QWidget (Qt5) - c++

The error in question is as follows:
QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setPen: Painter not active
QPainter::setFont: Painter not active
After looking at a ton of online forum posts that all came down to people making the same mistake of trying to paint on their widgets outside of paintEvent(), I've been unable to have any luck. I tried drawing directly on this custom QWidget subclass, I've tried making a child QWidget and drawing on that. Can someone please show me what thing I'm (probably obviously to someone else) doing wrong?
Thanks in advance.
Header:
#ifndef TEXTDISPLAY_H
#define TEXTDISPLAY_H
#include <QWidget>
class TextDisplay : public QWidget
{
Q_OBJECT
public:
TextDisplay(QString text, QString fontFamily = "Helvetica", int fontSize = 20,
int fontColor = Qt::black, QWidget* parent = 0);
protected:
void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE;
void resizeEvent(QResizeEvent *e) Q_DECL_OVERRIDE;
private:
QString text;
QString fontFamily;
int fontSize;
int fontColor;
};
#endif // TEXTDISPLAY_H
Cpp:
#include "textdisplay.h"
#include <QPainter>
TextDisplay::TextDisplay(QString text, QString fontFamily, int fontSize,
int fontColor, QWidget* parent)
: QWidget(parent), text(text), fontFamily(fontFamily),
fontSize(fontSize), fontColor(fontColor)
{
this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
}
void TextDisplay::resizeEvent(QResizeEvent*) {
paintEvent(NULL);
}
void TextDisplay::paintEvent(QPaintEvent *) {
QPainter painter(this);
painter.setPen(fontColor);
painter.setFont(QFont(fontFamily, fontSize));
QRect rect(QPoint(0, 0), this->size());
QRect bound;
QTextOption options;
options.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
painter.drawText(rect, Qt::TextWordWrap | Qt::TextWrapAnywhere, text, &bound);
this->setMinimumHeight(bound.size().height());
}
Edit with solution:
Thanks to both responders - I needed to do two things to get it working:
a) Get rid of paintEvent(NULL). I got rid of the resizeEvent override as well, it was unnecessary as suggested.
b) Set a minimum size for the widget. Without this, the paintEvent was never called by Qt.

If you want to schedule a redraw for your widget, just call update().
If you need an immediate repaint (which you almost never need) you can call repaint() instead.
Calling paintEvent() directly will not work -- Qt needs to prepare the backing store to handle the painting, so you can't bypass the update mechanism. Instead, call the methods above, which will result in a call to paintEvent() (if the widget is visibile, not occluded, etc.etc.etc.).
Note also that you should not need to reimplement resizeEvent() just to update your widget. That should already be done for you by Qt...

Related

qt 5 how to prevent repaint whole window if i want repaint only 1 widget

Minimal code example:
class Boo : public QPushButton{
public:
Boo(QWidget* w) : QPushButton(w){}
virtual void paintEvent(QPaintEvent* ev){
qDebug()<<__FUNCTION__<<this->objectName();
}
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
virtual void paintEvent(QPaintEvent* ev){
qDebug()<<__FUNCTION__;
}
private:
QTimer t;
Ui::MainWindow *ui;
Boo *b1, *b2;
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
b1 = new Boo(this);
b1->setObjectName("O1");
b2 = new Boo(this);
b2->setObjectName("O2");
connect(&t, &QTimer::timeout, this, [this](){
b2->repaint();
});
t.start();
t.setInterval(10);
}
Outputs infinitely: MainWindow::paintEvent Boo::paintEvent "O1" Boo::paintEvent "O2"
but i call repaint only for button b2. Using "QWidget::repaint(int x, int y, int w, int h)" or :QWidget::update()" also invoke repaint for main window.
Problem exists on Qt5.12/5.15 and windows11, but looks like general qt bug.
This issue cause high GPU consumption in our more complex GUI application.
Ok. I found the reason myself, after add:
...
b1->setGeometry(15,15, 12, 12);
...
b2->setGeometry(35,35, 14, 14);
...
virtual void paintEvent(QPaintEvent* ev){
qDebug()<<__FUNCTION__<<ev->rect();
}
ouput says, that actual repaint is done only for button region:
MainWindow::paintEvent QRect(35,35 14x14)
Boo::paintEvent "O2" QRect(0,0 14x14)
Important thing: ivoke setGeometry for button, not just add as child. In other case (usable only for toy example, ofcourse) repaint button with unset geometry will lead to repaint every button with unset geometry.
Qt repaints by default all parents too, to allow widgets being partially transparent. If Qt doesn't repaint the parent on an update, some pixels of the previous paintEvent may still be visible.
Qt provides two methods to optimise this behaviour when no transparent background is needed:
Set autoFillBackground. This option is preferred in case of an opaque background color.
Set Qt::WA_OpaquePaintEvent to indicate that you will paint the whole widget (with opaque colors).
More information
Qt documentation: QWidget: Transparency and Double Buffering

moving child widget inside parent widget without repainting

I am trying to make a child widget to be aligned to the right-bottom corner of a parent widget. The repainting of the child widget is costly so I want to avoid it. Because I am only moving the child widget with a static content, theoretically no repainting is necessary. But I do not know how to achieve that. I react to resizeEvent(), in which I update the child's position to the corner. I have set the widget attributes (trial-error method) to minimize the paint events as far as I was able to. But there are still some repaint events called for the child when the parent is being resized - when enlarged and shrinked as well. When being resized slowly and only in one direction (x or y), it seems only 1 pixel wide band is repainted. When resized faster and in both directions at the same time, the child widget is repainted all. Is it possible to tweak the code to avoid the repainting of the child widget completely? I would like to avoid write manual double-buffering algorithm for this case myself, I hope Qt is able to solve this somehow for me. Is it?
#include <QApplication>
#include <QDebug>
#include <QPainter>
#include <QPaintEvent>
#include <QWidget>
class ChildWidget : public QWidget
{
public:
ChildWidget(QWidget *parent) : QWidget(parent)
{
setAttribute(Qt::WA_StaticContents);
setAttribute(Qt::WA_OpaquePaintEvent);
}
protected:
void paintEvent(QPaintEvent *event) override
{
qDebug() << "paintEvent" << event->rect();
QPainter painter(this);
painter.fillRect(event->rect(), QBrush(QColor("red")));
}
};
class ParentWidget : public QWidget
{
public:
ParentWidget()
{
m_childWidget = new ChildWidget(this);
m_childWidget->resize(100, 100);
setAttribute(Qt::WA_StaticContents);
resize(200, 200);
}
protected:
void resizeEvent(QResizeEvent *event) override
{
QWidget::resizeEvent(event);
m_childWidget->move(width() - 100, height() - 100);
}
private:
ChildWidget *m_childWidget;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ParentWidget w;
w.show();
return a.exec();
}
Note: In my final use case, this parent widget is intended to be covered completely by several non-overlapping child widgets and each of these child widgets will be opaque and will take care of painting its whole area. I.e. no special handling of alpha channel is needed. This might be important information for setting the widget attributes.
Note: I tried a trick, not moving the child immediately, but postpone it with QTimer::singleShot(0, [this]{m_childWidget->move(width() - 100, height() - 100);}); This prevents repainting during parent widget resizing but only in cases when the size of the parent grows. When it shrinks, it is the same as before.

How to draw when holding down the mouse button?

I am trying to create a simple painting application - it is just supposed to draw when you click and drag a cursor (just like Paint). I know I have to use QPainter but how can I handle it? How to do it? Any help would be really appreciated. I tried lurking through internet but didn't find too much info (I daw drawing lines etc. by code that you launch an app and it is here but I can not find an example of drawing something by user).
Here's Jeremy's answer in code instead of prose:
// https://github.com/KubaO/stackoverflown/tree/master/questions/simplepaint-39358392
#include <QtWidgets>
// Make a subclass of the QWidget class, so that you can override some of its
// virtual methods
class PaintWidget : public QWidget {
// Create a QPixmap object that you will use to store the bitmap
// that the user will draw [on].
QPixmap m_pixmap;
QPoint m_lastPos;
// Override the paintEvent(QPaintEvent *) [...]
void paintEvent(QPaintEvent *) override {
QPainter painter{this};
painter.drawPixmap(0, 0, m_pixmap);
}
void resizeEvent(QResizeEvent *) override {
// [...] size the QPixmap to be at least as big as the maximum size of the window
// We'll also never let it shrink so as not to lose the already drawn image.
auto newRect = m_pixmap.rect().united(rect());
if (newRect == m_pixmap.rect()) return;
QPixmap newPixmap{newRect.size()};
QPainter painter{&newPixmap};
painter.fillRect(newPixmap.rect(), Qt::white);
painter.drawPixmap(0, 0, m_pixmap);
m_pixmap = newPixmap;
}
// Override the mousePressEvent(QMouseEvent *) [...]
void mousePressEvent(QMouseEvent * ev) override {
m_lastPos = ev->pos();
draw(ev->pos());
}
// Override the mouseMoveEvent(QMouseEvent *) [...]
void mouseMoveEvent(QMouseEvent * ev) override {
draw(ev->pos());
}
void draw(const QPoint & pos) {
QPainter painter{&m_pixmap};
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen({Qt::blue, 2.0});
painter.drawLine(m_lastPos, pos);
m_lastPos = pos;
update();
}
public:
using QWidget::QWidget;
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
// Create an object of your subclass and call show()
PaintWidget ui;
ui.show();
return app.exec();
}
It is not necessary to override the mouseReleaseEvent. In a widget, the default behavior is to track the mouse movement only when a mouse button is depressed. The mouseMoveEvent won't be called unless a button is depressed.
It's a pretty broad question, but here are the basics:
Make a subclass of the QWidget class, so that you can override some of its virtual methods later on.
Create an object of your subclass and call show() on it (just before calling QApplication::exec()). This object will appear on screen as a very simple window, and it will serve as your user's painting-surface.
Create a QPixmap object that you will use to store the bitmap that the user will draw. Make sure to size the QPixmap to be at least as big as the maximum size of the window that you want to support. Call fill() on the QPixmap to fill it with your favorite background color.
Override the mousePressEvent(QMouseEvent *) method of your object to set a boolean is_mouse_down flag, and also to record the current position of the mouse pointer within the window (by calling pos() on the QMouseEvent object that gets passed in to the mousePressEvent() call and storing that into a member variable of your object).
Override the mouseMoveEvent(QMouseEvent *) method so that if is_mouse_down_is set to true, it creates a QPainter object on the stack -- pass a pointer to the QPixmap to the QPainter object's constructor so that the QPainter will draw into your QPixmap object. Then call drawLine() on the QPainter object to draw a line from the previous mouse position to your current one. Finally, call update() to tell Qt to call paintEvent() for you ASAP.
Override the mouseReleaseEvent(QMouseEvent *) method to set is_mouse_down to false again
Override the paintEvent(QPaintEvent *) method to create a QPainter object on the stack -- pass a pointer to (this) to the QPainter object's constructor, so that it will paint onto the QWidget directly. Then call drawPixmap() on the QPainter object so that it will draw your QPixmap object onto the widget's visible surface.
If you'd like to see a pre-written example, check out the Scribble application included with Qt, in $QTDIR/examples/widgets/widgets/scribble.

Paint over top of label, not behind it in Qt

I am creating a simple gauge in Qt 4.7.4, and everything is working wonderfully. Except for the fact that, for the life of me, I cannot get the dial shape to paint over the text labels when it passes over them. It always paints it behind the label. I am just using a simple drawpolygon() method.
I'm thinking this has something to do about paint events? I am drawing everything inside a QFrame inside a MainWindow. I am using QFrame's paintEvent.
Edit:
The QLabels are created on start up with new QLabel(this). They are only created once, and never touched again ( Similar to manually adding them on the Ui with Designer). The drawpolygon() is in the QFrame's Paint event.
"myclass.h"
class gauge : public QFrame
{
Q_OBJECT
public:
explicit gauge(QWidget *parent = 0);
~gauge();
void setValues(int req, int Limit, bool extra=false);
private:
void drawDial();
protected:
void paintEvent(QPaintEvent *e);
};
"myclass.cpp"
void gauge::paintEvent(QPaintEvent *e)
{
Q_UNUSED(e);
drawDial();
return;
}
void gauge::drawDial()
{
QPainter Needle(this);
Needle.save();
Needle.setRenderHint(Needle.Antialiasing, true); // Needle was Staggered looking, This will make it smooth
Needle.translate(centrePt); // Center of Widget
Needle.drawEllipse(QPoint(0,0),10,10);
Needle.restore();
Needle.end();
}
If the gauge widget and the QLabels are siblings, then you can move the gauge widget to the front by calling its raise() method.
If the QLabels are children of the gauge widget, on the other hand, then they will always display in front of it. In that case you can either reorganize your widget hierarchy so that they are siblings instead, or you can get rid of the QLabels and simply call drawText() from your paintEvent() method instead (after drawDial() returns)

How can I create text with an engraved effect?

I have a QLabel, and I want the text inside of it to appear engraved, similar to the text-shadow approach in CSS. Is there a way to do this in Qt?
Much more easier than overriding the paintEvent is using a QGraphicsEffect, precisely QGraphicsDropShadowEffect.
QGraphicsDropShadowEffect* effect = new QGraphicsDropShadowEffect();
effect->setBlurRadius(5);
effect->setXOffset(5);
effect->setYOffset(5);
label->setGraphicsEffect(effect);
and the result is something like this:
If you want to color the shadow you can easily achieve this through QGraphicsDropShadowEffect::setColor member function.
Hope this helps.
This can be accomplished by overriding the label's paint event in a subclass. Example:
#include <QRect>
#include <QLabel>
#include <QPainter>
class QEngravedLabel : public QLabel
{
public:
explicit QEngravedLabel(QWidget *parent=0, Qt::WindowFlags f=0)
: QLabel(parent, f){};
explicit QEngravedLabel(const QString &text, QWidget *parent=0, Qt::WindowFlags f=0)
: QLabel(text,parent,f){};
protected:
virtual void paintEvent(QPaintEvent *pe) override
{
QRect toPaint(pe->rect());
QPainter painter(this);
toPaint.translate(0,1);
painter.setPen(QColor("#CCC")); // light shadow on bottom
painter.drawText(toPaint, this->alignment() ,this->text());
toPaint.translate(0,-2);
painter.setPen(QColor("#333")); // dark shadow on top
painter.drawText(toPaint, this->alignment() ,this->text());
toPaint.translate(0,1);
painter.setPen(QColor("#000000")); // text
painter.drawText(toPaint, this->alignment() ,this->text());
}
};
These shadow colors are tailored for a light gray background.