Showing a QDialog from another one causes unexpected window stacking behaviour.
-
(Qt 5.12)
My application consists in a MainWindow instance which creates 2 dialogs instances (classes Dialog1 and Dialog2, derived from QDialog) at construction time.
Dialog1 and Dialog2 are identical, except for :- minor geometry differences.
- Dialog1 holds a pointer to the instance of Dialog2 (passed by MainWindow at construction time).
- Dialog2 has a button "show dialog2".
In particular, both Dialog1 and Dialog2 instances are constructed with the MainWindow instance as parent, and they are both modal : setModal(true);
The MainWindow has a button "show dialog1".
What I get works quite well : I can show the Dialog1 from the MainWindow, then I can show the Dialog2 from the dialog1.
I can close these dialogs in the reverse order and show them again.However, I am facing a strange behaviour with this simple application :
- Pressing the button "show dialog2" always succeeds in showing the Dialog2, but sometimes (quite frequently), this causes the MainWindow (and its Dialogs) to be lowered below an external window like my Qt Creator window (which gains focus), just as if I had clicked on it.
- Closing the Dialog2 always succeeds in hiding the Dialog2, but sometimes (quite frequently), this causes the MainWindow (and Dialog1) to be lowered below an external window (the very same issue).
I have noticed that pressing the button "show dialog1" (on then MainWindow) or closing the Dialog1 never causes this strange behaviour.
Question : Is this behavior a bug or is there something wrong in my code? How can I get rid of this issue with minimal change ?
main.cpp
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = 0); ~MainWindow(); }; #endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h" #include "dialog1.h" #include "dialog2.h" #include <QPushButton> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setGeometry(100, 100, 600, 400); Dialog2 * dialog2 = new Dialog2(this); Dialog1 * dialog1 = new Dialog1(dialog2, this); QPushButton * pushButton = new QPushButton("show dialog1", this); pushButton->setGeometry(50, 50, 75, 25); connect(pushButton, &QPushButton::pressed, this, [=](){ dialog1->show(); }); } MainWindow::~MainWindow() { }
dialog1.h
#ifndef DIALOG1_H #define DIALOG1_H #include <QDialog> class Dialog2; class Dialog1 : public QDialog { public: Dialog1(Dialog2 * dialog2, QWidget * parent = nullptr); }; #endif // DIALOG1_H
dialog1.cpp
#include "dialog1.h" #include "dialog2.h" #include <QPushButton> Dialog1::Dialog1(Dialog2 * dialog2, QWidget * parent) : QDialog(parent) { setWindowTitle("dialog1"); setGeometry(200, 200, 300, 200); setModal(true); QPushButton * pushButton = new QPushButton("show dialog2", this); pushButton->setGeometry(50, 50, 75, 25); connect(pushButton, &QPushButton::pressed, this, [=](){ dialog2->show(); }); }
dialog2.h
#ifndef DIALOG2_H #define DIALOG2_H #include <QDialog> class Dialog2 : public QDialog { public: Dialog2(QWidget * parent = nullptr); }; #endif // DIALOG2_H
dialog2.cpp
#include "dialog2.h" Dialog2::Dialog2(QWidget * parent) : QDialog(parent) { setWindowTitle("dialog2"); setGeometry(300, 300, 200, 150); setModal(true); }
-
@nostrapus said in Showing a QDialog from another one causes unexpected window stacking behaviour.:
Dialog2 * dialog2 = new Dialog2(this); Dialog1 * dialog1 = new Dialog1(dialog2, this);
This is exact the opposite you use it later on where dialog2 is a child or dialog1.
-
-
@nostrapus
I don't know why you have theMainWindow
as the parent ofdialog2
? You end up with (modal) dialogs which are siblings. If you makedialog1
the parent ofdialog2
instead, which is what I would have expected given your explanation, do you get different/better behaviour? -
Thanks for your reply.
Yes I know that the usual way to get this exemple work is to make dialog2 a child of dialog1. It works, I did this many times.
Unfortunately, this exemple just illustrates the issue I'am facing in a very minimalist way. My real application handles many dialogs by creating them first, than showing/hiding them "on demand". "On demand" here means that the order in wich these dialogs are shown/hidden is dynamic. From which (parent) window a dialog should be shown is dynamic too.
The simpliest way I've found to achieve this is too create all the dialogs at startup (this part is really a requirement), and set them as children of the MainWindow (because there is really no other objective choice at this time).
I am thinking of using the setParent function dynamically, each time a dialog needs to show up, but I couldn't get this work, even in the exemple I've given.Please note that the simple app in the exemple I have published behaves just as I expects, apart from the fact that sometimes a foreign window "stoles" the focus and gets the active window (I have noticed that this never happens if I press the buttons using the keyboard (<space>)). In particular, even if dialog1 and dialog2 are indeed siblings under the woods, the modal aspect works fine for me : no way for the user to access dialog1 while dialog2 is shown (which is what I expects because dialog2 is shown after).
So I'd like to understand this : does the simple code I've put in the exemple really break some of the usage requirements of Qt's API (in particular QDialog) ?
-
@nostrapus said in Showing a QDialog from another one causes unexpected window stacking behaviour.:
I am thinking of using the setParent function dynamically, each time a dialog needs to show up, but I couldn't get this work, even in the exemple I've given.
That's what I was going to suggest you might want to do. I would have expected it to work, I don't know why you say you could not get it to work. I wonder if you did not read https://doc.qt.io/qt-5/qdialog.html#details, see what it says about calling
setParent()
?There are potential problems. See e.g. https://forum.qt.io/topic/105157/reparenting-of-qdialog-leads-to-wrong-z-ordering-simple-example
Also, I am worried by your
In particular, even if dialog1 and dialog2 are indeed siblings under the woods, the modal aspect works fine for me : no way for the user to access dialog1 while dialog2 is shown (which is what I expects because dialog2 is shown after).
I was expecting that actually the user would be able to access
dialog1
whiledialog2
was open. I don't see why the "shown after" should actually guarantee to work the way you'd like it to, maybe this is not the case cross-platform?Anyway, best of luck.
-
In my example I expect the user can't access dialog1 while dialog2 is open because dialog1 is constructed with setModal(true);
The effects of setModal is explained in the doc you pointed.Thank you for your suggestion, I will have another try with setParent and its overloads in the next days, reading carrefully the documentation and the post you pointed.
If I can make setParent work, I should be able to use it in my real application with some work, although I'm not very pleased with this solution.
-
@nostrapus said in Showing a QDialog from another one causes unexpected window stacking behaviour.:
In my example I expect the user can't access dialog1 while dialog2 is open because dialog1 is constructed with setModal(true);
The effects of setModal is explained in the doc you pointed.(They both have
setModal(true)
.)Indeed, but that is not my understanding! The modality is with respect to the parent specified. I would expect both to block
MainWindow
(parent) input, but I see nothing about blocking each other as siblings. -
From the section Modal Dialogs in the documentation you pointed :
First §:
| A modal dialog is a dialog that blocks input to other visible windows in the same application. [...]Second §:
| When an application [(the default)] modal dialog is opened, the user must finish interacting with the dialog
| and close it before they can access any other window in the application. [...]This seems to me that the section Modal Dialogs doesn't say anything about the parent relationship.
Reading the description above this section, I could only get that the parent relationship affects the position and the taskbar entry. It also states that "the parent relationship does not imply that the dialog will always be stacked on top of the parent window" (which is why I'am using setModal(true);).
-
@nostrapus
I seem to keep getting it in my head that:QDialog(parentWidget) / setParent(parentWidget) => *window* modal QDialog(nullptr) / setParent(nullptr) => *application* modal
Now I see
setWindowModality(Qt::WindowModality windowModality)
. I have to also setsetWindowModality(Qt::WindowModal)
to get what I thought above (first case).I wonder whether this is why I have had problems in the past? :) or maybe I just forgot.
Anyway, ignore my previous in that light. And it wouldn't have necessarily helped your issue.
Finally, to say: if you cannot resolve this and it is a problem, can you do some explicit activating/focusing/raising in a
MainWindow
slot when detect dialog closure? But if another application is "(which gains focus), just as if I had clicked on it", this sounds like a windowing system thing, you may not be able to re-activate yourself? -
I finally could found a workaround :
Inserting a processEvents() right before showing dialog2 seems to remove the window stacking issue :connect(pushButton, &QPushButton::pressed, this, [=](){ qApp->processEvents(); dialog2->show(); });
With this single add, I could not reproduce the issue (by either pressing the button "show dialog2" or closing dialog2.
Unfortunately, in my case (my real application), it is likely that qApp->processEvents() will introduce unwanted side effects.
Ideally, I'd like to flush only mouse events (because I believe the issue is somehow caused by Qt doing some weird things with some of the mouse events involved by the user clicking this button, in this specific case - siblings QDialogs -, unless I misuse it). But there doesn't seem to be an easy way to achieve this.
Just delaying the execution of dialog2->show(); might be the simpliest approch in my specific case.I didn't spend more time to test the setParent approach because in my real application, getting the "parent window" is not obvious at the moment a dialog must be shown, because the reason why it is shown is decoupled. Not impossible but not easy.
As for your last suggestion, I've thought of some things like that (focus / activateWindow lost detection). Sadly this sounds more or less like admission of failure, a workaround subject to false positives, and that may suffer from unwanted visual effects.
Thank you very much for your advices and ideas, which helped me somehow.