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)
Related
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.
I do have 2 QDeclarativeItems.
void BackgroundLayer::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
painter->drawImage( QRectF(dx1, dy1, dx2-dx1, dy2-dy1), shownImage, QRectF(sx1, sy1, sx2-sx1, sy2-sy1) );
}
void ForegroundLayer::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
QPen pen(Qt::red, 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
painter->setPen(pen);
painter->drawLine(p1, p2);
}
QML
Rectangle {
width: 1920
height: 1080
BackgroundLayer{
id: background_layer
anchors.fill: parent
}
ForegroundLayer {
id: foreground_layer
anchors.fill: parent
}
}
Drawing on ForegroundLayer triggers BackgroundLayer paint event, causing it to repaint the whole image. As a result, drawing works slow. Is it possible to avoid this and repaint the image only when it's really needed?
Why do you expect any other kind of behavior? Qt doesn't keep the images of every declarative item for you, it'd be prohibitively expensive in terms of memory. You have the option of enabling this, though: perhaps you should. See the cacheMode documentation.
When any item needs to be updated, everything underneath and intersecting the update rectangle has to be repainted too, in the Z order from bottom to top. If there are any widgets underneath the QGraphicsView and if the view itself is translucent, then these widgets will have to be repainted as well.
If you have knowledge exactly of what area needs to be updated, you should use that knowledge: call QGraphicsItem::update(const QRectF &) to indicate the bounds of what needs updating. Otherwise, with a null rectangle, the update region spans the whole item.
Also ensure that the QGraphicsView's updateMode is set to MinimalViewportUpdate.
Under the covers, all QGraphicsItem instances and all QWidget instances all paint on an internal QImage that is then blitted or swapped into the underlying native window. They paint in back-to-front Z order, and the only widgets or items that are skipped are those that are completely contained under an opaque widget or item.
Short answer : Just use a QPixmap converted once from shownImage
void BackgroundLayer::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
painter->drawPixmap( QRectF(dx1, dy1, dx2-dx1, dy2-dy1), shownPixmap, QRectF(sx1, sy1, sx2-sx1, sy2-sy1) );
}
Explanation :
painting pixmaps on screen is much faster than images. The other choices may not work or are way too complex.
Paint events propagate from the top widgets to their children, recursively.
Basically Qt is given a rectangle to paint, and every widget inside this rectangle will receive a paint event.
I am sure what you want to achieve might be doable in some specific cases with widget attribute hacking, but I fail to see how you can do it here without having old paint artifacts from the ForegroundLayer.
Let say you have two lines AB et CD.
After the first call to paint you only want to see line AB
After the p1, p2 have been updated you only want to see line CD
In order to prevent you from seeing the line AB when painting CD, Qt has to clear the entire background in the rectangle being painted. If for some reason BackgroundLayer doesn't paint, the background image will disappear.
I'm currently chewing through C++ GUI Programming with Qt4 (Blanchette/Summerfield). In the 5. chapter a basic widget to plot a data set is presented, including a zoom function and view selection via a rubberband that is painted manually (not using QRubberBand).
To manually paint the rubberband, the widget reimplements the handler for paint events:
void Plotter::paintEvent(QPaintEvent * /* event */)
{
QStylePainter painter(this);
painter.drawPixmap(0, 0, pixmap); //This widget is always drawn from a pixmap
if (rubberBandIsShown) {
painter.setPen(palette().light().color());
painter.drawRect(rubberBandRect.normalized()
.adjusted(0, 0, -1, -1));
}
...
}
Notice that the handler does not make use of any argument, which looks to me as if it always painted the whole widget (the pixmap + the rubberband if necessary). However, in another function the author calls update(QRect) multiple times, specifying an area to be updated:
void Plotter::updateRubberBandRegion()
{
QRect rect = rubberBandRect.normalized();
update(rect.left(), rect.top(), rect.width(), 1);
update(rect.left(), rect.top(), 1, rect.height());
update(rect.left(), rect.bottom(), rect.width(), 1);
update(rect.right(), rect.top(), 1, rect.height());
}
Now my questions is: Why are the above updates scheduled for a specific rectangle of the widget while the paint event dicards any positional information and paints the whole widget instead? Couldn't I just have called update() once without arguments?
No part in the implementation of the paint event handler makes use of any rectangle or area (no event->rect() or alike), unless I misunderstood something horribly or paintEvent(..) is smarter than its implementation and additional background work is done that I am not aware of.
Thanks a lot.
I'm using Qt 4.8.6 with g++ 4.8.2 on Windows 8.1 x64.
I want to design a new QIcon and want it to look like a fixed text with a rounded rectangle around it
.-----.
| Phy |
`-----´
The icon is supposed to scale without the "pixel-blocks" effect when painting on a QPainter that eventually has a scale transformation applied (for example when I paint into a widget that is part of a QGraphicsView with a scale applied on its scene).
Therefor, I have difficulties knowing how I should paint my QIcon. If I do it in the following way, I will paint a QPixmap that always has a fixed amount of pixels, thus introducing the pixel-blocks effect inevitably when the scale is large enough
void MyWidget::drawIcon(QPainter *painter, QPoint pos) {
QPixmap pixmap = icon.pixmap(QSize(22, 22),
isEnabled() ? QIcon::Normal
: QIcon::Disabled,
isChecked() ? QIcon::On
: QIcon::Off);
painter->drawPixmap(pos, pixmap);
}
What I am looking for is a way similar to how QFont with drawText works. Regardless on how large my scale is, when I draw fonts it always looks sharp and I cannot detect individual pixels.
I imagine that I could tell QPainter to paint my icon into a given pixel rectangle, and QPainter transforms the rectangle itself before letting my QIconEngine::paint render the item into a possibly larger rectangle or pixmap. But I see no way how I could do something like this.
Am I just being stupid and not seeing the obvious solution?
I was indeed completely dump. I can just use QIcon::paint and pass it the rectangle. It will correctly delegate the request to the icon engine.
I do this by creating my icons/images as SVG files, and using QSvgRenderer to paint them onto a QPainter. The required classes are in the SVG module.
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 :)