Modal windows have event handling issues when QApplication.focusChanged() is used to set QSS of widget
-
What I'm trying to do is effortlessly change QDockWidget style on
focusIn
/focusOut
events of children, as described over here (https://stackoverflow.com/a/50001844/714907). Problem is that when I open any kind of Qt-derived modal window and try to set QSS, buttons of that window behave extremely weird: once pressed, they get stuck in pressed state. Though this doesn't happen for not standard buttons -- the ones not included inQDialogButtonBox
, the ones you add as a normal widgets.Everything works when I don't set QSS in else-case from the example -- for the widget that lost focus. Same is true for empty QSS, thus it's not the calls to
setStyleSheet()
that cause this. With zero-wait single shot timer buttons get stuck in unpressed state, though I think you can notice extremely fast change to pressed state. Changing window title works flawlessly, so changing of properties of QDockWidget in such case isn't the issue. But here's what's interesting: native Qt modal windows show red background along with QDockWidget, meaning that both widgets have focus, whereas native OS modal dialogs show parent QDockWidget in blue. I.e., could it be some glitch with improper setting of focus, and Qt doesn't remove focus from QDockWidget before opening modal window?BTW, here's somewhat related issue: https://forum.qt.io/topic/32850/qapplication-focuschanged-does-not-actually-report-focus-events-qt-4-8/3 Fix described in last post worked for me -- use of modeless windows. That's how I was able to figure out that this is modality-based issue.
Here's the example that reproduces this issue. Also, it does the same thing in PyQt5, which most likely means it's not a bug from one of those wrappers. I have painted entire widget rather than
QDockWidget
, so that it was easier to spot changes of focus.from PySide2.QtWidgets import * from PySide2.QtCore import * from PySide2.QtGui import * import sys class Dock(QDockWidget): def __init__(self, *arg, **kwarg): QDockWidget.__init__(self, *arg, **kwarg) QApplication.instance().focusChanged.connect(self.focusInAppChanged) def focusInAppChanged(self, old, new): w = new while w and w != self: w = w.parentWidget() if w == self: print('x'*3, self.windowTitle()) qss = 'QWidget { background-color: red; }' self.setStyleSheet(qss) # self.setStyleSheet('') # QTimer.singleShot(0, lambda: self.setStyleSheet(qss)) # self.setWindowTitle('focus') # in this case it's OK to set QSS on any kind of widget, not just QDockWidget -- # this way we can see which widget has focus. as explained above, said issue # occurs only in if-case. else: print('x', self.windowTitle()) qss = 'QWidget { background-color: blue; }' self.setStyleSheet(qss) # self.setStyleSheet('') # QTimer.singleShot(0, lambda: self.setStyleSheet(qss)) # self.setWindowTitle('no focus') if __name__ == '__main__': app = QApplication([]) app.setStyle('Fusion') w = QMainWindow() d1 = Dock('d1') b1 = QPushButton('Qt modal window') b1.pressed.connect(lambda: QInputDialog.getText(d1, 'getText()', 'enter text')) d1.setWidget(b1) w.addDockWidget(Qt.RightDockWidgetArea, d1, Qt.Horizontal) d2 = Dock('d2') b2 = QPushButton('OS modal window') b2.pressed.connect(lambda: QFileDialog.getOpenFileName(d2, 'getOpenFileName()', '.')) d2.setWidget(b2) w.addDockWidget(Qt.RightDockWidgetArea, d2, Qt.Horizontal) d3 = Dock('d3') d3.setWidget(QTextEdit('d3')) w.addDockWidget(Qt.RightDockWidgetArea, d3, Qt.Horizontal) w.show() sys.exit(app.exec_())
-
OK, so I just tried to pass
QMainWindow
instance as a parent toQFileDialog
, and it seems that this focus issue is gone.So now my question is this: does anyone else think this is a bug, so that I could file bug report? I wonder if it's something related to modality type as described over here (http://doc.qt.io/qt-5/qdialog.html#modal-dialogs), like
QDockWidget
being able to turn intoQt.Tool
. But then again, by defaultQDialog
-based classes useQt.ApplicationModal
... Anyway, what you think: is this a bug? -
Hi,
Just a wild guess but the dialog that you invoke triggers a local event loop that blocks the application flow until you’re done with it. That event loop now processes all the events, so there might be something regarding that.