How to display icon and text together in PYQT5 menubar

2.7k Views Asked by At

So, i want to display both the text and the ICON as a menubar item.

I have used the below statement as:

self.helpMenu = menubar1.addMenu(QtGui.QIcon("home.png"),"&TEXT")

But this displays only the icon and not the text. So need help to fix it

2

There are 2 best solutions below

0
musicamante On

Premise

It seems that, despite Qt provides an addMenu() function to create a menu that has both an icon and text, it is not fully supported.

There is a related and very old bug report on the matter, which has been flagged as closed and "Out of scope". I cannot test it right now, but I'm going to suppose that it's due to the native menubar support, which is mostly intended for macOS and Linux distros that support that feature.

That said, a workaround is possible, and that's done through a QProxyStyle.

It's a bit complex, but it works seamlessly given that:

  1. it's enabled only when the native menubar feature is not used (whether it's available or just disabled);
  2. it uses the 'fusion' style or the default style on Windows;

The trick is to ensure that the proxy returns a correct size for sizeFromContents() that includes both the text and the icon if both exist, and to use the default implementations as much as possible in drawControl() and drawItemText() (which is called from more standard styles).

menubar with icon and text

class MenuProxy(QtWidgets.QProxyStyle):
    menuHack = False
    alertShown = False

    def useMenuHack(self, element, opt, widget):
        if (element in (self.CT_MenuBarItem, self.CE_MenuBarItem) and
            isinstance(widget, QtWidgets.QMenuBar) and
            opt.icon and not opt.icon.isNull() and opt.text):
                if not self.alertShown:
                    if widget.isNativeMenuBar():
                        # this will probably not be shown...
                        print('WARNING: menubar items with icons and text not supported for native menu bars')
                    styleName = self.baseStyle().objectName()
                    if not 'windows' in styleName and styleName != 'fusion':
                        print('WARNING: menubar items with icons and text not supported for "{}" style'.format(
                            styleName))
                    self.alertShown = True
                return True
        return False

    def sizeFromContents(self, content, opt, size, widget=None):
        if self.useMenuHack(content, opt, widget):
            # return a valid size that includes both the icon and the text
            alignment = (QtCore.Qt.AlignCenter | QtCore.Qt.TextShowMnemonic |
                QtCore.Qt.TextDontClip | QtCore.Qt.TextSingleLine)
            if not self.proxy().styleHint(self.SH_UnderlineShortcut, opt, widget):
                alignment |= QtCore.Qt.TextHideMnemonic

            width = (opt.fontMetrics.size(alignment, opt.text).width() +
                self.pixelMetric(self.PM_SmallIconSize) +
                self.pixelMetric(self.PM_LayoutLeftMargin) * 2)

            textOpt = QtWidgets.QStyleOptionMenuItem(opt)
            textOpt.icon = QtGui.QIcon()
            height = super().sizeFromContents(content, textOpt, size, widget).height()

            return QtCore.QSize(width, height)

        return super().sizeFromContents(content, opt, size, widget)

    def drawControl(self, ctl, opt, qp, widget=None):
        if self.useMenuHack(ctl, opt, widget):
            # create a new option with no icon to draw a menubar item; setting
            # the menuHack allows us to ensure that the icon size is taken into
            # account from the drawItemText function
            textOpt = QtWidgets.QStyleOptionMenuItem(opt)
            textOpt.icon = QtGui.QIcon()
            self.menuHack = True
            self.drawControl(ctl, textOpt, qp, widget)
            self.menuHack = False

            # compute the rectangle for the icon and call the default 
            # implementation to draw it
            iconExtent = self.pixelMetric(self.PM_SmallIconSize)
            margin = self.pixelMetric(self.PM_LayoutLeftMargin) / 2
            top = opt.rect.y() + (opt.rect.height() - iconExtent) / 2
            iconRect = QtCore.QRect(opt.rect.x() + margin, top, iconExtent, iconExtent)
            pm = opt.icon.pixmap(widget.window().windowHandle(), 
                QtCore.QSize(iconExtent, iconExtent), 
                QtGui.QIcon.Normal if opt.state & self.State_Enabled else QtGui.QIcon.Disabled)
            self.drawItemPixmap(qp, iconRect, QtCore.Qt.AlignCenter, pm)
            return
        super().drawControl(ctl, opt, qp, widget)

    def drawItemText(self, qp, rect, alignment, palette, enabled, text, role=QtGui.QPalette.NoRole):
        if self.menuHack:
            margin = (self.pixelMetric(self.PM_SmallIconSize) + 
                self.pixelMetric(self.PM_LayoutLeftMargin))
            rect = rect.adjusted(margin, 0, 0, 0)
        super().drawItemText(qp, rect, alignment, palette, enabled, text, role)


class Test(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        menu = self.menuBar().addMenu(QtGui.QIcon.fromTheme('document-new'), 'File')
        menu.addAction(QtGui.QIcon.fromTheme('application-exit'), 'Quit')
        self.menuBar().addMenu(QtGui.QIcon.fromTheme('edit-cut'), 'Edit')


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    app.setStyle(MenuProxy(QtWidgets.QStyleFactory.create('fusion')))
    # or, for windows systems:
    # app.setStyle(MenuProxy())

    test = Test()
    test.show()
    sys.exit(app.exec_())
0
S. Nick On

I have the same story with Windows 7 and PyQt 5.12.2 and tried to solve it like this:

import sys
from PyQt5.Qt import *


class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.centralWidget = QLabel("Hello, World")
        self.centralWidget.setAlignment(Qt.AlignCenter)
        self.setCentralWidget(self.centralWidget)

        menuBar = QMenuBar(self)
        self.setMenuBar(menuBar)    
        
        self.helpContentAction = QAction(QIcon("img/readMe.png"), "&Help Content", self)
        self.aboutAction = QAction("&About", self)

#        helpMenu = menuBar.addMenu(QIcon("img/qtlogo.png"), "&Help")
        helpMenu = menuBar.addMenu("            &Help")                   # +++
#                                   ^^^^^^^^^^^^        
        
        helpMenu.addAction(self.helpContentAction)
        helpMenu.addAction(self.aboutAction)
 
qss = """        
QMenuBar {
    background-color: qlineargradient(
        x1:0, y1:0, x2:0, y2:1,
        stop:0 lightgray, stop:1 darkgray
    );
}
QMenuBar::item {
    background-color: darkgray;      
    padding: 1px 5px 1px -25px;                                           /* +++  */
    background: transparent;
    image: url(img/qtlogo.png);                                           /* +++ * /
}
QMenuBar::item:selected {    
    background-color: lightgray;
}
QMenuBar::item:pressed {
    background: lightgray;
}        
"""


if __name__ == "__main__":
    app = QApplication(sys.argv)
    
#    app.setStyle('Fusion')  
    app.setStyleSheet(qss)                                                # +++
    app.setFont(QFont("Times", 10, QFont.Bold))
    
    win = Window()
    win.setWindowTitle("Python Menus")
    win.resize(600, 350)
    win.show()
    sys.exit(app.exec_())

enter image description here