ToSimplicity
ToSimplicity

Reputation: 303

resizeEvent not working after widget has called show()

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

The new resizeEvent is never fired in this code#1(when I resize the window manually).

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

The new resizeEvent is nicely fired in this code#2(when I resize the window manually). And I can see msg printed out.

Does anyone know the reason? Even I add widget.update() and widget.show() after widget.resizeEvent = onResize in code#1, the resizeEvent code just keeps silence...

Upvotes: 3

Views: 2664

Answers (2)

musicamante
musicamante

Reputation: 48529

resizeEvent is a "virtual protected" method, and such methods should not be "overwritten" like this.

To implement them in a safe fashion, you'd better use subclassing:

class MyButton(QtWidgets.QPushButton):
    def resizeEvent(self, event):
        print("Nice to get here!")

Virtual methods are functions that are mostly used for subclasses.

When a subclass calls that method, it will look up in its class methods first.
If that method exists that method will be called, otherwise the MRO (as in Method Resolution Order) will be followed: simply speaking, "follow the steps back from the class to its inheritation(s) and stop when the method is found".

NOTE: in Python, almost everything is virtual, except things like "magic methods" ("dunder methods", the builtin methods that are enclosed within double underscores like __init__). With bindings like PyQt things are a bit more complex, and "monkey patching" (which completely relies on the virtual nature of Python) becomes less intuitive than it was.

In your case, the inheritance follows this path:

  1. [python object]
  2. Qt.QObject
  3. QtWidget.QWidget
  4. QtWidget.QAbstractButton
  5. QtWidget.QPushButton

So, when you create QPushButton('Test') (calling its __init__(self, *args, **kwargs)), the path will be reversed:

  1. __init__() a QtWidget.QPushButton with the new instance as first argument and the following "test" argument parameter;
  2. QPushButton calls the inherited QAbstractButton class __init__(self, text), which gets the first argument after the instance as the button text;
  3. QAbstractButton will call "some" of its methods or those of QWidget for size hints, font managing, etc.;
  4. and so on...

In the same fashion, whenever yourButton.resizeEvent is called, the path will be reversed:

  1. look for QtWidget.QPushButton.resizeEvent
  2. look for QtWidget.QAbstractButton.resizeEvent
  3. ...

SIP (the tool created to generate python bindings for Qt) uses caching for virtuals, and as soon as a virtual [inherited] function is found, that function will always be called in the future if another Qt function requires it. This means that, as soon as no python override is found and the method lookup is successful, that method will always be used from that moment on, unless explicitly called (using "self.resizeEvent()" within your python code).

After calling show(), a QEvent.Resize is received by QWidget.event() (which is a virtual itself); if event() is not overwritten, the base class implementation is called, which will look up for the class resizeEvent function and call it with the event as argument.
Since, at that point, you've not overwritten it yet, PyQt will fallback to the default widget resizeEvent function, and from that point on it will always use that (according to the list above and QPushButton implementation, it will be the basic QWidget.resizeEvent call).

In your second example, show() is called after overwriting the resizeEvent function, allowing the event() function to "find" it (thus ignoring its base implementation along with those defined in the inherited classes) and will use yours until the program exits.
Also, in this case the method can be overwritten again, since SIP/Qt will not use caching anymore. This is a subtle but still very important difference to keep in mind: from this moment you can override that instance (note the bold characters) method as much as you want, as soon as you're certain it's not been called before!

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

As a rule of thumb, everything you see in the official Qt documentation that is labeled as virtual/protected generally requires a subclass to correctly override it. You can see that "[virtual protected]" text on the right of the resizeEvent() definition, and the function is also in the Protected Function list.

PS: with PyQt, some level of monkey patching works with classes too (meaning that you can overwrite class methods that will be automatically inherited by their subclasses), but that's not guaranteed and their behavior is often unexpected, especially due to the cross platform nature of Qt. It mainly depends on the class, its internal C++ behavior and inheritance(s), and how SIP relates to the original C++ objects.

Upvotes: 7

Eliakin Costa
Eliakin Costa

Reputation: 960

Phil Thompson, the creator of PyQt talked about this in a thread.

PyQt caches lookups for Python reimplementations of methods. Monkey patching will only work if you patch a method before it is looked up for the first time. Your example will work for QWidget if you move the call to show() after you patch it.

I think the best solution is to implement your own QPushButton subclass or assign it before show method as your code #2.

Upvotes: 2

Related Questions