Search code examples
pythonuser-interfacepyqt5windows-10qfiledialog

Menubar sometimes does not become un-greyed when QFileDialog closes


OS: W10. This may be significant. If you have different results on a different platform, feedback would be helpful.

Here is an MRE. If you run it and go Ctrl+O, the menu labels become greyed. If you select a file in the QFileDialog by clicking the "Open" button or using its mnemonic (Alt+O), the open-file dialog is dismissed and the "Files" and "Help" menus become un-greyed.

However, if you go Ctrl+O again, and this time enter the name of a file in the "File name" box (QLineEdit), and then press Return, the dialog is dismissed (with a successful selection result) but the "Files" and "Help" menus remain greyed-out. It looks like this:

enter image description here

import sys, os 
from PyQt5 import QtWidgets, QtCore, QtGui

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Greying of menus MRE')
        self.setGeometry(QtCore.QRect(100, 100, 400, 200))

        menubar = QtWidgets.QMenuBar(self)
        self.setMenuBar(menubar)
        self.files_menu = QtWidgets.QMenu('&Files', self)
        menubar.addMenu(self.files_menu)
        self.help_menu = QtWidgets.QMenu('&Help', self)
        menubar.addMenu(self.help_menu)
        self.new_action = QtWidgets.QAction('&New', self)
        self.files_menu.addAction(self.new_action)
        self.open_action = QtWidgets.QAction('&Open', self)
        self.files_menu.addAction(self.open_action)
        self.open_action.setShortcut("Ctrl+O")
        self.open_action.triggered.connect(self.open_file)
                
    def focusInEvent(self, event ):
        print('main_window focusInEvent')
        super().focusInEvent(event)

    def focusOutEvent(self, event ):
        print('main_window focusOutEvent')
        super().focusInEvent(event)
        
    def activateWindow(self):
        print('main_window activateWindow')
        super().activateWindow()    
        
    def open_file(self):
        print('open file')
        
        main_window_self = self

        # open_doc_dialog = QtWidgets.QFileDialog(self.get_main_window())
        class OpenDocFileDialog(QtWidgets.QFileDialog):
            def accepted(self):
                print('accepted')
                super().accepted()

            def accept(self):
                print('accept')
                super().accept()
        
            def close(self):
                print('close')
                super().close()
        
            def done(self, r):
                print(f'done r {r}')
                
                # neither of these solves the problem:
                # main_window_self.activateWindow()
                # main_window_self.files_menu.activateWindow()
                super().done(r)
        
            def hide(self):
                print(f'hide')
                super().hide()
                
            def focusInEvent(self, event ):
                print('focusInEvent')
                super().focusInEvent(event)
        
            def focusOutEvent(self, event ):
                print('focusOutEvent')
                super().focusInEvent(event)
                
            def activateWindow(self):
                print('activateWindow')
                super().activateWindow()    
                
        
        open_doc_dialog = OpenDocFileDialog(self)
        open_doc_dialog.setWindowTitle('Choose file')
        open_doc_dialog.setDirectory(os.getcwd())
        # we cannot use the native dialog, because we need control over the UI
        options = open_doc_dialog.Options(open_doc_dialog.DontUseNativeDialog)
        open_doc_dialog.setOptions(options)
        open_doc_button = open_doc_dialog.findChild(QtWidgets.QDialogButtonBox).button(QtWidgets.QDialogButtonBox.Open)
        lineEdit = open_doc_dialog.findChild(QtWidgets.QLineEdit)
        
        # this does not solve the problem
        # lineEdit.returnPressed.disconnect()
        # lineEdit.returnPressed.connect(open_doc_button.click)
        
        print(f'open_doc_button {open_doc_button}, lineEdit {lineEdit}')
        # show the dialog
        dialog_code = open_doc_dialog.exec()
        if dialog_code != QtWidgets.QDialog.Accepted: return
        sel_files = open_doc_dialog.selectedFiles()
        print(f'sel_files: {sel_files}')

                
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec())    

This problem can be understood, if not solved, with reference to this answer.

Note that this greying-out is not disablement. As explained in the above link, this has to do with "active/inactive states" of the menus (or their labels). The menus remain enabled throughout, although in this case it's impossible to know that while the open-file dialog is showing because it is modal. Clicking on one menu after the dialog has gone, or just hovering over it, is enough to un-grey them both...

The explanation, as I understand it, is that the "File name" box QLineEdit has a signal, returnPressed, which appears to activate something subtley different to the slot which is invoked when you use the "Choose" button. You can see I have experimented with trying to re-wire that signal, to no avail.

The method done of the QFileDialog appears to be called however the dialog closes (unlike close!), so I tried "activating" the main window... and then the individual QMenus... Doesn't work.

I am not clear how to get a handle on this "active state" business or why the slot connected to returnPressed is (seemingly) unable to give the "active state" back to the menus when the other slot manages to do so.

Edit
Searching on Musicamante's "unpolishing" suggestion led me to this:

lineEdit.returnPressed.disconnect()
def return_pressed():
    style = main_window_self.menubar.style()
    style.unpolish(main_window_self.menubar)
    open_doc_button.click()
lineEdit.returnPressed.connect(return_pressed)

... unfortunately this doesn't work.


Solution

  • This looks like a possible Windows-related bug, since I can't reproduce it on Linux. As a work-around, you could try forcing a repaint after the dialog closes:

    # show the dialog
    dialog_code = open_doc_dialog.exec()
    self.menubar.repaint()