Modeless dialog problem
-
Qt 5.9. Tested under Linux. Target both Linux & Windows (which I cannot test). Hopefully none of this is actually relevant.
I have a problem whereby a modeless dialog is getting put behind another dialog/window when I intend it to be 100% independent/top-level/free to be clicked to be either behind or on top, like any independent window.
-
Main window creates
QDialog
(call itmodalDialog
) with itself (main window) as parent, and displays it viaexec()
. Correctly produces a modal dialog:
-
That modal dialog creates another
QDialog
(call itmodelessDialog
) withNone
/nullptr
as parent, and displays it viashow()
. Produces a modeless dialog, but...:
See how the modeless dialog is behind the main window/currently displayed modal dialog? If I click on the modeless, it briefly brings it up top but then immediately brings the main window/modal dialog back on top of the modeless (this may be Linux/GNOME behaviour, I don't know). It's as though while inside anexec()
something checks and brings it back on top if something else is clicked to bring that on top?? That is not what I want: I wish the main+modal to be 100% independent of the modeless, I should be able to click either to be on top as I please. -
On a whim, I click the
X
to close the modal dialog. Now the modeless dialog is indeed fully independent of the main window, and I can click either to bring up-front.
This is the behaviour I want, but cannot achieve, even when a modal dialog is currently shown with anexec()
.
If anyone wants, here is about the minimal code I am using. See my comments about parent vs no parent and
exec()
vsshow()
.import sys from PyQt5 import QtWidgets class Main(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Main") self.setGeometry(100, 100, 500, 500) self.btnModal = QtWidgets.QPushButton("Open Modal", self) self.btnModal.clicked.connect(self.openModal) def openModal(self): # Note: dlgModal has parent QMainWindow self.dlgModal = QtWidgets.QDialog(self) self.dlgModal.setWindowTitle("Modal Dialog") self.dlgModal.setFixedSize(400, 400) self.dlgModal.btnModeless = QtWidgets.QPushButton("Open Modeless", self.dlgModal) self.dlgModal.btnModeless.clicked.connect(self.openModeless) # Note: dlgModal is opened modal via exec() # If I replace `exec()` with `show()` I do not get unwanted behaviour # but I do want this invoking dialog to be modal self.dlgModal.exec() def openModeless(self): # Note: dlgModal has parent None self.dlgModeless = QtWidgets.QDialog(None) self.dlgModeless.setWindowTitle("Modeless Dialog") self.dlgModeless.setFixedSize(300, 300) # Note: dlgModeless is opened modeless via show() # It should be "fully independent of anything else", but it is not...? self.dlgModeless.show() if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) main = Main() main.show() sys.exit(app.exec_())
If I replace the
self.dlgModal.exec()
byself.dlgModal.show()
I do not get the unwanted up-fronting behaviour, the modeless can go on top of it. But I do want the invoking dialog to be modal.... -
-
I didn't really get a perfect answer. An application-modeless dialog still gets blocked if you open an application-modal dialog, which is not what I want, but there it is. I do not want to change all my application's modal dialogs to be parent-window-modal to solve.
To move on I am redesigning so that when the modeless is opened all the modals get closed now. It'll have to do, it's time for me to close this issue now....
-
IIRC, modal dialogs always have focus, so are always on top. Also, I believe that exec() is the correct way to run a modal dialog, as the modal dialog has its own event loop.
What you are experiencing is the expected behaviour.
-
Hi @JonB,
Try replacing
self.dlgModal.exec()
withself.dlgModal.open()
, working perfectly on Windows 10.Note: " Unlike exec(), open() is asynchronous, and does not spin an additional event loop. So when using open() you can connect to the finished() signal of QDialog to be notified when the dialog is closed. " - Qt 5.12 docs
-
IIRC, modal dialogs always have focus, so are always on top.
What you are experiencing is the expected behaviour.Hmm, you may be correct. Which may not be good for me.
That would mean I can never make an application's modeless dialog/window be up-front whenever the app is showing a modal somewhere. Say it was a debug window --- I couldn't even do that?
One thing: from how it behaves under Linux, and what someone said looking at it under Windows, it sounded like this is not an OS windowing system behaviour/limitation. It looks like you can temporarily click to get the modeless up-front, but something in the
exec()
loop sees that and brings its modal dialog back on top. So a Qt behaviour, rather than a native windowing one. What do you think? -
@SamurayH
Thanks, will investigate tomorrow and see ifopen()
is required instead ofexec()
to get what I expect. Also I recall some "window on top" flag, maybe that affects behaviour (though I'm not sure that wouldn't introduce its own misbehaviours). Or there might be another window/dialog flag suitable?What I want is: a modeless dialog which belongs to the application but is simply unaffected by what other modal dialogs happen to be currently displayed elsewhere in the app. It's an independent window/dialog sitting there letting user interact with it whenever he wants till he closes it. He may need to interact with it while a modal is displayed, e.g. to garner information from the modeless to paste into the modal, or vice versa.
-
@JonB said in Modeless dialog problem:
So a Qt behaviour, rather than a native windowing one. What do you think?
I would agree with that assertion.
-
Try replacing
self.dlgModal.exec()
withself.dlgModal.open()
, working perfectly on Windows 10.I replaced
self.dlgModal.exec()
by
self.dlgModal.setWindowModality(QtCore.Qt.WindowModal) self.dlgModal.show() # or it turns out I can also still use here: # self.dlgModal.exec()
and sure enough that does behave as I wanted --- when the modeless is later opened it is quite independent of the invoking modal and I can switch between them as desired.
This shows the problem lies in the
dlgModal.windowModality()
behaviour. I then tried:self.dlgModal.setWindowModality(QtCore.Qt.ApplicationModal) self.dlgModal.show() # or self.dlgModal.exec()
and behaviour reverts to the undesirable.
QDialog.exec()
uses application modal, hence its behaviour:If the dialog is application modal, users cannot interact with any other window in the same application until they close the dialog. If the dialog is window modal, only interaction with the parent window is blocked while the dialog is open. By default, the dialog is application modal.
So this must be the root of my issue. However, I have a problem: application has many, many modal dialogs opened via
exec()
(without specifying anysetWindowModality()
), any of which might have a button which wants to open the independent modeless dialog. I do not want to have to change the behaviour in the invoking modal dialogs. What I need is something on opening the modeless dialog which makes it a top-level, independent window, without having to alter the invoking dialogs' code, which I should not have to. But it may be that is not possible... :(Can you think of anything I might try on opening the modeless which would make it independent of any application-modal dialog which might be open or become open at a later point??
-
I didn't really get a perfect answer. An application-modeless dialog still gets blocked if you open an application-modal dialog, which is not what I want, but there it is. I do not want to change all my application's modal dialogs to be parent-window-modal to solve.
To move on I am redesigning so that when the modeless is opened all the modals get closed now. It'll have to do, it's time for me to close this issue now....