Solved Make dialog modal but don't block context menu
-
I have an application which has a pyqtgraph instance in a dialog box. I want this dialog box to be modal (I don't want the user to be able to interact with the main window while the dialog box is open), so normally I would set
setWindowModality()
toQt.ApplicationModal
. However, this blocks the pyqtgraph's context menu from being accessible (I can right click on the pyqtgraph instance, and it shows me the context menu, but I'm not allowed to click on it).I tried instead to set
setWindowModality()
toQt.WindowModal
. However, this locks the dialog box to the main window, which I don't like (I want the user to be able to see the main window while interacting with the dialog box).Is there anyway to either:
- Use
setWindowModality(Qt.ApplicationModal)
but not block the pyqtgraph context menu?
or - Use
setWindowModality(Qt.WindowModal)
but be able to undock the dialog box?
A minimal working example showing the behavior is below. You can see the 3 behaviors that result when setting the window modality to Qt.ApplicationModal, Qt.WindowModal, and Qt.NonModal.
import sys from PySide2.QtWidgets import * from PySide2.QtGui import * from PySide2.QtCore import * import pyqtgraph as pg class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) b = QPushButton() b.setText("Open dialog") self.setCentralWidget(b) b.clicked.connect(self.showdialog) def showdialog(self): self.d = CustomDialog(self) # # not modal # self.d.setWindowModality(Qt.NonModal) # self.d.show() # location can't be moved self.d.setWindowModality(Qt.WindowModal) self.d.exec_() # # blocks context menu # self.d.setWindowModality(Qt.ApplicationModal) # self.d.exec_() class CustomDialog(QDialog): def __init__(self, *args, **kwargs): super(CustomDialog, self).__init__(*args, **kwargs) buttons = QDialogButtonBox.Cancel self.buttonBox = QDialogButtonBox(buttons) self.buttonBox.rejected.connect(self.reject) self.layout = QVBoxLayout() self.layout.addWidget(pg.PlotWidget(self)) self.layout.addWidget(self.buttonBox) self.setLayout(self.layout) if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
- Use
-
A nice workaround was sent to me on the PyQtGraph forum (see https://groups.google.com/g/pyqtgraph/c/gNYYmVkP7hE).
We disable the entire parent window (repainting it to get around a separate bug: https://bugreports.qt.io/browse/PYSIDE-695), enable the dialog window (which was disabled automatically when its parent was disabled), throw in a Qt.WindowStaysOnTopHint window flag, and connect the dialog window's close to enable the parent window. This has almost the same effect as making the dialog modal (the only difference I can discern is that the parent window is grayed out), but the PyQtGraph object's context menu is accessible.
import sys from PySide2.QtWidgets import * from PySide2.QtGui import * from PySide2.QtCore import * import pyqtgraph as pg class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) b = QPushButton() b.setText("Open dialog") self.setCentralWidget(b) b.clicked.connect(self.showdialog) def showdialog(self): self.d = CustomDialog(self) # # not modal # self.d.setWindowModality(Qt.NonModal) # self.d.show() # # location can't be moved # self.d.setWindowModality(Qt.WindowModal) # self.d.exec_() # # blocks context menu # self.d.setWindowModality(Qt.ApplicationModal) # self.d.exec_() if sys.platform == 'darwin': self.setEnabled(False) self.repaint() self.d.setEnabled(True) self.d.setWindowFlag(Qt.WindowStaysOnTopHint) self.d.finished.connect(lambda: self.setEnabled(True)) self.d.show() # This dialog is not modal (though we make it modal manually). else: self.d.exec_() # This dialog is modal. class CustomDialog(QDialog): def __init__(self, *args, **kwargs): super(CustomDialog, self).__init__(*args, **kwargs) buttons = QDialogButtonBox.Cancel self.buttonBox = QDialogButtonBox(buttons) self.buttonBox.rejected.connect(self.reject) self.layout = QVBoxLayout() self.layout.addWidget(pg.PlotWidget(self)) self.layout.addWidget(self.buttonBox) self.setLayout(self.layout) if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
-
Update:
Apparently the pyqtgraph context menu only gets blocked on a Mac. When I run the working example using
Qt.ApplicationModal
on my Ubuntu OS, I can access the context menu. Still trying to figure out how to fix this issue on a Mac... -
Solution:
Replace:
self.d = CustomDialog(self)
with:
self.d = CustomDialog()
Then, using Qt.WindowModal won't anchor the dialog to its parent window, since it doesn't have a parent window.
-
Sorry, that is not a valid solution. That makes the dialog box modeless; it's the same as doing
self.d.setWindowModality(Qt.NonModal)
. The user can then access the MainWindow dialog, which I don't want.Any suggestions?
-
A nice workaround was sent to me on the PyQtGraph forum (see https://groups.google.com/g/pyqtgraph/c/gNYYmVkP7hE).
We disable the entire parent window (repainting it to get around a separate bug: https://bugreports.qt.io/browse/PYSIDE-695), enable the dialog window (which was disabled automatically when its parent was disabled), throw in a Qt.WindowStaysOnTopHint window flag, and connect the dialog window's close to enable the parent window. This has almost the same effect as making the dialog modal (the only difference I can discern is that the parent window is grayed out), but the PyQtGraph object's context menu is accessible.
import sys from PySide2.QtWidgets import * from PySide2.QtGui import * from PySide2.QtCore import * import pyqtgraph as pg class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) b = QPushButton() b.setText("Open dialog") self.setCentralWidget(b) b.clicked.connect(self.showdialog) def showdialog(self): self.d = CustomDialog(self) # # not modal # self.d.setWindowModality(Qt.NonModal) # self.d.show() # # location can't be moved # self.d.setWindowModality(Qt.WindowModal) # self.d.exec_() # # blocks context menu # self.d.setWindowModality(Qt.ApplicationModal) # self.d.exec_() if sys.platform == 'darwin': self.setEnabled(False) self.repaint() self.d.setEnabled(True) self.d.setWindowFlag(Qt.WindowStaysOnTopHint) self.d.finished.connect(lambda: self.setEnabled(True)) self.d.show() # This dialog is not modal (though we make it modal manually). else: self.d.exec_() # This dialog is modal. class CustomDialog(QDialog): def __init__(self, *args, **kwargs): super(CustomDialog, self).__init__(*args, **kwargs) buttons = QDialogButtonBox.Cancel self.buttonBox = QDialogButtonBox(buttons) self.buttonBox.rejected.connect(self.reject) self.layout = QVBoxLayout() self.layout.addWidget(pg.PlotWidget(self)) self.layout.addWidget(self.buttonBox) self.setLayout(self.layout) if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
-
I have the same problem on an OSX previously developed application, which was fully operational using PyQt5 V5.14.2 but stopped working from V5.15.0 onward. This seems to be a PyQt bug which is blocking the context menu mouse signal. Right click brings up the context menu fine but selecting an item is blocked using the mouse, using the keyboard however to navigate and select works fine. I have no idea how to raise this as a bug in Qt.
-
I have the same problem on an OSX previously developed application, which was fully operational using PyQt5 V5.14.2 but stopped working from V5.15.0 onward. This seems to be a PyQt bug
No this is a Qt bug. I have a same issue in C++. I have a menu set to a button in an Application Modal dialog and menu items are not selectable (macOS) since Qt5.15. I finally set it as WindowModal with a parent even if I'd prefer the window to have a title bar and be movable, not popping up inside parent window... Disabling parent and setting always on top is an idea but it would make all parent window disabled which is not a standard behavior.