# code#1
import sys
from PyQt5.QtWidgets import QApplication, QPushButton
def onResize(event):
print("Nice to get here!")
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = QPushButton('Test')
widget.resize(640, 480)
widget.show()
widget.resizeEvent = onResize
sys.exit(app.exec_())
Run Code Online (Sandbox Code Playgroud)
新的 resizeEvent 在这段代码中永远不会被触发#1(当我手动调整窗口大小时)。
# code#2
import sys
from PyQt5.QtWidgets import QApplication, QPushButton
def onResize(event):
print("Nice to get here!")
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = QPushButton('Test')
widget.resize(640, 480)
widget.resizeEvent = onResize
widget.show()
sys.exit(app.exec_())
Run Code Online (Sandbox Code Playgroud)
新的 resizeEvent 在此代码中很好地触发#2(当我手动调整窗口大小时)。我可以看到打印出的消息。
有谁知道原因吗?即使我在 code#1 中添加widget.update()andwidget.show()之后widget.resizeEvent = onResize,resizeEvent 代码也只是保持沉默......
resizeEvent是一个“虚拟保护”的方法,这样的方法不应该被这样“覆盖”。
为了以安全的方式实现它们,你最好使用子类化:
class MyButton(QtWidgets.QPushButton):
def resizeEvent(self, event):
print("Nice to get here!")
Run Code Online (Sandbox Code Playgroud)
虚方法是主要用于子类的函数。
当子类调用该方法时,它将首先在其类方法中查找。
如果该方法存在,则将调用该方法,否则将遵循 MRO(如方法解析顺序):简单地说,“按照从类返回到其继承的步骤,并在找到方法时停止”。
注意:在Python中,几乎所有东西都是虚拟的,除了“魔法方法”(“dunder方法”,包含在双引号内的内置方法,如__init__)。对于像 PyQt 这样的绑定,事情会变得更加复杂,并且“猴子修补”(完全依赖于 Python 的虚拟特性)变得不如以前那么直观。
在您的情况下,继承遵循以下路径:
因此,当您创建QPushButton('Test')(调用其__init__(self, *args, **kwargs))时,路径将被反转:
__init__()一个 QtWidget.QPushButton,以新实例作为第一个参数和以下“测试”参数;__init__(self, text),该类获取实例后的第一个参数作为按钮文本;以同样的方式,每当yourButton.resizeEvent被调用时,路径都会相反:
SIP(为 Qt 生成 python 绑定而创建的工具)使用virtuals缓存,一旦找到虚拟[继承]函数,如果另一个Qt函数需要它,将来总是会调用该函数。这意味着,一旦没有找到 python 覆盖并且方法查找成功,从那一刻起将始终使用该方法,除非显式调用(在 python 代码中使用“”)。self.resizeEvent()
调用后show(), aQEvent.Resize被接收QWidget.event()(它本身是一个虚拟值);如果event()没有被覆盖,则调用基类实现,它将查找类resizeEvent函数并以事件作为参数调用它。
因为此时您还没有覆盖它,PyQt 将回退到默认的小部件 resizeEvent 函数,并且从那时起它将始终使用该函数(根据上面的列表和 QPushButton 实现,它将是基本QWidget.resizeEvent调用)。
在您的第二个示例中,在覆盖函数后show()调用,允许函数“找到”它(从而忽略其基本实现以及继承类中定义的实现)并将使用您的实现,直到程序退出。
另外,在这种情况下,该方法可以再次被覆盖,因为 SIP/Qt 将不再使用缓存。这是一个微妙但仍然非常重要的区别,需要记住:从这一刻起,只要您确定以前没有调用过该实例(请注意粗体字符)方法,您就可以根据需要重写该方法!resizeEventevent()
def onResize1(event):
print("Nice to get here!")
def onResize2(event):
print("Can you see me?")
def changeResizeEvent(widget, func):
widget.resizeEvent = func
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = QPushButton('Test')
widget.resize(640, 480)
# change the resize event function *before* it might be called
changeResizeEvent(widget, onResize1)
widget.show()
# change the function again after clicking
widget.clicked.connect(lambda: changeResizeEvent(widget, onResize2))
sys.exit(app.exec_())
Run Code Online (Sandbox Code Playgroud)
根据经验,您在 Qt 官方文档中看到的所有标记为的内容virtual/protected通常都需要一个子类来正确覆盖它。您可以看到[virtual protected]定义右侧有“ ”文字resizeEvent(),并且该函数也在受保护函数列表中。
PS:使用 PyQt,某种程度的猴子修补也适用于类(这意味着您可以覆盖将由其子类自动继承的类方法),但这并不能保证,并且它们的行为通常是意外的,特别是由于跨平台性质Qt 的。它主要取决于类、其内部 C++ 行为和继承,以及 SIP 与原始 C++ 对象的关系。