英文:
How to add a permanent icon before statusBar in QMainWindow?
问题
我想在QMainWindow的statusBar之前添加一个永久图标,我知道可以使用addWidget
方法来实现,但是使用这个方法添加的小部件只会在有消息时显示。
我还知道,在其他小部件中,可以通过使用自定义布局来包装statusBar来实现这一目标。但在MainWindow中,我无法修改statusBar的布局。
另外,我知道这可能可以通过重写paintEvent
方法来实现,但我对如何实现不太清楚。这是我想要实现的渲染:
1: https://i.stack.imgur.com/jlU12.png
英文:
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:
答案1
得分: 1
QStatusBar有其自己的内部布局系统,这有点复杂,不幸的是不能用作标准的Qt布局管理器。
不过,通过一些巧妙的方法,我们可以实现所需的结果。
诀窍是使用addWidget()
与两个小部件:
- 一个“图标小部件”,用于显示图标;
- 一个QLabel,用于显示状态提示;
请注意,虽然我们可以使用QLabel来显示图标,但这将强制我们显式设置图标大小并获取QIcon的QPixmap。如果我们使用QToolButton,我们可以只使用QIcon对象,Qt将自动设置适当的图标大小(基于当前样式的PM_SmallIconSize
像素度量)。
我们可以仅使用按钮的样式表来完全隐藏按钮边框(使其看起来不像按钮),但仍然可以获得其功能,如clicked
信号,甚至是自定义菜单。
然后,我们可以通过将messageChanged
信号连接到标准QLabel的setText()
槽来更新标签。请注意,标签应该具有明确的最小宽度(1),以便在状态提示过长的情况下,不会由于标签的大小提示而强制调整主窗口的大小。
不幸的是,仅仅执行上述操作是不够的,因为addWidget()
文档解释了:
小部件可能会被临时消息遮挡。
幸运的是,QStatusBar提供了reformat()
函数:
更改状态栏的外观以考虑项目更改。
特殊的子类可能需要此函数,但几何管理通常会处理任何必要的重新排列。
是的,我们需要这个函数,因为它所做的是“重置”内部布局,确保所有小部件(永久或非永久的)都可见,无论当前显示的消息是什么。
现在,问题是,即使在调用reformat()
后小部件会再次变为可见,原始状态消息仍然会从paintEvent()
函数中绘制,位于我们的小部件下方;默认情况下,不会自动填充其背景(如标签以及在我们的情况下设置了一些视觉属性的按钮)的基本小部件会显示其后面的一切内容:
天哪,这太糟糕了...
好吧,这里的诀窍是通过设置更进一步的样式表规则来“隐藏”文本,为状态栏的color
属性使用透明:它仍然会被“绘制”,但不会可见。
以下代码显示了如何实现上述内容,并包括一个示例,显示了一个带有QStyle标准图标的列表小部件:将鼠标悬停在图标上将在我们自己的标签中显示适当的状态提示,单击它们实际上将更改状态栏图标,因为我已经使用按钮的icon()
和setIcon()
函数“修补”了状态栏的这些函数。
结果将类似于以下内容:
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)
# 直接引用图标函数
self.icon = self._iconWidget.icon
self.setIcon = self._iconWidget.setIcon
# 强制按钮始终显示图标,即使当前样式默认不同
self._iconWidget.setToolButtonStyle(Qt.ToolButtonIconOnly)
# 仅设置任意图标
icon = self.style().standardIcon(QStyle.SP_MessageBoxInformation)
self.setIcon(icon)
self._statusLabel = QLabel()
self._statusLabel.setMinimumWidth(1) # 允许忽略大小提示
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())
英文:
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:
- an "icon widget" that will display the icon;
- 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:
<sup>Gosh, that's bad...</sup>
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:
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())
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论