Qt propagates paint events on backgroung items - c++

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.

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)

How to control the Widgets in QGridLayout that should be painted in GUI?

In my application I have a QGridLayout which covers the majority of the Window. In it I have added a sequence of QLineEdit & QLabel objects. Currently when the no of QLineEdit objects > 500 && QLabel objects > 500 the GUI is significantly slow & for greater values does not operate properly. Also most of these widgets are not visible in the window, they need to be scrolled to be viewd.
Since I am adding so many widgets in the grid layout (by looping & calling repaint after the loop) the painting takes lots of time.
So I have an idea for the solution that even though my widgets are added in the Grid Layout not everybody are painted. I want to have a rectangle within which all widgets are painted & the co-ordinates of the rectangle will be updated whenever the the window is scrolled. But I dont know how to do this. So I wanted to know is it possible to do that?
And if possible please add a small sample code so that I can understand how to implement that.
Thank You.
UPDATE : Adding an image to depict the sitatuion.
Black Rectangle = QGridLayout say myGid.
Red Rectangle = Bounding Rectangle which is approximately same size as Main Window of my Application.
Green Rectangle = Widgets in myGrid.
Green Rectangle filled with yellow = Widgets shown in Main Window (only these widgets should be considered for call to repaint), rest of the unfilled rectangles are widgets present in myGrid but not be considered for call to repaint.
Thus when I scroll in my main application, the co-ordinates of red rectangle are updated & all the widgets bounded by it are considered for repaint.
I hope I made the problem simple to understand.
I understand you don't want to discard your code. I'd try one of these, starting from the easiest:
Are you using a QScrollArea or are you simulating it using scroll bars? The QScrollArea probably already discards paint events to children widgets that are off the viewport. Assemble the grid off-screen. Otherwise, Qt will recalculate and repaint the layout every time you add a new widget. (Here is a complete example.)
QWidget* widget = new QWidget(); // This is an invisible widget.
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
QLineEdit* lineEdit = new QLineEdit();
lineEdit->setText(QString("I am the line edit at (%1, %2)").arg(i).arg(j));
layout->addWidget(lineEdit, i, j);
if (j % 10 == 0) {
// Do not block the UI thread while the UI is being assembled.
qApp->processEvents();
}
}
}
// The layout will be calculated only once, here:
scrollArea->setWidget(widget);
widget->show();
If that doesn't work, create an event filter that has a reference the visible rectangle. Event filtering is a useful technique in which you can intercept events targeted at one or more widgets and decide if they should be discarded before being handled.
In your case, when you intercept a QPaintEvent, check if the target widget intersects the visible rectangle. If it does, pass the event along to the target widget. If it doesn't, discard the event.
I don't know the specifics of how you scroll your UI, so I leave the calculating the visible rectangle up to you. The event filter code would be something like this.
bool MyClass::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::Paint) {
if (QWidget* widget = qobject_cast<QWidget*>(object)) {
QRect visibleRectangle = somehowGetVisibleRectangle();
if (visibleRectangle.intersects(widget->geometry())) {
return false;
} else {
// Returning true means "drop this event."
return true;
}
}
}
// Assuming MyClass extends QWidget. Adjust as necessary.
return QWidget::eventFilter(obj, event);
}
As a last resort, relayout your UI using QGraphicsScene, QGraphicsWidget, QGraphicsGridLayout, and QGraphicsView. The scene graph might be better at discarding unnecessary UI repaints.
First of all. Are you sure you are solving your problem in the right way? Perhaps you will be happier with QTableWidget? Its cells can be editable and then QTableWidget will take care of creating and maintaining QLineEdit for the cell that is being edited.

QSpinBox not drawn correctly

I'm currently implementing a custom delegate, in part of which I need a QSpinBox to be drawn in the paint(..) method.
void Sy_floatingPointPD::paint( QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index ) const
{
painter->save();
// Paint check box.
QStyleOptionSpinBox spOpt;
spOpt.palette = option.palette;
spOpt.rect = option.rect;
spOpt.state = option.state;
spOpt.frame = true;
spOpt.stepEnabled = QAbstractSpinBox::StepUpEnabled |
QAbstractSpinBox::StepDownEnabled;
style->drawComplexControl( QStyle::CC_SpinBox, &spOpt, painter );
painter->restore();
}
Unfortunately it appears as:
As you can see the step buttons are drawn massive and only the down arrow appears. Interestingly the width of the buttons mirrors that of the first table column, despite option.rect being the size of the cell (which is correct, which is presumably why the frame is drawn correctly).
Any ideas what information I'm not giving QStyle?
Jens over at the qt-project forums answered this question, so I'll link to it here.
In short, there is a design flaw in the spin box drawing (at least in QGtkStyle) whereby it only uses the size of the option.rect, ignoring it's position. Although this perfectly reasonable in a 'normal' painting scenario because it maps to the widget origin, it fails when rendered in an item view due to the cell offset.
To solve this, move option.rect to the widget origin (i.e. move it's top left corner to (0,0)), and then translate the QPainter to take into account the cell offset.

Drawing a scalable QIcon using QPainter

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.

QT mouse event handling problem

Greetings all,
As seen in the picture
I have an extended QWidget object (which draws the cell images and some countour data) inside a QScrollBar.
User can zoom in/out the Image (QWidget size is changed according to the zoomed size of the QImage ) using mouse wheel.
I process the events (mouseMoveEvent(),wheelEvent()..etc) by implementing the listener methods in QWidget.
My problem is ,I can only perform zooming (and other events) when the mouse pointer is over the QWidget.
If the mouse point is over the QScrollBar (the gray area in the image) ,those events are consumed by the QScroolBar.
Any tips,
[Edit] Sorry I was refering to QScrollArea , not QScrollBar.
thanks,
umanga
I'm uncertain if you want the scroll wheel to only ever be used for zooming the image or if you want the scroll wheel to control zooming when the image is smaller than the scroll area viewport and then use the scroll wheel to do scrolling when the image is larger than the scroll area viewport. In either case, you should be able to customize how the wheel is handled with the following:
Since I've not actually tried this one, I'm not sure if it will work. The hope is that if you install an event filter and set ignore on the event, the event will still be propagated back to your image widget. This will allow you to leave your current mouse handling in the image widget intact.
bool YourImageWidget::eventFilter(QObject *obj, QEvent *event)
{
if((obj == scrollAreaPointer) && (event->type() == QEvent::Wheel))
{
if(!scrollAreaShouldHandleWheel)
{
event->ignore();
}
}
return false; // always pass the event back to the scroll area
}
The scrollAreaShouldHandleWheel flag is a boolean you would set from your image widget based on whether or not you want the scroll area to handle the wheel events.
Somewhere in your code you would install your image widget as an event filter for the scrollarea.
scrollArea->installEventFilter(imageWidget);
If this doesn't work, you can always use this filter to make sure your widget gets the event and handle it directly and then return true so that the scroll area won't be able to receive the event.
I recommend you use QGraphicsScene and QGraphicsView. The graphics framework already provides a lot of useful features (including viewport transformation). And the QGraphicsView is a scroll area.
have you done grabMouse() for Qwidget i.e for the one which you display image?