QWidget's paintEvent() lagging application - c++

i'm studying and modifying the fridge magnets example, and the last thing I've tried to do was to draw a few labels and lines that are supposed to be on the background.
After looking around trying to figure out how to draw the labels and lines, I learned that I could override QWidget's paintEvent() to do it. After I did it though, the application got laggy, and I found out that it was because paintEvent() was being called in a seemingly infinite loop.
Trying to figure out how to fix that, I moved the code that drew the labels and lines to the constructor of the class. Only the labels were drawn on the application though. After that, I left the labels in the constructor, but moved the code that drew the lines back to paintEvent(). It worked, the lines were drawn as expected, and paintEvent() was called only when dragging stuff around.
Why were the lines not being drawn on the constructor, and why paintEvent() got into an infinite loop?
Here's the snippet that's supposed to draw the labels and lines:
QPen pen(Qt::lightGray, 0, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin);
QPainter paint(this);
paint.setPen(pen);
int scale = 20;
for(int x=0; x<25; x++){
QString timetext= "0"+QString::number(x)+":00";
QLabel *time= new QLabel(timetext,this);
time->move(x*scale,2);
time->show();
paint.drawLine(x*scale,12,x*scale,400);
}

You are adding Objects to the Widget Tree during paintEvent(). That is deemed to fail. The Qt scheduler for damage&drawing will see that a new child has to be drawn and try to manage that and likely the loop is the result. If you override paintEvent(), do all the painting in the same object! Golden rule: paintEvent() is only for painting! Not for creating objects or anything else.
Do it like this:
QFont font(painter.font());
font.setBold(true);
painter.setFont(font);
painter.fillRect(rect(), Qt::black);
painter.setPen(Qt::white);
painter.drawText(rect(), Qt::AlignCenter, tr("White text on dark background. Awesome."));

Why were the lines not being drawn on the constructor ?
I think they were, but they were "erased" by the next call to paintEvent() in which you didn't draw the lines anymore...
Why paintEvent() got into an infinite loop?
I think it could be related to your time->show(); which is called 25 times everytime paintEvent is called... I'm not sure about that but, since time as the widget as parent, when you call "show", maybe it calls "show" on its parent, therefore triggering paintEvent.... You know what I mean...
Since, Ypnos gave you a solution, I refer to him :)

Related

how to paint outside of paintEvent()? Qt, C++

I am trying to draw rectangle outside of paintEvent().
If users click and drag the screen with their mouse, application should draw 'Selecting Area'.
But it seems impossible painting outside of paintEvent().
I already solved this problem on MFC with ReleaseDC().
Here is My Code on MFC :
void DrawingPaper::DrawSelectingArea() {
CDC *dc = this->GetDC();
CPen pen;
CPen *oldPen;
dc->SetROP2(R2_NOTXORPEN);
pen.CreatePen(PS_DOT, 1, RGB(166, 166, 166));
oldPen = dc->SelectObject(&pen);
dc->Rectangle(this->startX, this->startY, this->currentX, this->currentY);
dc->SelectObject(oldPen);
this->ReleaseDC(dc);
DeleteObject(pen);
}
it works well although the code is not in OnPaint().
But on Qt, how?
here is my code on Qt :
void DrawingPaper::DrawSelectingArea() {
QPainter painter(this);
QRect drawRect(this->startX, this->startY, this->currentX, this->currentY);
painter.drawRect(drawRect);
//this->ReleaseDC(dc);
}
it does not work because painter that draw rectangle will be removed by other QPainter in paintEvent().
Is there any solution like ReleaseDC()?
I am on Qt 5.12.6.
Thanks for your help.
The short answer is, you can’t — Qt does not work that way. If you’re outside of paintEvent() and you want to repaint your widget, what you need to do is call update() instead. That will cause paintEvent() to be called ASAP, and then your code inside paintEvent() can do the actual painting.
That said, if you absolutely must do your painting elsewhere, you can create a QPixmap object that is the same width and height as your widget, and pass a pointer to that QPixmap to the constructor of your QPainter object, and paint into the QPixmap. Then when you are done, call update(), which will cause paintEvent() to be called ASAP, and in the paintEvent() call you can call drawPixmap() with that QPixmap as an argument, to copy the pixels from the QPixmap to the widget’s on-screen buffer. Note that this is less efficient than just doing the original painting directly inside paintEvent(), though, since this approach requires the pixels to be copied an additional time (and possibly also causes the image to be drawn more often than is necessary)

Drawing outside of the QPaintEvent handler

We have big QT project where painting procedure often doesn't follow the rule that it should be done in overridden paintEvent method. As result we have warnings about it: Painter not active etc... But all work fine and at first glance I don't see any problems. Could you explain should I worry about it or not? What is the price of the incorrect use of this functionality?
Paint event is sended to the window when it is should be updated, eg when it is shown or something else. For example if widget is covered by another window, and this windows is moved away, then widget should be updated. Common way is to paint on the pixmap and draw this pixmap on the widget in the paint event handler. Or you can update/repaint each time you need to repaint it.
You can use QPainter to draw on pixmap, printer and so on whenever you want, but to draw on windget it must be done in paintEvent.
I found mistake - it is happened when invalid pixmap(I have created pixmap with size 0x0) is used. I have add check on it and now all are okey.

QGraphicsView freeze after scaling

I am on Windows with Qt 4.7.
I have a bug in my qgraphicsview/qgraphicsscene whereby after scaling the view, something is causing a "hang" which appears to be a recursive/infinite loop in the event loop.
I apologize for not having a simple, complete code example; I will describe the symptoms and situation as best I can, and I'm hoping somebody will have an idea or two I can try:
I have subclassed QGraphicsView, QGraphicsScene, and QGraphicsItem. For example, wheelEvent in QGraphicsView has been subclassed to do the following:
scaleView(pow((double)2, -event->delta() / 240.0));
where
void MyGraphicsView::scaleView(qreal scaleFactor)
{
qreal factor = transform().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width();
if (factor < 0.07 || factor > 100)
return;
scale(scaleFactor, scaleFactor);
}
The constructor of my graphicsview subclass is as follows:
MyGraphicsView(QWidget *parent) :
QGraphicsView(parent)
{
setRenderHint(QPainter::Antialiasing);
setTransformationAnchor(AnchorUnderMouse);
setCacheMode(QGraphicsView::CacheNone);
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
setDragMode(QGraphicsView::ScrollHandDrag);
}
My subclass of QGraphicsItem constructor is as follows:
MyGraphicsItem(QGraphicsObject *parent) :
QGraphicsObject(parent),
_colour(qrand() % 256, qrand() % 256, qrand() % 256),
_collapsed(false)
{
setFlag(ItemIsMovable);
setFlag(ItemSendsGeometryChanges);
setCacheMode(DeviceCoordinateCache);
setZValue(-1);
expandedPosition(this->pos());
}
I can isolate the problem to a call to the wheelEvent, or other event which calls QGraphicsView::scaleView.
If there is a MyGraphicsItem visible, then trying to paint (or something caused by painting) causes the whole system to hang. I think this is a loop in the event loop, but to get out of it (ctrl+alt+del to bring up "Start Task Manager", then esc to cancel) I lose the loop I was in.
The loop seems to be repeated calls to QGraphicsView::focusInEvent and focusOutEvent. I have overridden these and don't seem to have much luck in stopping the loop whatever I do with these functions.
Edit: this appears to be a red herring, a result of switching to breakpoints inside the debugger.
There is no issue if the view is not displaying a MyGraphicsItem.
Also, I can pan and repaint the view after doing the scale, but trying to resize the window or change focus causes the problem.
Similarly, if there has not been a call to scaleView, I have no problems.
Perhaps I am being too vague; but please fire away with any questions I can answer to help clarify. Any help appreciated!
Update:
So I seem to have got it down to something to do with a subclass of my subclass of QGraphicsItem.
I took a Node from the elastic nodes example, and put this in my scene. I can zoom in and out using my sceven and view, and no hang.
I can even make Node a subclass of MyGraphicsItem rather than QGraphicsItem, and again no hand.
As soon as I put my subclass of MyGraphicsItem in the scene, I see the issue.
Getting closer I hope!
Further Update:
I have found the cause of this. It seems to show that I missed an important memo somewhere.
I have a test graphics item that is NOT a MyGraphicsItem, but it is a QGraphicsLineItem, and this reproduces the problem.
I have overridden the paint method, and after a lot of elimination I have found that if I call setPen inside the paint method, my hang returns. e.g., my paint method is now:
void ConnectionItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
QLineF myline = line();
QBrush brush(Qt::red);
QPen pen = this->pen();
pen.setBrush(brush);
**// this line results in the hang when the window is resized
setPen(pen);**
painter->setPen(pen);
painter->setBrush(brush);
painter->drawLine(myline);
}
So setPen (which will be QGraphicsLineItem::setPen) calls update() and then gets stuck in a loop calling QGraphicsLineItem::Paint().
Moral of the story: do not setPen or other paint related property inside the paint function!
I am not 100% clear why this is only an issue after scaling, as the paint routine is called quite happily prior to zoom.
Edit:
similarly
QPen pen = this->pen();
pen.setBrush(brush);
causes a problem
as does calling
prepareGeometryChange();
inside the paint routine

QScrollArea::ensureVisible() and QScrollArea::setWidget()

I encountered another problem with QScrollArea, after I got help for the previous one, which was somewhat similar.
The problem now is that ensureVisible() does nothing if you create a scroll area and a label, set the label to be the scroll area's widget, and then load an image into the label - after setWidget():
This example illustrates the problem, just replace /path/to/some/image.png with some real image on your computer:
QScrollArea *scrollArea = new QScrollArea;
QLabel *label = new QLabel(scrollArea);
scrollArea->setWidgetResizable(true);
scrollArea->setWidget(label);
label->setPixmap(QPixmap("/path/to/some/image.png"));
label->setFixedSize(label->pixmap()->size());
scrollArea->ensureVisible(10000, 10000);
scrollArea->show();
If the setPixmap() is called before setWidget(), ensureVisible() will work.
Also, the problem is reproducible even though I call setWidgetResizable() and even setFixedSize().
Why does this happen, and is it possible to make ensureVisible() work without changing the order of setWidget() and setPixmap()?
When you call ensureVisible(10000, 10000); the scrollArea hasn't adjusted the widget's size yet. That is why it won't work.
If you create a slot that calls ensureVisible and use QTimer::singleShot to call that slot with the timeout set to 0 (you can also use QMetaObject::invokeMethod with queued connection), it will work, even if you set the scroll area's widget before you set the pixmap on the label.
What also works is, if you call ensureVisible after you call show. But this only works if your scrollArea is a top level window. If you embed it to a widget, it will not work.

Is it possible force Qt to call paintEvent after other Qt components are drawn?

I've a class that extends QWidget and contains a QLabel (lblBackground). I've overriden paintEvent function too.
I want to draw something on top of lblBackground however paintEvent method is called before the QLabel is drawn. Thus my custom drawings are overwritten.
Is there a way to change drawing order?
Painting the children on top of their parent is the common thing to do. That being said you could try one of the following options:
extend QLabel itself to paint whatever you want
try to set the Qt::WA_TranslucentBackground flag on the QLabel and having an alpha channel, so that the underlying parent (QWidget) would shine through
if you are only using the QLabel to paint some background, maybe you can get rid of it and paint the desired background first thing in the QWidget's paintEvent()?
If you want to use label as a background then just create your custom widget as a child of your label. May be split some window frame related tasks if any (to be implemented as a parent of the label) and drawing/controls/etc (to be child of the label).