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

Python 小部件调用show()后resizeEvent不工作,python,pyqt,pyqt5,Python,Pyqt,Pyqt5,此代码#1(当我手动调整窗口大小时)中从未触发新的resizeEvent 新的resizeEvent在这段代码#2中很好地触发(当我手动调整窗口大小时)。我可以看到味精打印出来 有人知道原因吗?即使我在代码1中的widget.update()和widget.show()之后添加了widget.resizeEvent=onResize,resizeEvent代码仍然保持沉默…PyQt的创建者菲尔·汤普森在一篇文章中谈到了这一点 PyQt缓存查找Python方法的重新实现。猴子 只有在查找方法之前对

此代码#1(当我手动调整窗口大小时)中从未触发新的resizeEvent

新的resizeEvent在这段代码#2中很好地触发(当我手动调整窗口大小时)。我可以看到味精打印出来


有人知道原因吗?即使我在代码1中的
widget.update()
widget.show()
之后添加了
widget.resizeEvent=onResize
,resizeEvent代码仍然保持沉默…

PyQt的创建者菲尔·汤普森在一篇文章中谈到了这一点

PyQt缓存查找Python方法的重新实现。猴子 只有在查找方法之前对其进行修补,修补才会起作用 这是第一次。如果您移动 在修补后调用show()


我认为最好的解决方案是实现您自己的
QPushButton
子类,或者在
show
方法之前将其指定为您的代码2。

resizeEvent
是一个“虚拟保护”方法,此类方法不应该像这样被“覆盖”

为了以安全的方式实现它们,您最好使用子类化:

# 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_())

Virtual方法是主要用于子类的函数

当子类调用该方法时,它将首先查找其类方法。
如果该方法存在,将调用该方法,否则将遵循MRO(如中所示):简单地说,“遵循从类到其继承的步骤,并在找到该方法时停止”

注意:在Python中,几乎所有东西都是虚拟的,除了像“magic methods”(dunder methods)之类的东西,它是一种内置的方法,包含在double下面的分数中,比如
\uu init\uuu
)。使用像PyQt这样的绑定,事情就复杂了一点,“猴子补丁”(完全依赖于Python的虚拟特性)变得不像以前那么直观了

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

  • [python对象]
  • Qt.QObject
  • QtWidget.QWidget
  • QtWidget.QAbstractButton
  • QtWidget.QPushButton
  • 因此,当您创建
    QPushButton('Test')
    (调用其
    初始化(self,*args,**kwargs)
    )时,路径将颠倒:

  • \uuuu init\uuuu()
    一个QtWidget.QPushButton,新实例作为第一个参数,下面的“test”参数
  • QPushButton调用继承的QAbstractButton类
    \uuuu init\uuuu(self,text)
    ,该类获取实例后的第一个参数作为按钮文本
  • QAbstractButton将调用它的一些方法或QWidget的一些方法来进行大小提示、字体管理等
  • 等等
  • 同样,无论何时调用
    yourButton.resizeEvent
    ,路径都会反转:

  • 查找QtWidget.QPushButton.resizeEvent
  • 查找QtWidget.QAbstractButton.resizeEvent

  • SIP(为Qt生成python绑定而创建的工具)使用虚拟缓存,一旦找到虚拟[继承]函数,如果另一个Qt函数需要该函数,则该函数将始终在将来被调用。这意味着,一旦没有找到python重写并且方法查找成功,该方法将从那时起一直被使用,除非显式调用(在python代码中使用“
    self.resizeEvent()
    ”)

    调用
    show()
    后,接收到一个
    QEvent.Resize
    (它本身就是一个虚拟对象);如果未覆盖
    event()
    ,则调用基类实现,它将查找类
    resizeEvent
    函数,并以事件作为参数调用它。
    因为,此时您还没有覆盖它,PyQt将退回到默认的widget resizeEvent函数,从那时起,PyQt将始终使用该函数(根据上面的列表和QPushButton实现,它将是基本的
    QWidget.resizeEvent
    调用)

    在第二个示例中,在覆盖
    resizeEvent
    函数后调用
    show()
    ,允许
    event()
    函数“查找”它(从而忽略其基本实现以及继承类中定义的实现),并将使用您的实现,直到程序退出。
    此外,在这种情况下,该方法可以再次被覆盖,因为SIP/Qt将不再使用缓存。这是一个微妙但仍然非常重要的区别,需要记住:从现在起,只要确定以前没有调用过该方法,就可以根据需要覆盖该实例(请注意粗体字符)方法

    class MyButton(QtWidgets.QPushButton):
        def resizeEvent(self, event):
            print("Nice to get here!")
    
    根据经验,在官方Qt文档中看到的所有标记为
    virtual/protected
    的内容通常都需要一个子类来正确覆盖它。您可以在定义右侧看到“
    [virtual protected]
    ”文本,该函数也在列表中


    PS:对于PyQt,某些级别的monkey补丁也适用于类(这意味着您可以覆盖将由其子类自动继承的类方法),但这并不能保证,而且它们的行为通常是意外的,特别是由于Qt的跨平台性质。它主要取决于类,它的内部C++行为和继承,以及SIP是如何与原始C++对象相关的。

    我对什么是不起作用,什么也不起作用感到困惑,抱歉。你说它是在代码2中激发的,但是它不是在代码2中激发的。。。你最后一句话的意思是“代码1”吗?仅当实际大小发生更改时才会触发Resize事件,这通常发生在第一次
    show()
    调用上,但不一定发生在其他
    show()
    调用上,并且不直接绑定到
    update()
    (可以在调整大小后调用update(),但不能反过来调用)。在代码#1中,当您第一次
    sh时,不会调用您的
    onResize
    class MyButton(QtWidgets.QPushButton):
        def resizeEvent(self, event):
            print("Nice to get here!")
    
    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_())