1

I want to add a permanent icon before the statusBar in QMianWindow, and I know that there is an addWidget method that can be used, but the widgets added by this method will only be displayed when there are messages.

I also know that in other widgets, this can be achieved by using a custom layout to wrap the statusBar. But in MainWindow, I can't modify the layout of the statusBar.

Additionally, I know that this may be achieved by rewriting the paintEvent method, but I am not very clear on how to implement it. Here is the rendering I want to achieve:

enter image description here

Pamputt
  • 173
  • 1
  • 11
Poison
  • 75
  • 1
  • 6
  • if you want only one icon on your toolbar and nothing else, you can add your widget , set its icon, then you can add a flag do display only icons on your toolbar :`this->addWidget(yourWidgetWithYourIcon); this->setToolButtonStyle(Qt::ToolButtonIconOnly); ` My bad it's only working on objects inheriting qToolBar, but maybe that's what you really need ? qStatusBar are designed to display temporary status info – Oka Jun 06 '23 at 12:24
  • @Oka Thanks. In fact, I can completely use universal widgets instead of MainWindow to achieve my goal. But I'm curious if there's a way to do the same in Mainwindow. As we known, QStatusBar actually provides a method called `addPermanentWidget` to add a permanent widget on its right side, but it can only be added on the right side and cannot be added on the left side – Poison Jun 06 '23 at 14:09
  • @Poison You could create your own status bar. As long as you don't use toolbars or dock widgets at the *bottom* of the main window, you could even add *any* QWidget subclass at the end of the central widget's layout, then you can intercept any `StatusTip` event type in the `event()` of the main window and show the event's `tip()` in that custom widget: that's what QMainWindow actually does whenever a QStatusBar has been set. – musicamante Jun 07 '23 at 02:22
  • @musicamante Thanks. Your answer is a great idea that has greatly benefited me. – Poison Jun 09 '23 at 08:57

1 Answers1

1

QStatusBar has its own internal layout system, which is a bit complex, and unfortunately cannot be used as a standard Qt layout manager.

With some ingenuity, though, we can achieve the wanted result.

The trick is to use addWidget() with two widgets:

  1. an "icon widget" that will display the icon;
  2. a QLabel that will display the status tip;

Note that while we could use a QLabel for the icon, that will force us to explicitly set the icon size and get the QPixmap of the QIcon. If we use a QToolButton, instead, we can just use QIcon objects, and Qt will automatically set an appropriate icon size (based on the PM_SmallIconSize pixel metric of the current style).

We can just use a style sheet for the button in order to completely hide the button border (so that it will not look like a button) and still get its features, such as the clicked signal, or even a custom menu.

Then, we can update the label by connecting the messageChanged signal to the standard QLabel setText() slot. Note that the label should have an explicit minimum width (1) so that, in case the status tip is too long, it won't force the resizing of the main window due to the label's size hint.

Unfortunately, just doing the above will not be enough, as the addWidget() documentation explains:

The widget [...] may be obscured by temporary messages.

Luckily, QStatusBar provides the reformat() function:

Changes the status bar's appearance to account for item changes.

Special subclasses may need this function, but geometry management will usually take care of any necessary rearrangements.

And, yes, we need this function, as what it does is to "reset" the internal layout, ensuring that all widgets (permanent or not) will be visible, no matter the currently shown message.

Now, the problem is that even if the widgets would become visible again after calling reformat(), the original status message would be painted anyway from the paintEvent() function, under our widgets; by default, basic widgets that don't autofill their background (such as labels, and buttons with some visual properties set, like in our case), will show everything that is behind them:

enter image description here

Gosh, that's bad...

Well, the trick here is to "hide" the text by setting a further style sheet rule, using a transparent color property for the status bar: it will be "painted" anyway, but it won't be visible.

The following code shows how the above was implemented, and includes an example that shows a list widget with QStyle standard icons: hovering them will show a proper status tip in our own label, and clicking them will actually change the status bar icon, since I've "patched" the icon() and setIcon() functions of the status bar with those of the button.

The result will be something like this:

Screenshot of the example code

class IconStatusBar(QStatusBar):
    iconClicked = pyqtSignal(bool)
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setStyleSheet('''
            QStatusBar { color: transparent; }
            QToolButton#statusBarIconWidget { border: none; }
        ''')

        self._iconWidget = QToolButton(objectName='statusBarIconWidget')
        self.addWidget(self._iconWidget)
        # add direct references to the icon functions
        self.icon = self._iconWidget.icon
        self.setIcon = self._iconWidget.setIcon
        # force the button to always show the icon, even if the 
        # current style default is different
        self._iconWidget.setToolButtonStyle(Qt.ToolButtonIconOnly)

        # just set an arbitrary icon
        icon = self.style().standardIcon(QStyle.SP_MessageBoxInformation)
        self.setIcon(icon)

        self._statusLabel = QLabel()
        self._statusLabel.setMinimumWidth(1) # allow ignoring the size hint
        self.addWidget(self._statusLabel)

        self.messageChanged.connect(self._updateStatus)
        self._iconWidget.clicked.connect(self.iconClicked)

    def _updateStatus(self, text):
        self.reformat()
        self._statusLabel.setText(text)


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)

    test = QMainWindow()
    statusBar = IconStatusBar()
    test.setStatusBar(statusBar)

    listWidget = QListWidget()
    test.setCentralWidget(listWidget)

    listWidget.setMouseTracking(True)
    style = app.style()
    for sp in range(80):
        icon = style.standardIcon(sp)
        if icon.isNull():
            continue
        item = QListWidgetItem(icon, 'Standard Icon #{}'.format(sp))
        item.setStatusTip('Click to set #{} as status bar icon'.format(sp))
        listWidget.addItem(item)

    def setIcon(item):
        statusBar.setIcon(item.icon())
    listWidget.itemClicked.connect(setIcon)

    test.show()
    sys.exit(app.exec())
musicamante
  • 41,230
  • 6
  • 33
  • 58