pythonpyqtpyqt5qdialogmodeless

How to create an independent non-modal dialog


I'm trying to extend this solution Non modal dialog

from PyQt5 import QtWidgets
dialog = None


class Dialog(QtWidgets.QDialog):
    def __init__(self, *args, **kwargs):
        super(Dialog, self).__init__(*args, **kwargs)
        self.setWindowTitle('A floating dialog')
        self.resize(250,250)


class Window(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        button = QtWidgets.QPushButton('Open Dialog', self)
        button.clicked.connect(self.handleOpenDialog)
        self.resize(300, 200)
        self._dialog = None
        global dialog
        dialog = Dialog(self)
        dialog.show()

    def handleOpenDialog(self):
        if self._dialog is None:
            self._dialog = QtWidgets.QDialog(self)
            self._dialog.resize(200, 100)
        self._dialog.exec_()


if __name__ == '__main__':

    import sys
    app = QtWidgets.QApplication(sys.argv)
    win = Window()
    win.show()
    sys.exit(app.exec_())

Apology if title wasn't relevant. I want to have a dialog/window that is independent of all existing window/dialogs, and can be always interacted with, i.e. GUI loop of application window/any dialogs doesn't block this non-model dialog. For simplicity, I have used global variable dialog in above code snippet which will hold the non-modal dialog instance.

When above program is run, the main window appears along-with the non-modal dialog, and both dialogs are user interactive, but when the button is clicked, the GUI loop of self._dialog starts, and user can no longer interact with the floating dialog, and application window. What I want is to be able to interact with dialog but not with Window

I want behavior similar to the example below:

enter image description here

I opened help dialog from main window, then I opened a non-modal dialog which appears on top of the main window, and can not interact with the main window, but still doesn't block help dialog/window and allows user to interact with this non-modal window i.e. the help dialog in the example.


Solution

  • When a dialog is opened with exec(), it will default to being application-modal. This means it will block all other windows in the application, regardless of whether they're parented to other windows or not. To make a dialog modal for only one window, it must be parented to that window and also have its modality explicitly set to window-modal.

    For a dialog to be fully non-modal with respect to all other windows (and any of their modal dialogs), it must have no parent and then be opened with show(). However, a side-effect of this is that it won't be automatically closed when the main-window is closed. To work around this, it can be explicitly closed in the closeEvent() of the main-window.

    Here is a simple demo that implements all of the above:

    import sys
    from PyQt5 import QtCore, QtWidgets
    
    class Window(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()
            self.setWindowTitle('Main Window')
            self.setGeometry(400, 100, 300, 200)
            self._help_dialog = None
            self._other_dialog = None
            self.buttonHelp = QtWidgets.QPushButton('Open Help')
            self.buttonHelp.clicked.connect(self.handleOpenHelp)
            self.buttonDialog = QtWidgets.QPushButton('Open Dialog')
            self.buttonDialog.clicked.connect(self.handleOpenDialog)
            layout = QtWidgets.QHBoxLayout(self)
            layout.addWidget(self.buttonDialog)
            layout.addWidget(self.buttonHelp)
            self.handleOpenHelp()
    
        def handleOpenDialog(self):
            if self._other_dialog is None:
                self._other_dialog = QtWidgets.QDialog(self)
                self._other_dialog.setWindowModality(QtCore.Qt.WindowModal)
                self._other_dialog.setWindowTitle('Other Dialog')
                self._other_dialog.resize(200, 100)
            self._other_dialog.exec_()
    
        def handleOpenHelp(self):
            if self._help_dialog is None:
                self._help_dialog = QtWidgets.QDialog()
                self._help_dialog.setWindowTitle('Help Dialog')
                self._help_dialog.setGeometry(750, 100, 250, 250)
            self._help_dialog.show()
    
        def closeEvent(self, event):
            if self._help_dialog is not None:
                self._help_dialog.close()
    
    if __name__ == '__main__':
    
        app = QtWidgets.QApplication(sys.argv)
        window = Window()
        window.show()
        sys.exit(app.exec_())