In my application I have a QDialog which itself contains a complex, QWidget-derived GUI element. The QDialog is modal and opened with exec() and the embedded GUI element handles all user interactions.
So only this child QWidget knows when the QDialog can be closed, which is done this way:
QDialog* parent=qobject_cast<QDialog*>(parentWidget());
if (parent) parent->close();
This is necessary because the QDialog has to be closed and not only the QWidget.
Now a user reported a situation where QDialog::exec() has returned but where the dialog (or only the GUI element?) was still visible. From the log files I can see QDialog::exec() really has returned and the code right after this call was executed.
So my current assumption: the GUI element has lost its parent so that the close() call shown above was not called because "parent" was null.
Any idea how this can happen? Is there a regular way where the parent of a QWidget can disappear?
Generally speaking, using QDialog::exec to reenter the event loop will cause trouble, because suddenly all the code that runs in the main thread must be reentrant. Most likely you're facing fallout from that. Don't reenter the event loop, and you'll be fine or the problem will become reproducible.
If you need to react to the dialog being accepted or rejected, connect code to the relevant slots. I.e. change this:
void do() {
MyDialog dialog{this};
auto rc = dialog.exec();
qDebug() << "dialog returned" << rc;
}
to that:
class Foo : public QWidget {
MyDialog dialog{this};
...
Foo() {
connect(&dialog, &QDialog::done, this, &Foo::dialogDone);
}
void do() {
dialog.show();
}
void dialogDone(int rc) {
qDebug() << "dialog returned" << rc;
}
};
or, if you want to lazily initialize the dialog:
class Foo : public QWidget {
MyDialog * m_dialog = nullptr;
MyDialog * dialog() {
if (! m_dialog) {
m_dialog = new MyDialog{this};
connect(m_dialog, &QDialog::done, this, &Foo::dialogDone);
}
return m_dialog;
}
...
void do() {
dialog()->show();
}
void dialogDone(int rc) {
qDebug() << "dialog returned" << rc;
}
};
It is a horrible antipattern for the child widget to attempt to meddle with the parent. The knowledge that the widget has a parent should not leak into the widget, it should be localized to the parent. Thus, the child widget should emit a signal that indicates that e.g. the data was accepted. When you create the dialog, connect this signal to the dialog's accept() or close() slots:
class MyWidget : public QWidget {
Q_OBJECT
public:
Q_SIGNAL void isDone();
...
};
class MyDialog : public QDialog {
QGridLayout layout{this};
MyWidget widget;
public:
MyDialog() {
layout.addWidget(&widget, 0, 0);
connect(&widget, &MyWidget::isDone, this, &QDialog::accepted);
}
};
Related
I create a new QWidget object and I want to know when the close button is pressed.
I have tried the following code:
pWindow = new QWidget();
connect(pWindow , SIGNAL(triggered()), this, SLOT(processCloseButtonWindowsClicked()));
but it give an error:
no signal triggered of pWindow
How to achieve this?
Cause
QWidget does not have a triggered signal.
Solution
I would suggest you to:
Subclass QWidget and reimplement QWidget::closeEvent
Check QEvent::spontaneous to differentiate between a click of the close button and the call to QWidget::close
According to your app's logic either call QWidget::closeEvent(event); to close the widget, or QEvent::ignore to leave it open
Example
I have prepared an example for you of how to implement the proposed solution:
#include <QMainWindow>
#include <QCloseEvent>
#include <QPushButton>
class FooWidget : public QWidget
{
Q_OBJECT
public:
explicit FooWidget(QWidget *parent = nullptr) :
QWidget(parent) {
auto *button = new QPushButton(tr("Close"), this);
connect(button, &QPushButton::clicked, this, &FooWidget::close);
resize(300, 200);
setWindowTitle("Foo");
}
protected:
void closeEvent(QCloseEvent *event) override {
if (event->spontaneous()) {
qDebug("The close button was clicked");
// do event->ignore();
// or QWidget::closeEvent(event);
} else {
QWidget::closeEvent(event);
}
}
};
class MainWindow : public QMainWindow
{
Q_OBJECT
FooWidget *pWindow;
public:
explicit MainWindow(QWidget *parent = nullptr) :
QMainWindow(parent),
pWindow(new FooWidget()) {
pWindow->show();
}
};
void QWidget::closeEvent(QCloseEvent *event) will be the possible way I would go with.
You can read the documentation here.
Before, check if Qt has a class for what you want to do. Maybe you want to use QDialog instead of QWidget for what you want to achieve.
The following code: suppose you want to delete the widget when the X is clicked and you just want to know when to do something.
Try connecting the signal from the base class QObject of your widget when it is Destroyed:
-Your Widget
-attribute setted to destroy your widget after X(closebotton is clicked) or the close() handler is triggered
-connect the destroyed() signal to whatever slot you want to do something before it is destroyed
pWindow = new QWidget();
pWindow->setAttribute(Qt::WA_DeleteOnClose,true);
connect(pWindow , SIGNAL(destroyed()), this,SLOT(processCloseButtonWindowsClicked()));
for more info:
https://doc.qt.io/qt-5/qwidget.html#close
https://doc.qt.io/qt-5/qobject.html#destroyed
I connected the clicked(bool) event from QPushButton to a private slot mySlot() of my own Widget. But the slot is never called (I placed a breakpoint in mySlot()). I'm using c++ and Qt5.
I wrote a minimal version of my code:
MyLayout.h
class MyLayout : public QWidget
{
Q_OBJECT
public:
MyLayout(QWidget* parent = NULL);
private:
QPushButton *next;
private slots:
void mySlot();
}
MyLayout.cpp
MyLayout::MyLayout(QWidget* parent) : QWidget(parent)
{
next = new QPushButton("Next Step");
QObject::connect(next, SIGNAL(clicked(bool)), this, SLOT(mySlot()));
}
void MyLayout::mySlot() { /* do something */ }
Any ideas?
You created a parentless button and never showed it. Start by giving it parent (this), so it gets shown together with your widget:
next = new QPushButton("Next Step", this);
Then learn how to use layouts.
I have a QStackedWidget within a QDockWidget - depending on which page is shown I wish to show/hide the close button on the QDockWidget. I can do this by using QDockWidget::setFeatures().
However the issue I'm facing is how to capture the signal of the close button so that I can change the dock features / set the stacked widget page index.
I have attempted to use an event filter:
class EventFilter : public QObject
{
Q_OBJECT
public:
EventFilter( QObject* aParent );
protected:
bool eventFilter(QObject *obj, QEvent *event);
};
EventFilter::EventFilter( QObject* aParent )
: QObject( aParent )
{
}
bool EventFilter::eventFilter( QObject *obj, QEvent *event )
{
if ( event->type() == QEvent::Close )
{
return true;
}
return QObject::eventFilter( obj, event );
}
And installed it as so:
EventFilter* filter = new EventFilter( this );
u->dockWidget_6->installEventFilter( filter );
In the constructor of my QMainWindow - the eventFilter() method did not get called.
So next I attempted to use the QDockWidget::visibilityChanged changed signal since it sounded like this might be what I wanted:
connect( u->dockWidget_6, SIGNAL(visibilityChanged(bool)), SLOT(dockWindowClosed(bool)) );
This slot did get called - but not when the close button was clicked.
Finally I attempted to use a promoted QDockWidget to capture the QWidget::closeEvent().
class DockWidgetWithCloseSignal : public QDockWidget
{
Q_OBJECT
public:
explicit DockWidgetWithCloseSignal(const QString &title, QWidget *parent = 0, Qt::WindowFlags flags = 0)
: QDockWidget( title, parent, flags )
{
}
explicit DockWidgetWithCloseSignal(QWidget *parent = 0, Qt::WindowFlags flags = 0)
: QDockWidget( parent, flags )
{
}
protected:
void closeEvent(QCloseEvent *event)
{
event->ignore();
}
};
I saw the constructor was called which means this new widget was indeed being used, but again no joy since the closeEvent() was never called when the close button was clicked.
Turns out that everything apart from the visibilityChanged signal works!
I added a signal to the overridden closeEvent() method which I could then connect to any slot I wanted.
The actual issue was that within the stacked widget I had another QDockWidget on another page, hence I was adding all of these things to the wrong QDockWidget! (And of course promoted the wrong QDockWidget too doh!).
Hopefully this question can serve as a reference to anyone else that needs to figure out how to do this - rather than why it isn't working.
Create a new CloseDockWidget based on DockWidget.
Override the closeEvent() method, but emit an additional closed() signal from there.
widgets/qclosedockwidget.cpp:
#include "qclosedockwidget.h"
namespace Widgets
{
QCloseDockWidget::QCloseDockWidget(const QString &title, QWidget *parent)
: QDockWidget(title, parent)
{
// constructor
}
void QCloseDockWidget::closeEvent(QCloseEvent *event)
{
emit closed(); // <------ signal
QDockWidget::closeEvent(event);
}
} // namespace Widgets
widgets/qclosedockwidget.h:
#ifndef QCLOSEDOCKWIDGET_H
#define QCLOSEDOCKWIDGET_H
#include <QDockWidget>
namespace Widgets
{
class QCloseDockWidget : public QDockWidget
{
Q_OBJECT
public:
QCloseDockWidget(const QString &title = "", QWidget *parent = nullptr);
protected:
void closeEvent(QCloseEvent *event);
signals:
void closed();
};
} // namespace Widgets
#endif // QCLOSEDOCKWIDGET_H
Now you are able to instantiate and connect to the new signal:
auto *dockWidget = new Widgets::QCloseDockWidget("MyDockWidget", this);
connect(dockWidget, &Widgets::QCloseDockWidget::closed, this, &MainWindow::dockWidgetCloseClicked);
Using Qt I create a QMainWindow and want to call a function AFTER the windows is shown. When I call the function in the constructor the function (a dialog actually) get's called before the window is shown.
If you want to do something while the widget is made visible, you can override QWidget::showEvent like this:
class YourWidget : public QWidget { ...
void YourWidget::showEvent( QShowEvent* event ) {
QWidget::showEvent( event );
//your code here
}
After analyzing the solutions above, it turns that all of them, including the heavily upvoted ones, are faulty.
Many recommend something like this:
class MyWidget : public QWidget {
// ...
};
void MyWidget::showEvent(QShowEvent* event) {
QWidget::showEvent(event);
DoSomething();
}
void MyWidget::DoSomething() {
// ...
}
This works as long as there is no QCoreApplication::processEvents(); in DoSomething. If there is one, it processes all events in the queue, including the QShowEvent which called MyWidget::showEvent in the first place. When it gets to the original QShowEvent, it calls MyWidget::showEvent again, causing an infinite loop.
If this happens, there are three solutions:
Solution 1. Avoid calling processEvents in MyWidget::DoSomething, instead call update or repaint when necessary. If DoSomething calls something else, these functions should avoid processEvents also.
Solution 2. Make DoSomething a slot, and replace direct call to DoSomething() by
QTimer::singleShot(0, this, SLOT(DoSomething()));
Since zero interval timer fires only when after all events in the queue are processed, it will process all events, including the original QShowEvent, remove them from the queue, and only then call DoSomething. I like it the most.
Since only zero interval timer fires only when after all events in the queue are processed, you should not try to "improve" it by lengthening the interval, for instance
QTimer::singleShot(50, this, SLOT(DoSomething())); // WRONG!
Since 50 ms is usually enough time for processing events in the queue, that would usually work, causing an error which is hard to reproduce.
Solution 3. Make a flag which prevents calling DoSomething the second time:
class MyWidget : public QWidget {
// ...
};
void MyWidget::showEvent(QShowEvent* event) {
if (is_opening)
return;
is_opening = true;
QWidget::showEvent(event);
DoSomething();
is_opening = false;
}
void MyWidget::DoSomething() {
// ...
}
Here, is_opening is a boolean flag which should be initialized as false in constructor.
try this:
in mainwindow.h:
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
protected:
void showEvent(QShowEvent *ev);
private:
void showEventHelper();
Ui::MainWindow *ui;
}
in mainwindow.cpp:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
void MainWindow::showEvent(QShowEvent *ev)
{
QMainWindow::showEvent(ev);
showEventHelper();
}
void MainWindow::showEventHelper()
{
// your code placed here
}
Follow Reza Ebrahimi's example, but keep this in mind:
Do not omit the 5th parameter of connect() function which specifies the connection type; make sure it to be QueuedConnection.
I.E.,
connect(this, SIGNAL(window_loaded), this, SLOT(your_function()), Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection));
I believe that you'd achieve what you need if you do it this way.
There are several types in signal-slot connections: AutoConnection, DirectConnection, QueuedConnection, BlockingQueuedConnection (+ optional UniqueConnection). Read the manual for details. :)
Assuming you want to run your code in the UI thread of the window after the window has been shown you could use the following relatively compact code.
class MainWindow : public QMainWindow
{
// constructors etc omitted.
protected:
void showEvent(QShowEvent *ev)
{
QMainWindow::showEvent(ev);
// Call slot via queued connection so it's called from the UI thread after this method has returned and the window has been shown
QMetaObject::invokeMethod(this, "afterWindowShown", Qt::ConnectionType::QueuedConnection);
}
private slots:
void afterWindowShown()
{
// your code here
// note this code will also be called every time the window is restored from a minimized state
}
};
It does invoke afterWindowShown by name but that sort of thing is fairly common practice in Qt. There are ways of avoiding this but they're a bit more verbose.
Note that this code should work for any QWidget derived class, not just QMainWindow derived classes.
In theory it might be possible for a very quick user to invoke some sort of action on the UI of the displayed window before afterWindowShown can be called but it seems unlikely. Something to bear in mind and code defensively against perhaps.
I found a nice answer in this question which works well, even if you use a Sleep() function.
So tried this:
//- cpp-file ----------------------------------------
#include "myapp.h"
#include <time.h>
#include <iosteream>
MyApp::MyApp(QWidget *parent)
: QMainWindow(parent, Qt::FramelessWindowHint)
{
ui.setupUi(this);
}
MyApp::~MyApp()
{
}
void MyApp::showEvent(QShowEvent *event) {
QMainWindow::showEvent(event);
QTimer::singleShot(50, this, SLOT(window_shown()));
return;
}
void MyApp::window_shown() {
std::cout << "Running" << std::endl;
Sleep(10000);
std::cout << "Delayed" << std::endl;
return;
}
//- h-file ----------------------------------------
#ifndef MYAPP_H
#define MYAPP_H
#include <QtWidgets/QMainWindow>
#include <qtimer.h>
#include <time.h>
#include "ui_myapp.h"
class MyApp : public QMainWindow
{
Q_OBJECT
public:
MyApp(QWidget *parent = 0);
~MyApp();
protected:
void showEvent(QShowEvent *event);
private slots:
void window_shown();
private:
Ui::MyAppClass ui;
};
#endif // MYAPP_H
I solved it without a timer using Paint event. Works for me at least on Windows.
// MainWindow.h
class MainWindow : public QMainWindow
{
...
bool event(QEvent *event) override;
void functionAfterShown();
...
bool functionAfterShownCalled = false;
...
}
// MainWindow.cpp
bool MainWindow::event(QEvent *event)
{
const bool ret_val = QMainWindow::event(event);
if(!functionAfterShownCalled && event->type() == QEvent::Paint)
{
functionAfterShown();
functionAfterShownCalled = true;
}
return ret_val;
}
The best solution for me is count once paint event:
.H
public:
void paintEvent(QPaintEvent *event);
.CPP
#include "qpainter.h"
#include <QMessageBox> // example
int contPaintEvent= 0;
void Form2::paintEvent(QPaintEvent* event)
{
if (contPaintEvent ==0 )
{
QPainter painter(this);
QMessageBox::information(this, "title", "1 event paint"); // example
// actions
contPaintEvent++;
}
}
Reimplement method void show() like this:
void MainWindow::show()
{
QMainWindow::show();
// Call your special function here.
}
I have two widgets defined as follows
class mainWindow : public QWidget
{
Q_OBJECT
public:
mainWindow();
void readConfig();
private:
SWindow *config;
QVector <QString> filePath;
QVector <QLabel*> alias,procStatus;
QVector <int> delay;
QGridLayout *mainLayout;
QVector<QPushButton*> stopButton,restartButton;
QVector<QProcess*> proc;
QSignalMapper *stateSignalMapper, *stopSignalMapper, *restartSignalMapper;
public slots:
void openSettings();
void startRunning();
void statusChange(int);
void stopProc(int);
void restartProc(int);
void renew();
};
class SWindow : public QWidget
{
Q_OBJECT
public:
SWindow(QWidget *parent=0);
void readConfig();
void addLine(int);
private:
QVector<QPushButton*> selectButton;
QVector<QLabel*> filePath;
QVector<QLineEdit*> alias;
QSignalMapper *selectSignalMapper;
QVector<QSpinBox*> delay;
QGridLayout *mainLayout;
public slots:
void selectFile(int);
void saveFile();
void addLineSlot();
};
when i create and display SWindow object from mainWindow like this
void mainWindow::openSettings()
{
config = new SWindow();
config->show();
}
everything is ok, but now i need to access the mainWindow from SWindow, and
void mainWindow::openSettings()
{
config = new SWindow(this);
config->show();
}
doesn't display SWindow. How can i display SWindow?
How do i call a function on widget close?
By default a QWidget isn't a window. If it is not a window and you specify a parent, it will be displayed inside the parent (so in your case it is probably hidden by other widgets inside your mainWindow).
Look at windowFlags() too. Or you could make your SWindow inherit from QDialog, depending on what you use it for.
As for calling a function on widget close : you could reimplement closeEvent().
When you do config = new SWindow(this); you're setting the parent of config to be the instance of mainWindow.
This means config is no longer a top-level widget, therefore it won't display outside the mainWindow instance (specifically, it would need to be the central widget or inside the mainWindow instance's layout to be displayed).
EDIT: Sorry - I missed your last question; How do i call a function on widget close
You will want to override the QWidget::closeEvent(QCloseEvent *event) method. This gets called when you close a top-level widget. The most practical thing to do is emit() a signal so that another class can handle it having been closed.
As noted by Leiaz, you can use the windowsFlags flag when you create the widget. It would look like this:
void mainWindow::openSettings()
{
config = new SWindow(this, Qt::window);
config->show();
}
To reimplement the closeEvent:
header:
protected:
virtual void closeEvent ( QCloseEvent * event )
cpp:
void sWindow::closeEvent(QCloseEvent *event)
{
this->parentWidget()->SomeFunction();
qWidget::closeEvent(event);
}
However, its probably better to use signal/slots for your case here. Since you said you want to call the parent's renew method on some button click in sWindow, what you want is to EMIT a signal everytime the button is clicked, and connect this signal in the parent with the parent's refresh slot.
void sWindow::sWindow()
{
...
connect(ui.button, SIGNAL(clicked()), this, SLOT(btnClicked()));
}
void sWindow::btnClicked()
{
// whatever else the button is supposed to do
emit buttonClicked();
}
and in your parent class
void mainWindow::openSettings()
{
config = new SWindow(this, Qt::window);
connect(config, SIGNAL(buttonClicked()), this, SLOT(refresh()));
config->show();
}