pythonpyqt5

How to add separator to menuBar in pyqt5


main window with menubar ,toolbar and statusbar

class TajirMWindow(QMainWindow):
    """Main Window."""
    def __init__(self, parent=None):
        """Initializer."""
        super().__init__(parent)
        self.setWindowTitle("Tajir/Home")
        self.resize(800, 400)
        self.centralWidget = QLabel("Hello, World")
        self.centralWidget.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.setCentralWidget(self.centralWidget)
        self._createActions()
        self._connectActions()
        self._createMenuBar() #Menus
        self._createToolBars()#tools
'''
Menu Bar function
'''
    def _createMenuBar():
        menuBar = self.menuBar()
        fileMenu = menuBar.addMenu("&File")
        outilMenu = menuBar.addMenu("&Outils")
        menuBar.addSeparator()
        achatMenu = menuBar.addMenu("Bying")
        stockMenu = menuBar.addMenu("Stocks")
        menuBar.addSeparator()
        helpMenu = menuBar.addMenu("&Help")

when i run the code no probleme accur but i didn t get the separator


Solution

  • QMenuBar doesn't support separators natively. It doesn't even support adding QWidgetActions, as they appear as empty actions and their widget are never actually shown.

    The only solution is to create a "fake" action with an unique identifier, and bypass the drawing of the action using a QProxyStyle: we override sizeFromContents() so that when the QMenuBar computes the sizes of its actions it returns an appropriate width, and drawControl() to draw a fake QFrame that is displayed as a VLine (like vertical separators in Designers).

    menubar with separators

    Important: this will not work on MacOS when using the nativeMenuBar.

    class MenuProxy(QtWidgets.QProxyStyle):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            # create a fake frame that we'll use as a source for the separator
            self.fakeFrame = QtWidgets.QFrame(frameShape=QtWidgets.QFrame.VLine)
    
        def sizeFromContents(self, content, opt, size, widget=None):
            size = super().sizeFromContents(content, opt, size, widget)
            if (content == self.CT_MenuBarItem and 
                isinstance(widget, QtWidgets.QMenuBar) 
                and opt.text == '_'):
                    # use the size hint of the frame to return the correct width
                    size.setWidth(self.fakeFrame.sizeHint().width())
            return size
    
        def drawControl(self, ctl, opt, qp, widget=None):
            if (ctl == self.CE_MenuBarItem and 
                isinstance(widget, QtWidgets.QMenuBar) and
                opt.text == '_'):
                    self.fakeFrame.resize(opt.rect.size())
                    frameOpt = QtWidgets.QStyleOptionFrame()
                    self.fakeFrame.initStyleOption(frameOpt)
                    # the frame will be drawn using its own coordinates (starting 
                    # from 0, 0), so we need to save the current state of the painter 
                    # and then translate to the correct position
                    qp.save()
                    qp.translate(opt.rect.topLeft())
                    # paint the "fake" frame
                    self.drawControl(self.CE_ShapedFrame, frameOpt, qp, self.fakeFrame)
                    # restore the painter to the previous position
                    qp.restore()
                    return
            super().drawControl(ctl, opt, qp, widget)
    
    
    class YourWindow(QtWidgets.QMainWindow):
        def __init__(self):
            # ...
            sep = QtWidgets.QAction('_', self, enabled=False)
            menuBar.addAction(sep)
            # ...
    
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        app.setStyle(MenuProxy())
    

    Note that: