小部件调用 show() 后 resizeEvent 不起作用

ToS*_*ity 3 python pyqt pyqt5

# 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 代码也只是保持沉默......

mus*_*nte 6

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 的虚拟特性)变得不如以前那么直观。

在您的情况下,继承遵循以下路径:

  1. [Python对象]
  2. Qt.QObject
  3. QtWidget.QWidget
  4. QtWidget.QAbstractButton
  5. QtWidget.QPushButton

因此,当您创建QPushButton('Test')(调用其__init__(self, *args, **kwargs))时,路径将被反转:

  1. __init__()一个 QtWidget.QPushButton,以新实例作为第一个参数和以下“测试”参数;
  2. QPushButton 调用继承的 QAbstractButton 类__init__(self, text),该类获取实例后的第一个参数作为按钮文本;
  3. QAbstractButton 将调用其“某些”方法或 QWidget 的方法来进行大小提示、字体管理等;
  4. 等等...

以同样的方式,每当yourButton.resizeEvent被调用时,路径都会相反:

  1. 寻找 QtWidget.QPushButton.resizeEvent
  2. 寻找 QtWidget.QAbstractButton.resizeEvent
  3. ...

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++ 对象的关系。