Search code examples
pythonpyqt5qmainwindowqdialog

QDialog as central widget layout in QMainWindow


Can you display a QDialog as the central widget of a QMainWindow in Python? If so, how would you do it? I am trying to find the easiest way to add a menu bar which is only available with QMainWindow to my understanding. Is it possible to connect the two together?


Solution

  • tl;dr

    No, but you can add a menubar to a layout set for the QDialog, using setMenuBar() or even by adding the menubar as you would do for any other widget, just by doing that on "top" of that layout.

    Is it possible?

    The technical answer is "yes": since QDialog is a QWidget, you can just use setCentralWidget() as you would do with any other QWidget subclass.

    The real answer is "NO: don't do it!".

    QDialog, just like QMainWindow, is intended to be a top level widget (aka, a "window"), so it should never be added as a child of a widget or in its layout.

    There are very few exceptions to that:

    • when using systems that are intended as possible "containers" of windows, which are:
    • in very special cases (such as attempting custom layouts of dock widgets), for which you should really know what you're doing, be really aware of the limitations and aspects related to the UX;

    Most importantly, QDialog has specific flags and event filters that might be problematic.

    For instance, take this simple example:

    mainWindow = QMainWindow()
    dialog = QDialog()
    layout = QVBoxLayout(dialog)
    layout.addWidget(QPushButton())
    mainWindow.setCentralWidget(dialog)
    mainWindow.show()
    

    Now, just press Esc, and you'll see that the dialog disappears.

    The same happens by adding a QDialog as a child of any widget, clearly meaning that it should never be done (unless when using the "container" systems listed above).

    This is one of the many reasons for which some tutorials on YouTube should be completely disregarded (since they provide terrible suggestions, like adding a QMainWindow or QDialog to a QStackedWidget).

    The solution

    Actually, it's very simple: just add the menubar to the top level layout of the dialog, just like you would do for any other widget.

    Besides, consider that all Qt layout managers inherit from QLayout, which has a very basic and important function that is often ignored: setMenuBar().

    Tells the geometry manager to place the menu bar widget at the top of parentWidget(), outside QWidget::contentsMargins(). All child widgets are placed below the bottom edge of the menu bar.

    Note that this is actually a "convenience" feature, and it only works for the "top level" layout: if you add the menubar to a nested layout, it won't be considered in the whole size hint/policy computation, and it will be probably shown above (in the z stacking level) any other widget near it.

    Also note that, for obvious reasons, this cannot be done from Designer. If you have a QDialog created in Designer and you want to add a menubar, you have to do it by code.

    Assuming that you properly set a top level layout (as you should always do) for your dialog in Designer:

    from myDialog import Ui_MyDialog
    from PyQt5.QWidgets import *
    
    class MyDialog(QDialog, Ui_MyDialog):
        def __init__(self):
            super().__init__()
            self.setupUi(self)
            self.menuBar = QMenuBar()
            self.layout().setMenuBar(self.menuBar)
    
            self.fileMenu = self.menuBar.addMenu('File')
            self.someAction = self.fileMenu.addAction('Some action')
    
    
    if __name__ == '__main__':
        import sys
        app = QApplication(sys.argv)
        dialog = MyDialog()
        dialog.exec()