resizeEvent not working after widget has called show() - python

# 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...

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:
[python object]
Qt.QObject
QtWidget.QWidget
QtWidget.QAbstractButton
QtWidget.QPushButton
So, when you create QPushButton('Test') (calling its __init__(self, *args, **kwargs)), the path will be reversed:
__init__() a QtWidget.QPushButton with the new instance as first argument and the following "test" argument parameter;
QPushButton calls the inherited QAbstractButton class __init__(self, text), which gets the first argument after the instance as the button text;
QAbstractButton will call "some" of its methods or those of QWidget for size hints, font managing, etc.;
and so on...
In the same fashion, whenever yourButton.resizeEvent is called, the path will be reversed:
look for QtWidget.QPushButton.resizeEvent
look for QtWidget.QAbstractButton.resizeEvent
...
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.

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.

Related

QOpenGLWidget updates not working when initializeGL and PaintGL are called before showing QApplication [duplicate]

# 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...
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:
[python object]
Qt.QObject
QtWidget.QWidget
QtWidget.QAbstractButton
QtWidget.QPushButton
So, when you create QPushButton('Test') (calling its __init__(self, *args, **kwargs)), the path will be reversed:
__init__() a QtWidget.QPushButton with the new instance as first argument and the following "test" argument parameter;
QPushButton calls the inherited QAbstractButton class __init__(self, text), which gets the first argument after the instance as the button text;
QAbstractButton will call "some" of its methods or those of QWidget for size hints, font managing, etc.;
and so on...
In the same fashion, whenever yourButton.resizeEvent is called, the path will be reversed:
look for QtWidget.QPushButton.resizeEvent
look for QtWidget.QAbstractButton.resizeEvent
...
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.
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.

What are the responsibilities of size_allocate() in a custom container class? How does it relate to adjust_size_allocation()?

I try to create a proper container Class for Gtk in Python (MyBin in the code below). There seems to be no authoritative documentation on how to do that, only bits and pieces, which I glued together with trial and error. – There are many open questions, but let me focus on size_allocate() here:
What is the the original size_allocate() function doing? And what is my overlaid version in my container class supposed to do?
I already do 95% know that I have to replace the original method with my own by prepending “do_” to form the do_size_allocate() method (by trial and error – see code below; I could not find anything talking about all those “do_*” functions).
I found so far, that calling self.set_allocation(allocation) seems to be a good idea as is self.get_child().size_allocate(...) to give the child some working space (see code). – Any other obligations I have to fulfill? What is the original function doing additionally?
Regarding the allocation I pass down to the child: I need to adapt it (not in the code below yet). I successfully(?) used a fresh Gdk.Rectangle() on which I set x, y, width and height, but I somehow feel that it’s wrong and something with (do_)adjust_size_allocation() should be used instead. But how?
Lets look into the documentation of do_size_allocate():
This function is only used by Gtk.Container subclasses, to assign a
size and position to their child widgets. [“Only used”? Only called from? Only implemented at? Only overwritten by? – Well, I’m doing it already, but the doc wasn’t helpful in finding out how.]
In this function, the allocation may be adjusted. [How?] It will be forced to
a 1x1 minimum size [Can confirm only that get_allocation() will return a 1x1 if I don’t set anything.],and the adjust_size_allocation virtual method on
the child will be used to adjust the allocation. [What? For my own allocation? Or for the one I set on the child via ...get_child().size_allocate()? Who calls that adjust method? In which conditions is it not called before size_allocate()?] Standard adjustments
include removing the widget’s margins, and applying the widget’s
Gtk.Widget :halign and Gtk.Widget :valign properties.
#!/usr/bin/python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class MyBin(Gtk.Bin):
def __init__(self):
super().__init__()
def do_size_allocate(self, allocation):
print('PARENT METHOD do_size_allocate', allocation.width)
# if not called, `get_allocation()` in `do_draw()` will return a 1x1 rectangle
self.set_allocation(allocation)
# strangely required so that `do_draw()` will be called even on this class!
self.get_child().size_allocate(allocation)
def do_draw(self, c):
allocation = self.get_allocation()
print('PARENT do_draw()', allocation.width)
self.propagate_draw(self.get_child(), c)
class MyChild(Gtk.Button):
def __init__(self):
super().__init__()
self.connect('size-allocate', self.size_allocate_handler)
self.connect('draw', self.draw_handler)
def size_allocate_handler(self, self2, allocation):
print('CHILD signal size-allocate', allocation.width)
def draw_handler(self, self2, c):
allocation = self.get_allocation()
print('CHILD signal draw', allocation.width)
class MainWindow(Gtk.Window):
def __init__(self):
super().__init__(title='d2see test pattern')
the_child = MyChild()
my_container = MyBin()
my_container.add(the_child)
self.add(my_container)
self.show_all()
if __name__ == '__main__':
MainWindow()
Gtk.main()

How to Use QTimer With a QWidget (if possible)

I need some help figuring out how to create a timer under a widget class. I've got the following class:
class TimerClass(QtGui.QWidget):
def __init__(self, parent = None):
super(TimerClass, self).__init__()
and I'm trying to implement a timer as follows:
def start_timer(self):
timer = QtCore.QTimer(self)
timer.timeout.connect(self.__Time)
timer.start(1000)
and it calls the following:
def __Time(self):
print("Timer End")
This QWidget is called from my MainWindow and I have another timer that works without a problem as shown above with MainWindow but I can't figure out how to get it to work with QWidget. I assume the use of QWidget is the problem because I get the following error when I try and run it:
AttributeError: 'MainWindow' object has no attribute '_TimerClass__Time'
Can anyone tell me what I'm doing wrong or what the proper way of doing this would be?
The only way to get that error from the code you've posted, is if the MainWindow class inherits the TimerClass, and then an instance of MainWindow tries to call self.__Time().
That cannot work, because double-underscored attributes are only directly accessible to the class that defines them. If you rename the method to have only one underscore, the error will go away.
However, using multiple inheritance with QObject subclasses (such as QWidget) is generally a bad idea, and should be avoided. Use delegation instead.

What's the difference between #pyqtSignature() and #pyqtSlot() pyqt decorators

I was trying to write a slot that connects to different pyqt signals. What I still can't wrap my head around is the difference between the two decorators #pyqtSignature() and #pyqtSlot().
Example to connect to pyqt clicked signal of QPushButton which is inherited from QAbstractButton, I used the following syntax on_widgetName_signalName, when using #pyqtSignature(""):
#pyqtSignature("")
def on_bn_gpx_select_file_clicked(self):
"""
Run when QPushButton is pressed, or do something
"""
pass
Now when using #pyqtSlot()
#pyqtSlot()
def on_bn_gpx_select_file_clicked(self):
"""
Run when QPushButton is pressed, or do something
"""
pass
My question is, what is the difference between the two decorators and when should I use #pyqtSignature or #pyqtSlot()
Thanks
The pyqtSignature decorator is part of the old-style signal and slot syntax, which was replaced by the new-style signal and slot syntax in PyQt-4.5.
Both decorators serve the same purpose, which is to explicitly mark a python method as a Qt slot and specify a C++ signature for it (most commonly in order to select a particular overload). The only relevant difference between the two decorators is that pyqtSlot has a much more pythonic API.
There should never be any need to use pyqtSignature in new code - it is only really needed for backwards-compatibility.

Splitting PyQt Code - Passing Main App By Reference Or Import

Am having much trouble splitting PyQt code:
main.py
(PyQt modules)
from titles import *
appl = QApplication(sys.argv)
from main import Ui_MainWindow
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
QMainWindow.__init__(self)
self.u = Ui_MainWindow()
self.u.setupUi(self)
Titles(self)
titles.py
import sys
(PyQt modules)
(dbconnections)
class Titles():
def __init__(self, a): #<-- APP IS PASSED AS ARGUMENT AND NOW CALLED 'A'
a.u.table.setModel(titles)
a.u.lineEdit.setText("Titles Init")
a.u.add.clicked.connect(titles.insertRow)
class TitlesTableModel(QSqlTableModel):
def __init__(self):
QSqlTableModel.__init__(self)
self.setTable("titles")
self.setEditStrategy(self.OnFieldChange)
self.select()
def insertRow(self):
return self.insertRecord(-1, self.record())
a.u.lineEdit.setText("Insert Title")
titles = Titles()
Running main.py loads all data. QPushButton inserts a row, but doesn't set lineEdit to "Insert Title", because "a" isn't defined globally. Mostly tried creating a function in titles.py, triggered when main.py loads, looking like:
a = 0 #<-- THIS WAS A LAST STRAW AS WARNED BY RESEARCHING OTHERS, BUT AM LOST
def start(app):
global a
a = app
Titles(a); TitlesTableModel(a) #<-- EVEN THOUGH TITLES.PY IS IMPORTED, IT DIDN'T INCLUDE THE APP REFERENCE, SO AM TRYING TO 'REFRESH' THE TITLESTABLEMODEL
...with Titles & TitlesTableModel requiring an extra argument (self, a)
This loads data & functions, but again, insertRow doesn't update lineEdit.
Other attempt
change Songs class to
class Songs():
def __init__(self, a):
titles = Titles(a)
...(rest the same)
...and removing titles=Titles() from below the model definition. This again, shows data, but doesn't update lineEdit when pressing 'Add'.
Ultimately, it feels titles.py needs to have 'from main import *', but the main applications instance is defined after titles.py is called, and importing main.Main creates a recursion. Have tried inheriting multiple times via 'from main import Main', & writing 'class Songs(Main)' (so Songs can use the UI without passing a reference), but again, recursion occurs. Nine hours today plus three weeks prior looking at others, so am really stumped. Others somewhat recommended using a config file of even 'builtin', but that looks very bad.
Regards
In PyQt, classes generally use Signals to communicate between one another, especially when one class inherits from QWidget and the other does not inherit from that, as you've demonstrated by connecting signals (albeit wrongly, or at least you're missing bits and pieces of your code here on SO).
However, your insertRow() -> lineEdit method as it stands will never be called because it follows a return statement, meaning that the lineEdit part will never be hit. But I would be surprised if this fixed the problem.
Also, I would consider redesigning (refactoring) your code from the grounds up. Is there really a reason you have a different Titles() class?
While this is shameless self-promotion, I think you might benefit from my course on YouTube that deals with building Python applications using PySide (which is nearly identical to PyQt) - I discuss cross-thread (cross-class) communication a fair bit - link is http://youtube.com/Deusdies2
Your code has several issues, but the main problem is the snippet:
def insertRow(self):
return self.insertRecord(-1, self.record())
a.u.lineEdit.setText("Insert Title")
as you can see you're returning from the function before the line a.u.lineEdit.setText("Insert Title") get excecuted. Hence, this function willl never change the text of your QLineEdit.
Change your code b
def insertRow(self):
a.u.lineEdit.setText("Insert Title") # First change text.
return self.insertRecord(-1, self.record()) # Then insert record and return.
On the other hand: If you are working with global variables (a bad practice, I have to say) why are you passing it as arguments? Try to not use global variables at least is absolutly necesary.

Categories

Resources