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.
Related
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.
EDIT and Some Self Critisicm
I tried both given solutions, which both solved my problem, and thus I thank you both! I marked the transparent solution as accepted because I thought it is was the easiest implementation when I only had one child widget, but I wish to share some insight for other beginners:
I first used QLabel, which apperently has enabled Qt::WA_TransparentForMouseEvents by default and thus obviously worked, but I also wanted the text to be selectable, by using QPlainTextEdit instead. Laughably, this is not possible in any way, because if you try to select the text (by clicking) you will close the window! I ended up keeping the transparancy, and neglecting the select-text-feature.
I'm guessing my following question has been answered somewhere before, but after an hour of searching I now post the question myself. I'm grateful if someone can point me to an already answered question that solves my problem.
Anyhow, I'm creating a popup window, using C++ and Qt. I've created the following PopupDialog class which works well and fine for all its purposes. However, I've removed its frame (including the bar containing the close button and window title) to make it look minimalistic, and now I want it to close if the user presses/releases the mouse button anywhere inside the popup window (dialog).
The below code works, however in such a way that I have to click and release the mouse exactly at the QDialog-window itself. It will not close when i click if the mouse hovers over the child widget(s) inside the QDialog, e.g. a QPlainTextEdit, which is displaying text.
Hence, I'm in need of a solution for QDialog to recognize QMouseEvents inside its child widgets. Please, don't hesitate to ask if something is unclear. I have not included my mainwindow.h/.cpp files, or popupdialog.ui file since I believe it would be a little too much to post here, but the .ui extremely simple: Just the QDialog window holding a QBoxLayout, containing a single widget, a QPlainTextEdit. I may posts these on request if it helps.
// popupdialog.h
#ifndef POPUPDIALOG_H
#define POPUPDIALOG_H
#include <QDialog>
#include <QString>
namespace Ui {class PopupDialog;}
class PopupDialog : public QDialog
{
Q_OBJECT
public:
explicit PopupDialog(QWidget *parent = 0, QString msgTxt="");
~PopupDialog();
private:
Ui::PopupDialog *ui;
QString messageText;
void mouseReleaseEvent(QMouseEvent*);
};
#endif //POPUPDIALOG_H
...
// popupdialog.cpp
#include "popupdialog.h"
#include "ui_popupdialog.h"
PopupDialog::PopupDialog(QWidget *parent, QString msgTxt) :
QDialog(parent),
ui(new Ui::PopupDialog),
messageText(msgTxt)
{
ui->setupUi(this);
setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
setModal(true);
ui->message_text_display->setText(messageText);
// The message_text_display is an instance of the class,
// "PlainTextEdit". Using "QLabel" partly solves my
// problem, but does not allow text selection.
}
PopupDialog::~PopupDialog()
{
delete ui;
}
void PopupDialog::mouseReleaseEvent(QMouseEvent *e)
{
this->close();
}
As you already noticed mouse events are handled from child widgets and propagated to parents if not accepted. You can read more about it here
To close your popup window when the click is done inside a child widget you can do two things. You could try looking into installEventFilter and set it up on each child widget to call close().
Another option would require you to have a kind of centralWidget (like the MainWindow usually has) - just to group all your child widgets. This way you could call setAttribute() on it to set Qt::WA_TransparentForMouseEvents property to simply skip handling mouse events on the widget and all of its children.
groupWidget->setAttribute(Qt::WA_TransparentForMouseEvents);
According to Qt docs:
When enabled, this attribute disables the delivery of mouse events to
the widget and its children. Mouse events are delivered to other
widgets as if the widget and its children were not present in the
widget hierarchy; mouse clicks and other events effectively "pass
through" them. This attribute is disabled by default.
Which basically means the event would be passed up the chain to the first widget which can handle the event. In your case it would be the PopupDialog and the already overriden mouseReleaseEvent slot.
in header file
class PopupDialog : public QDialog
{
Q_OBJECT
public:
explicit PopupDialog(QWidget *parent = 0, QString msgTxt="");
~PopupDialog();
//////////////////////////////////
protected:
bool eventFilter(QObject *obj, QEvent *event);
//////////////////////////////////////
private:
Ui::PopupDialog *ui;
QString messageText;
void mouseReleaseEvent(QMouseEvent*);
};
in cpp
PopupDialog::PopupDialog(QWidget *parent, QString msgTxt) :
QDialog(parent),
ui(new Ui::PopupDialog),
messageText(msgTxt)
{
ui->setupUi(this);
setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
setModal(true);
ui->message_text_display->setText(messageText);
// The message_text_display is an instance of the class,
// "PlainTextEdit". Using "QLabel" partly solves my
// problem, but does not allow text selection.
///////////////////////////////////////
foreach (QObject *child, children())
{
child->installEventFilter(this);
}
///////////////////////////////////////
}
///////////////////////////////////////
bool PopupDialog::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::MouseButtonRelease)
{
this->close();
}
}
Everywhere only just "before QPaintDevice" questions and nowhere is my error. So, here we go.
I need an extern QWidget to be able to get access to it from outside (because I don't know any other ways to do it). Basically, I need this: Create 2 QWidgets from 1 window, go to first window and from there hide main window and show second window created by main window (although main window is not main(), it is QWidget too).
I added
extern QWidget *widget = new QWidget
everywhere and everyhow in possible ways, and I still got this message. I suppose, it means that I need to create my QApplication (in main.cpp) and only then declare any QWidgets. But then HOW can I access those QWidgets from another QWidgets?
Code is here:
https://github.com/ewancoder/game/tree/QWidget_before_QApp_problem
P.S. The final goal is to be able show and hide both gamewindow.cpp and world.cpp from battle.cpp (just regular class)
And btw, adding Q_OBJECT and #include both don't work.
Anyway, if I cannot use functions from one window to another, than what's the point? I can have one window in another, and then another in that one, and then one in that another... but I can't do anything from the last to the previous. After years on Delphi that seems strange to me.
Don't use extern or otherwise static variables which lead to creation of the widget before the QApplication is created in main. The QApplication must exist before the constructor of the QWidget is executed.
Instead of sharing the variable via extern, either make the other windows members of the main window, and then make the windows known to each other by passing around pointers, or keep them private in MainWindow and request the actions from the subwindows e.g. via signal/slots. As a generic rule, don't use global variables but class members.
In the following FirstWindow (which is supposed hide main window and secondWindow) gets the main window and the second window passed via pointers and then just calls show/hide on them directly.
int main(int argc, char **argv) {
QApplication app(argc, argv);
MainWindow mainWindow;
mainWindow.show();
return app.exec();
}
In main window, have two members for the two other windows, say FirstWindow and SecondWindow:
class MainWindow : public QMainWindow {
...
private:
FirstWindow *m_firstWindow;
SecondWindow *m_secondWindow;
};
MainWindow::MainWindow(QWidget *parent) {
m_firstWindow = new FirstWindow; //not pass this as parent as you want to hide the main window while the others are visible)
m_secondWindow = new SecondWindow;
m_firstWindow->setMainWindow(this);
m_firstWindow->setSecond(m_secondWindow);
m_firstWindow->show(); //Show first window immediately, leave second window hidden
}
MainWindow::~MainWindow() {
//Manual deletion is necessary as no parent is passed. Alternatively, use QScopedPointer
delete m_firstWindow;
delete m_secondWindow;
}
FirstWindow, inline for brevity:
class FirstWindow : public QWidget {
Q_OBJECT
public:
explicit FirstWindow(QWidget *parent = 0) : QWidget(parent) {}
void setMainWindow(MainWindow *mainWindow) { m_mainWindow = mainWindow); }
void setSecondWindow(SecondWindow *secondWindow) { m_secondWindow = secondWindow; }
private Q_SLOTS:
void somethingHappened() { //e.g. some button was clicked
m_mainWindow->hide();
m_secondWindow->show();
}
private:
MainWindow* m_mainWindow;
SecondWindow* m_secondWindow;
};
Maybe not helping the former author, but others facing the problem.
I simply got this error by mistaking a debug-library with a release one. So check your linker settings, if you are sure the implementation is done right (first instancing application and then using widgets).
I was able to create a context menu for my QTreeWidget as below
QMenu* pContextMenu = new QMenu(this)
QTreeWidget* pTreeWidget = new QTreeWidget();
QAction* pOpenFile = new QAction(tr("Open A File"), pContextMenu);
pTreeWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
pTreeWidget->addAction(pOpenFile);
But I want a different popup for a branch than a leaf. How do I assign a different popup depending on the type of widgetitem clicked?
My tree:
Branch1 <-- Popup1
Leaf1
Leaf2 <-- Popup2
Branch2
Branch3
Leaf1
QWidget::actions() is not listed as virtual. Else I would have derived my own class from QTreeWidget & reimplemented actions().
Method 1: Override QTreeWidget
A context menu assigned to the QTreeWidget itself will not let you have different context menus for different items, as you have discovered.
As the Qt item views don't have special API for context menus, you have to implement this yourself. Fortunately, it's not very difficult; you just need to:
Create a subclass of QTreeWidget.
Connect the customContextMenuRequested(const QPoint&) signal to a custom slot.
Display the desired context menu.
I've posted a complete working example. Some details to note include:
QTreeWidgetItem provides a handy type property to let you identify items easily without casting, string parsing, or other awkward/fragile methods.
Custom QTreeWidgetItem type values should be greater than or equal to QTreeWidgetItem::UserType.
When displaying a context menu, you must pass a global position to exec(). To correctly map from a position in the widget's space in the slot, you must use the item's viewport widget.
Method 2: Override QItemDelegate (and QTreeWidget ...)
An alternate method is to implement your own QAbstractItemDelegate subclass, and assign it to your tree widget. In your item delegate, you can override editorEvent() to handle mouse presses in the same way.
Although this approach frres is actually more in line with Qt's item view API design, there are a few key disadvantages to this approach:
Item delegates use QModelIndex objects to represent items. To convert to a QTreeWidgetItem, you must use the QTreeWidget::itemFromIndex() method. Unfortunately, this is protected, so it will actually require you to subclass QTreeWidget anyway to provide this API for your delegate. This adds some more boilerplate complexity to your code.
The editorEvent() hook is invoked before the item view handles the event. This means that you can't easily display a context menu and allow the default behavior at the same time (such as selecting the item that was right-clicked).
Since the editorEvent() handler sees all kinds of different events, you must be even more careful to handle them correctly. You must also be careful not to let this monolithic handler grow out of control if your behaviors are complicated.
The core code is very similar, but again, there's a bit more boilerplate. I've posted an example of this approach, as well.
I've modified jmk's code slightly to show how this can be done with
setContextMenuPolicy(Qt::CustomContextMenu) and customContextMenuRequested(const QPoint&) signal.
mytreewidget.h
#include <QTreeWidget>
static const int ItemType1 = QTreeWidgetItem::UserType + 1;
static const int ItemType2 = QTreeWidgetItem::UserType + 2;
class MyTreeWidget : public QTreeWidget
{
Q_OBJECT
public:
MyTreeWidget(QWidget *parent = 0);
private slots:
void showContextMenu(const QPoint &pos);
};
mytreewidget.cpp:
#include "mytreewidget.h"
#include <QMenu>
#include <QTreeWidgetItem>
MyTreeWidget::MyTreeWidget(QWidget *parent)
: QTreeWidget(parent)
{
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
SLOT(showContextMenu(const QPoint&)));
}
void MyTreeWidget::showContextMenu(const QPoint &pos)
{
QMenu menu;
QTreeWidgetItem* item = itemAt(pos);
switch (item->type()) {
case ItemType1:
menu.addAction("This is a type 1");
break;
case ItemType2:
menu.addAction("This is a type 2");
break;
}
menu.exec(mapToGlobal(pos));
}
main.cpp:
#include <QApplication>
#include "mytreewidget.h"
int main(int argc, char** argv)
{
QApplication app(argc, argv);
MyTreeWidget w;
// Add test items.
w.addTopLevelItem(new QTreeWidgetItem(QStringList("A (type 1)"),
ItemType1));
w.addTopLevelItem(new QTreeWidgetItem(QStringList("B (type 1)"),
ItemType1));
w.addTopLevelItem(new QTreeWidgetItem(QStringList("C (type 2)"),
ItemType2));
w.addTopLevelItem(new QTreeWidgetItem(QStringList("D (type 2)"),
ItemType2));
w.show();
return app.exec();
}
I have an application which needs to draw on a pixel by pixel basis at a specified frame rate (simulating an old machine). One caveat is that the main machine engine runs in a background thread in order to ensure that the UI remains responsive and usable during simulation.
Currently, I am toying with using something like this:
class QVideo : public QWidget {
public:
QVideo(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), screen_image_(256, 240, QImage::Format_RGB32) {
}
void draw_frame(void *data) {
// render data into screen_image_
}
void start_frame() {
// do any pre-rendering prep work that needs to be done before
// each frame
}
void end_frame() {
update(); // force a paint event
}
void paintEvent(QPaintEvent *) {
QPainter p(this);
p.drawImage(rect(), screen_image_, screen_image_.rect());
}
QImage screen_image_;
};
This is mostly effective, and surprisingly not very slow. However, there is an issue. The update function schedules a paintEvent, it may not hapen right away. In fact, a bunch of paintEvent's may get "combined" according to the Qt documentation.
The negative effect that I am seeing is that after a few minutes of simulation, the screen stops updating (image appears frozen though simulation is still running) until I do something that forces a screen update for example switching the window in and out of maximized.
I have experimented with using QTimer's and other similar mechanism to have the effect of the rendering being in the GUI thread so that I can force immediate updates, but this resulted in unacceptable performance issues.
Is there a better way to draw pixels onto a widget constantly at a fixed interval. Pure Qt solutions are preferred.
EDIT: Since some people choose to have an attitude instead of reading the whole question, I will clarify the issue. I cannot use QWidget::repaint because it has a limitation in that it must be called from the same thread as the event loop. Otherwise, no update occurs and instead I get qDebug messages such as these:
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QWidget::repaint: Recursive repaint detected
QPainter::begin: A paint device can only be painted by one painter at a time.
QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent
QWidget::repaint: It is dangerous to leave painters active on a widget outside of the PaintEvent
EDIT: to demonstrate the issue I have created this simple example code:
QVideo.h
#include <QWidget>
#include <QPainter>
class QVideo : public QWidget {
Q_OBJECT;
public:
QVideo(QWidget *parent = 0, Qt::WindowFlags f = 0) : QWidget(parent, f), screen_image_(256, 240, QImage::Format_RGB32) {
}
void draw_frame(void *data) {
// render data into screen_image_
// I am using fill here, but in the real thing I am rendering
// on a pixel by pixel basis
screen_image_.fill(rand());
}
void start_frame() {
// do any pre-rendering prep work that needs to be done before
// each frame
}
void end_frame() {
//update(); // force a paint event
repaint();
}
void paintEvent(QPaintEvent *) {
QPainter p(this);
p.drawImage(rect(), screen_image_, screen_image_.rect());
}
QImage screen_image_;
};
main.cc:
#include <QApplication>
#include <QThread>
#include <cstdio>
#include "QVideo.h"
struct Thread : public QThread {
Thread(QVideo *v) : v_(v) {
}
void run() {
while(1) {
v_->start_frame();
v_->draw_frame(0); // contents doesn't matter for this example
v_->end_frame();
QThread::sleep(1);
}
}
QVideo *v_;
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QVideo w;
w.show();
Thread t(&w);
t.start();
return app.exec();
}
I am definitely willing to explore options which don't use a temporary QImage to render. It is just the only class in Qt which seems to have a direct pixel writing interface.
Try emitting a signal from the thread to a slot in the event loop widget that calls repaint(), which will then execute right away. I am doing something like this in my graphing program, which executes the main calculations in one thread, then tells the widget when it is time to repaint() the data.
In similar cases what I did was still using a QTimer, but doing several simulation steps instead of just one. You can even make the program auto-tuning the number of simulation steps to be able to get whatever frames per seconds you like for the screen update.