Qt: Does a reimplemented paintEvent(QPaintEvent*) do hidden background work? - c++

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.

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)

Qt propagates paint events on backgroung items

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.

Drawing with wxWidgets

I'm creating an application for capturing the screen or a part of it, and for that I need to select the part of the screen I want to capture.
The idea is to create a fullscreen semi-transparent window and draw thing on it so the user can see what he is doing. I have a working implementation where the selected area is dislayed as a wxPanel, but I want to rewrite it to paint everything to the main wxPanel manually.
frame = new wxFrame(NULL, -1, "", wxPoint(0,0), wxSize(0,0), wxSTAY_ON_TOP|wxFRAME_NO_TASKBAR|wxFRAME_SHAPED);
panel = new wxPanel(frame, -1);
SetTopWindow( frame );
panel->Bind(wxEVT_KEY_DOWN, &wxMiniApp::OnKeyDown, this);
panel->Bind(wxEVT_KEY_UP, &wxMiniApp::OnKeyUp, this);
panel->Bind(wxEVT_LEFT_DOWN, &wxMiniApp::OnMouseStart, this);
panel->Bind(wxEVT_LEFT_UP, &wxMiniApp::OnMouseEnd, this);
panel->Bind(wxEVT_MOTION, &wxMiniApp::OnMouseMove, this);
panel->Bind(wxEVT_PAINT, &wxMiniApp::OnPaintPanel, this);
panel->SetFocus();
panel->SetBackgroundColour(wxColor(0,0,0,100));
panel->SetBackgroundStyle(wxBG_STYLE_PAINT);
frame->SetBackgroundColour(wxColor(0,0,0,0));
frame->Show();
frame->ShowFullScreen(true);
I use panel to capture the mouse/keyboard events and I want to do the painting there too, but I don't know how to do this.
I want to have semi-transparent black background and the selected area should be transparent white or even fully transparent (without the black background).
The result of every attempt was that either it draw a solid color background or I got the famous WinXP lag effect.
Can you give me a basic OnPaintPanel(wxPaintEvent &event) implementation using x, y, with, height of the selected area (can be in wxPython too, if you're more comfortable with it) ?
You need to use SetTransparent(), wxDC doesn't support using alpha transparency anyhow (wxGraphicsContext does but it wouldn't help here).

Clear Transparent Background for QWidget

I have a widget as on overlay for another widget. The transparency works fine, as long as I don't clear the background of the overlay.
But I have to clear the widget to acutalize the displayed "effects". I tried to solve the inital problem (the background getting the default color) like described in 20848594, but except changing the color to black it had no effect...
Has anyone an idea why the widget which should be transparent does not display the underlaying widget(s)?
Here the code:
SudokuMarkerOverlayWidget::SudokuMarkerOverlayWidget(QWidget* parent, uint const fieldSize) : QWidget(parent)
{
// Translucent should be the correct one
setAttribute(Qt::WA_TranslucentBackground);
//setAttribute(Qt::WA_NoSystemBackground);
setAttribute(Qt::WA_TransparentForMouseEvents);
...
}
void SudokuMarkerOverlayWidget::paintEvent(QPaintEvent*)
{
QPainter painter(this);
painter.setRenderHint( QPainter::Antialiasing );
// Tried this too, no difference
// painter.setCompositionMode(QPainter::CompositionMode_Source);
// painter.fillRect( this->rect(), Qt::transparent );
painter.setCompositionMode(QPainter::CompositionMode_Clear);
painter.eraseRect( this->rect() );
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
...
}
EDIT:
Just noticed, using CompositionMode Source instead of SourceOver for my semi-transparent painting (gradient seen in first image) also causes them to be a red-to-black-gradient instead of red-to-transparent.
That means every transparency except the inital by WA_TranslucentBackground or WA_NoSystemBackground isn't working.
Widgets in Qt can be either a 'native' or 'alien' type. In one case they're a separate operating system window. In some operating systems a transparent window isn't supported.
You might want to consider using QML if you're after fancy visual effects. That's what it was designed for.

QT: picture as window

I want to make window frame using some picture. Window shouldn't have borders, titlebars, etc. It also should be hidden from active windows list (in taskbar).
Second part of question I did with:
this->setAttribute(Qt::WA_NoSystemBackground);
this->setAttribute(Qt::WA_QuitOnClose);
this->setAutoFillBackground(true);
this->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool);
for new class which inherits QMainWindow. It's hidden, for example, at gnome taskbar, but in Awn (awant windows navigator) I seed it in the list of active windows :(.
What about first part. I did this some time ago with QRegion, QPixmap and mask in overloaded paintEvent. I've lost the code. Can you help me with this?
regarding the first part of the question, you, probably, looking for smth like this:
void MainWindow::paintEvent(QPaintEvent * event)
{
QPainter painter(this);
QPixmap pixmap = QPixmap();
pixmap.load("/home/my_image.jpg");
painter.drawPixmap(event->rect(), pixmap);
}
as an alternative you can create a label and show it on your main window, smth like this:
QLabel* label = new QLabel(this); // where 'this' is your window
label->setGeometry(this->geometry());
QPixmap pixmap = QPixmap();
pixmap.load("/home/my_image.jpg");
label->setPixmap(pixmap.scaled(label->size(), Qt::KeepAspectRatio));
hope this helps, regards