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.
Related
I've written a simple "proxy widget" class in Qt; the idea is that this widget will hold a single child QWidget and represent that widget in the QWidget hierarchy. (FWIW, the motivation for doing this is to make it easy to move the child widget around the hierarchy without having to directly disturb the state of various other container-QWidgets to do so).
This seems to work fairly well; the only problem I've run into is that I want my ProxyWidget to always be laid-out the same way as its child-QWidget would be (if the child had been added to the widget hierarchy directly); but instead I find that the ProxyWidget is often sized larger than its child would be, leading to wasted space in the GUI.
Therefore, is there some way I can craft my ProxyWidget class so that Qt's layout managers to move/size it exactly the same as if its child widget was added directly?
As a minimal test/example, you can compile the following code and run it with or without the "proxy" command line argument -- my goal is that the visual results would be the same either way, and in particular that you would never see any red pixels in the window (since red pixels indicate areas where the ProxyWidget has been sized larger than the blue child widget it contains)
#include <QApplication>
#include <QStackedLayout>
#include <QWidget>
class ProxyWidget : public QWidget
{
public:
ProxyWidget(QWidget * childWidget)
: _childWidget(childWidget)
, _layout(new QStackedLayout(this))
{
_layout->addWidget(childWidget);
setSizePolicy(childWidget->sizePolicy());
}
virtual QSize sizeHint() const {return _childWidget->sizeHint();}
virtual QSize minimumSizeHint() const {return _childWidget->minimumSizeHint();}
private:
QWidget * _childWidget;
QStackedLayout * _layout;
};
static void SetWidgetBackgroundColor(QWidget * w, const QColor bc)
{
QPalette p = w->palette();
p.setColor(QPalette::Window, bc);
w->setAutoFillBackground(true);
w->setPalette(p);
}
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
QWidget * win = new QWidget;
win->setWindowTitle("Proxy Widget test");
QWidget * proxyMe = new QWidget;
proxyMe->setFixedSize(100, 50);
SetWidgetBackgroundColor(proxyMe, Qt::blue);
QBoxLayout * winLayout = new QBoxLayout(QBoxLayout::TopToBottom, win);
if ((argc >= 2)&&(strcmp(argv[1], "proxy") == 0))
{
ProxyWidget * proxyWidget = new ProxyWidget(proxyMe);
SetWidgetBackgroundColor(proxyWidget, Qt::red);
winLayout->addWidget(proxyWidget);
}
else winLayout->addWidget(proxyMe);
win->show();
return app.exec();
}
I guess minimumSize and maximumSize of ProxyWidget is different from its child widget and setting them fix things in your particular example :
ProxyWidget(QWidget * childWidget)
: _childWidget(childWidget)
, _layout(new QStackedLayout(this))
{
_layout->addWidget(childWidget);
setSizePolicy(childWidget->sizePolicy());
this->setMinimumSize(childWidget->minimumSize());
this->setMaximumSize(childWidget->maximumSize());
}
However i am not sure it's the best solution but it might gives you a hint to a better one.
I have a widget W deriving from QFrame with layout set to an instance of QVBoxLayout. I wonder if the following resizeEvent implementation is correct or is it going to cause an infinite loop:
void W::resizeEvent(QResizeEvent *event) {
for (/* some condition based on the new size of this widget */) {
// Infinite loop or not?
qobject_cast<QVBoxLayout *>(layout())->addWidget(new QWidget());
}
}
So far it worked for me, is this by pure luck?
This is okay. W owns a QLayout which owns QWidget. Adding the QWidget to the QLayout does not change the size of W. You see this all the time. For example, if you place a child widget in a parent and the parent is too small, the child widget will be clipped. Stately differently, the size of the parent does not stretch to accommodate the size of the child. I believe your code would be a typical way to hide or show widgets based on the size of the parent (for example, when the window size changes).
Painting and constructing a hierarchy of widgets are two different things. So, adding QWidgets is just fine, but using QPainter directly in resizeEvent not.
Hierarchy of QWidgets
A hierarchy of QWidgets derivatives (QLineEdit, QPushButton, ...) is a high level specification of how the graphical user interface should look like and may be ordered using QLayout items.
Painting
Painting (using QPainter) is the process of actually drawing something on the screen and is purely done in the virtual function QWidget::paintEvent. Every derivative of QWidget should provide an implementation of this empty base function. The default derivatives (QLineEdit, ...) provide an implementation of paintEvent based on their current state (size of the widget, current text for a QLineEdit, ...) and the current QStyle object, which is typically automatically set based on your OS, but may be changed programmatically using QWidget::setStyle or QApplication::setStyle. A repaint can be requested using QWidget::update.
"Should not/need not" vs "may not"
The sentence "No drawing need be (or should be) done inside this handler." is meant for people implementing a custom QWidget (with a new implementation of paintEvent) to make it clear that you should not implement your painting here, but that a paintEvent will be automatically triggered.
"Should not/need not" is some advice, they do not write "may not". So, if you for some reason (ex. real-time applications) want an immediate screen refreshment, you may invoke a repaint immediately using repaint, resulting in paintEvent being called during resizeEvent. As long as all the QPainter operations on a QWidget are inside a paintEvent (as required by the warning in the QPainter documentation), everything is just fine.
Adding widgets to the layout, using addWidget, within the resizeEvent function is not a problem as it does not instantly trigger a drawing.
You can easily verify this by compiling and executing this simple project:
dialog.h:
#pragma once
#include <QDialog>
class Dialog : public QDialog
{
Q_OBJECT
public:
Dialog(QWidget *parent = 0);
~Dialog();
void resizeEvent(QResizeEvent *event);
void paintEvent(QPaintEvent *event);
private:
bool resizing;
};
dialog.cpp:
#include "dialog.h"
#include <QResizeEvent>
#include <QVBoxLayout>
#include <QPushButton>
#include <QDebug>
Dialog::Dialog(QWidget *parent)
: QDialog(parent),
resizing(false)
{
new QVBoxLayout(this);
}
Dialog::~Dialog()
{
}
void Dialog::resizeEvent(QResizeEvent *event)
{
resizing = true;
if ( event->size().width() == event->size().height() )
{
qDebug() << "Adding widget";
// Infinite loop or not?
layout()->addWidget(new QPushButton());
}
resizing = false;
}
void Dialog::paintEvent(QPaintEvent *event)
{
if ( resizing )
{
qDebug() << "Painting while resizing widget";
}
}
main.cpp:
#include "dialog.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Dialog w;
w.show();
return a.exec();
}
When you run the program, resize the dialog to make it be square (width==height), some buttons are inserted ("Adding widget" is printed to the console), but you'll never see "Painting while resizing widget" message. This is most likely because addWidget sets a dirty display flag that is processed later by the framework. It invalidates the display, but does not repaint it right away.
So what you are doing is fine and does not violate the framework requirement ("No drawing need be (or should be) done inside this handler.").
However, if you are not confident (maybe the painting could be operated right away on different OS, or in future Qt versions....you can't be sure), you can also delay the insertion by emitting a signal connected to a slot using Qt::QueuedConnection, this slot would be executed "later" and then do the call to addWidget, guaranteeing that it's done outside the resizeEvent function.
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...
Summary: I wish to use X11 to paint a custom QWidget. It works unless that widget is in a layout or QMainWindow
I have a custom widget derived from QWidget that I'd like to be the main widget in a QMainWindow. When I run something like this:
int main(int argc, char** argv) {
QApplication app(argc, argv);
ModelWidget mw;
mw.show();
return app.exec();
}
everything works fine, including resizing, obscuring and revealing the window contents, etc.
However, if I try to use that widget as the central widget in a QMainWindow, nothing is painted in the central widget area of the QMainWindow.
Here's the constructor of the QMainWindow:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
modelwidg = new ModelWidget;
setCentralWidget(modelwidg);
createActions();
createMenus();
}
I get the feeling that there's something related to the size or resize policy of my custom widget that I need to implement, but I can't find any documentation regarding what functions must be provided by a widget for it to be usable as a central widget in a QMainWindow. What am I missing?
Edit: Here's the custom widget
ModelWidget::ModelWidget(QWidget *parent) :
QWidget(parent)
{
setAttribute(Qt::WA_PaintOnScreen);
setAttribute(Qt::WA_NoSystemBackground);
setAttribute(Qt::WA_OpaquePaintEvent);
setAttribute(Qt::WA_NativeWindow);
setAutoFillBackground(true);
const QX11Info &info = x11Info();
// Elided X11 and glX specific stuff
create(wnd, true, true);
}
void ModelWidget::paintEvent(QPaintEvent *)
{
// scene.render is just some OpenGL stuff
scene->render();
glXSwapBuffers(dpy, glxwnd);
}
void ModelWidget::resizeEvent(QResizeEvent * e)
{
glViewport(0, 0, e->size().width(), e->size().height());
scene->set_aspect(float(e->size().width()) / float(e->size().height()));
update();
}
QSize ModelWidget::sizeHint() const
{
return QSize(640, 480);
}
Further...
According to the docs,
To render outside of Qt's paint system, e.g., if you require native
painting primitives, you need to reimplement QWidget::paintEngine() to
return 0 and set [Qt::WA_PaintOnScreen].
I've done that, but the window still remains unpainted. I suspect that one of my X11 objects, Window, Display*, is being altered by adding this widget to a layout or MainWindow.
I have a custom widget, which inherits QWidget. It has own paintEvent and I cannot change it. So I want to use such a widget in my dialog object, but I need to draw some graphics on it after it draws its own graphics (that widget draws video frames on it, an I need to draw some lines over it). Can I draw every time after the paintEvent of that widget? I used installEventFilter and caught the event wuth type Qt::Paint, but I canoont see anything I've drown. Is there any other way?
You can derive from the custom widget class, reimplement paintEvent, and call the inherited paintEvent first, then do your drawing.
You can install an event filter on the widget and do the same: call the widget's paintEvent first, then do your drawing.
Hide the other widget. Create your own widget, and call the other widget's render method in your widget's paintEvent, then do your drawing. Since the other widget is presumably rendering video frames that change periodically over time, you might need to use a timer to update() your widget.
In neither case are you modifying the 3rd party custom widget.
In order to call other widget's protected paintEvent you need to be using a QWidget, even if just a dummy, invisible one.
This is a very simple code sample that draw inside a custom widget. It draws a blue rectangle inside of a QPushButton.
The method used is exactly what has been described in option 1 by #Kuba
So, you inherit from the custom widget class where you want to draw in, reimplement paintEvent, and call the inherited paintEvent first and the do your drawing.
Hope this helps
#include <QApplication>
#include <QPushButton>
#include <QPainter>
#include <QPaintEvent>
// inherit from the class over which you want to draw
class DrawOverButton : public QPushButton
{
Q_OBJECT
public:
DrawOverButton(const QString &text, QWidget *parent = 0) :
QPushButton(text, parent)
{
// enlarge the button so there is some space to draw in
setStyleSheet("QPushButton {min-height: 60px; "
"min-width: 120px; margin: 5px;}");
}
protected:
virtual void paintEvent(QPaintEvent *event) {
// call the base class paint event method
// this will draw the base class content
QPushButton::paintEvent(event);
// draw a blue border inside the button
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(QColor("#3cf"), 4));
const int distance = 20;
painter.drawRoundedRect(QRect(distance, distance,
width() - 2 * distance, height() - 2 * distance),
10, 10);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
DrawOverButton *button = new DrawOverButton("Button");
button->show();
return a.exec();
}
#include "main.moc"