是的,我看到了:
...但这些方法似乎都不适用于我的示例:
基本上,这是一个尝试使用 QtAwesome 为 QMessageBox 提供旋转图标的示例; QtAwesome 中有一个类
Spin
可以做动画,它有一个方法 _update
:我想对这个方法进行猴子补丁,所以首先调用原始方法,然后打印一条消息 - 所以我有这个:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox
import qtawesome as qta
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 220)
self.setWindowTitle('Hello World')
self.show()
msgBox = QMessageBox( QMessageBox.Information, "Title", "Content ...", QMessageBox.Cancel )
orig_ipmsize = msgBox.iconPixmap().size()
print(orig_ipmsize.width(), orig_ipmsize.height()) # 0 0 for QMessageBox.NoIcon; 32 32 for QMessageBox.Information
animation = qta.Spin(msgBox, autostart=True)
DO_MONKEYPATCH = 2 # 1 or 2
if DO_MONKEYPATCH == 1:
old_anim_update = animation._update
def new_anim_update(self):
old_anim_update(self) # TypeError: Spin._update() takes 1 positional argument but 2 were given
print("new_anim_update")
animation._update = new_anim_update.__get__(animation, qta.Spin) # https://stackoverflow.com/a/28127947
elif DO_MONKEYPATCH == 2:
def update_decorator(method):
def decorate_update(self=None):
method(self) # TypeError: Spin._update() takes 1 positional argument but 2 were given
print("decorate_update")
return decorate_update
animation._update = update_decorator(animation._update) # https://stackoverflow.com/a/8726680
#print(animation._update)
spin_icon = qta.icon('fa5s.spinner', color='red', animation=animation)
msgBox.setIconPixmap(spin_icon.pixmap(orig_ipmsize)) #msgBox.setIcon(spin_icon)
#animation.start()
returnValue = msgBox.exec()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
但是,无论我选择哪种
DO_MONKEYPATCH
方法,我都会得到TypeError: Spin._update() takes 1 positional argument but 2 were given
?
好吧,只是注意到在编写问题时这两个错误是如何应用的,并发现如果我将“旧方法”调用更改为不使用
(self)
参数 - 即我将 old_anim_update(self)
/ method(self)
更改为 old_anim_update()
/ method()
- 那么两个 DO_MONKEYPATCH
方法都允许在没有位置参数错误的情况下运行 - 但是只有 DO_MONKEYPATCH
方法 1 似乎保留 self
:
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox
import qtawesome as qta
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 220)
self.setWindowTitle('Hello World')
self.show()
msgBox = QMessageBox( QMessageBox.Information, "Title", "Content ...", QMessageBox.Cancel )
orig_ipmsize = msgBox.iconPixmap().size()
print(orig_ipmsize.width(), orig_ipmsize.height()) # 0 0 for QMessageBox.NoIcon; 32 32 for QMessageBox.Information
animation = qta.Spin(msgBox, autostart=True)
DO_MONKEYPATCH = 1 # 1 or 2
if DO_MONKEYPATCH == 1:
old_anim_update = animation._update
def new_anim_update(self):
old_anim_update() # no error
print("new_anim_update {}".format(self)) # self is <qtawesome.animation.Spin object at 0x00000238f1d45f10>
animation._update = new_anim_update.__get__(animation, qta.Spin) # https://stackoverflow.com/a/28127947
elif DO_MONKEYPATCH == 2:
def update_decorator(method):
def decorate_update(self=None):
method() # TypeError: Spin._update() takes 1 positional argument but 2 were given
print("decorate_update {}".format(self)) # self is None
return decorate_update
animation._update = update_decorator(animation._update) # https://stackoverflow.com/a/8726680
#print(animation._update)
spin_icon = qta.icon('fa5s.spinner', color='red', animation=animation)
msgBox.setIconPixmap(spin_icon.pixmap(orig_ipmsize)) #msgBox.setIcon(spin_icon)
#animation.start()
returnValue = msgBox.exec()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
所以,似乎
DO_MONKEYPATCH == 1
方法是最初提出的这个问题的答案 - 但我仍然担心,我对 old_anim_update()
的调用,在没有任何对 self
的原始引用的情况下,如何正确调用旧方法?或者有更正确的方法来做这种monkeypatch吗?
仅执行
old_anim_update()
而不使用 self
有效的原因是因为 old_anim_update
已经是对实例方法的引用,并且您不需要使用 self
来调用它。
当您执行
update_decorator(animation._update)
时,您将传递对已经 bound 到实例的函数的引用,从而成为实例方法。
澄清上述内容的简化版本可能如下:
something = animation._update
something()
上面将自动调用
qta.Spin._update()
,并将 animation
作为其 self
。
self
是必要参数的情况是,如果您使用对 class 的命名空间中的方法的引用:
something = qta.Spin._update
something(animation)
在上面的例子中,
animation
参数变成了self
中的def _update(self)
。这就是在 super()
出现之前,Python 中通常调用默认实例方法的方式:上面的内容与 qta.Spin._update(animation)
相同。
请注意,虽然猴子修补通常可以很好地处理简单的方面,但通常最好创建一个子类,特别是因为这使其可重用。
一个可能的解决方案是只使用
parent_widget
(这是消息框),并使用样式的度量来获取标准像素图大小,这就是 QMessageBox 内部实际执行的操作:
class MsgBoxSpin(qta.Spin):
def _update(self):
super()._update()
iconSize = self.parent_widget.style().pixelMetric(
QStyle.PM_MessageBoxIconSize, None, self.parent_widget)
self.parent_widget.setIconPixmap(self.icon.pixmap(
iconSize, iconSize))
def setIcon(self, icon):
self.icon = icon
...
animation = qta.Spin(msgBox, autostart=True)
spin_icon = qta.icon('fa5s.spinner', color='red', animation=animation)
animation.setIcon(spin_icon)
上面的内容是必要的,因为给定的父级实际上是消息框,并且
_update()
将调用 its QWidget.update()
,这可能在显示像素图的小部件上没有结果;此外,qta.Spin
对象实际上对所显示的实际图标一无所知,这意味着:
它可以工作,但它也有点笨重。
不过,还有另一种选择。当我只想显示 QIcon 时,我有时会使用没有设置边框或背景且禁用任何交互的虚拟按钮。
QMessageBox 在 QLabel 中显示图标,该图标具有硬编码的对象名称 (
qt_msgboxex_icon_label
),因此我们可以使用 QObject.findChild()
检索它,为其创建布局,并向其添加虚拟按钮。最后,我们设置一个具有适当大小的透明 QPixmap(如上所述),确保它始终需要必要的空间,即使它是空的。class MessageBox(QMessageBox):
_iconWidget = None
def setIcon(self, icon):
if isinstance(icon, QMessageBox.Icon):
super().setIcon(icon)
if self._iconWidget:
self._iconWidget.deleteLater()
self._iconWidget = None
elif isinstance(icon, QIcon):
self.iconWidget().setIcon(icon)
def iconWidget(self):
if not self._iconWidget:
label = self.findChild(QLabel, 'qt_msgboxex_icon_label')
iconSize = self.style().pixelMetric(
QStyle.PM_MessageBoxIconSize, None, self)
pm = QPixmap(iconSize, iconSize)
pm.fill(Qt.transparent)
self.setIconPixmap(pm)
layout = label.layout()
if layout is None:
layout = QVBoxLayout(label)
layout.setContentsMargins(0, 0, 0, 0)
self._iconWidget = QPushButton()
layout.addWidget(self._iconWidget)
self._iconWidget.setStyleSheet('''
QPushButton {
border: none;
background: none;
}
''')
self._iconWidget.setIconSize(QSize(iconSize, iconSize))
self._iconWidget.setFocusPolicy(Qt.NoFocus)
self._iconWidget.setAttribute(Qt.WA_TransparentForMouseEvents)
return self._iconWidget
然后您可以使用自定义消息框功能创建图标
iconWidget()
(因为它需要父级进行更新),并且您不再需要任何猴子修补或图标子类化。