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.
Related
# 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.
# 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.
I have a dialog class that is inheriting a pyside-uic-generated python class but my problem is that it cannot be extended my adding another base class.
import sys
from PySide import QtGui
from mi_ui import Ui_Dialog
class Worker(object):
def __init__(self):
super(Worker, self).__init__()
self.data = 1
class MainDialog(QtGui.QDialog, Ui_Dialog, Worker):
def __init__(self):
super(MainDialog, self).__init__()
self.setupUi(self)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dlg = MainDialog()
print dlg.data
dlg.show()
sys.exit(app.exec_())
When I try to extend MainDialog with Worker, super does not call the Worker's __init__ and the print dlg.data fails because "AttributeError: 'MainDialog' object has no attribute 'data'"
My only work around seems to ignore super and invoke each __init__ manually.
QtGui.QDialog.__init__(self)
Worker.__init__(self)
Is this my only solution?
This is for Python 2.7.
Multiple inheritance is tricky in python. The classes that you are inheriting from cannot have any conflicts if you want it to work perfectly. In most cases multiple inheritance with pyside does cause a conflict because everything inherits QObject giving identical variables and methods. Python doesn't know which one to inherit. The painting is another area for conflict. The other thing to consider is the order of the inheritance. I believe that python tries inheritance and init from left to right. So if you only want the init method for the QtGui.QDialog and the Worker (Ui_Dialog will probably conflict) then you might want to try.
class MainDialog(QtGui.QDialog, Worker, Ui_Dialog):
In python 3 you can call the super method a little differently.
class MainDialog(QtGui.QDialog, Worker, Ui_Dialog):
super().__init__()
I believe that the way you are calling the init for 2.7 is the correct way of doing things. Questions similar to this are all over the place. It is a common problem known as the Diamond problem. Python super method and calling alternatives may explain things a little more.
Make Worker the first base-class:
class MainDialog(Worker, QtGui.QDialog, Ui_Dialog)
This will still result in MainDialog.__init__ being called first, then Worker.__init__ (which can see if you add some print statements). But you will still be able to access the data attribute from inside the MainDialog.__init__.
The Ui_Dialog class doesn't really figure in any of this, because it is just an ordinary python class inheriting from object and doesn't ever call super. So it can go anywhere you like in the base-class order.
Obviously, if you do things this way, you will have to take care not to clobber any methods from the other base-classes in the Worker class - but you already had this "problem" anyway (just in a different order).
I'm using pyqt4.
I have a class multi inherited from QObject and QRunnable like this:
class DownloadTask(QObject, QRunnable):
def __init__(self):
QObject.__init__(self)
QRunnable.__init__(self)
self.setAutoDelete(False)
When an instance of DownloadTask is initializing, the last line throws exception:
TypeError: could not convert 'DownloadTask' to 'QRunnable'
But I think it is correct in grammer, QRunnable has the method setAutoDelete. Why can't it convert to QRunnable?
Update:
I intend to use QThreadPool to manage multi threads downloading resources from Internet, and emit a signal after finished. How can I do that?
PyQt reference guide > Things to be Aware Of > Multiple inheritance:
It is not possible to define a new Python class that sub-classes from more than one Qt class.
I am using PySide (Python Qt binding).
I have a worker thread of class QThread that updates the main GUI thread (updates the QTableWidget) through signal/slot mechanism.
In my worker thread, I have the following:
self.emit(SIGNAL("alterTable(object"), params)
In my GUI thread I have this:
self.connect(self.worker, SIGNAL("alterTable(object)"), self.updateMainTable, Qt.AutoConnection)
Since there are several similar worker threads running all connecting to the same slot (the self.updateMainTable), I should use the AutoConnection (and consequently the QueuedConnection). Using the Qt.DirectConnection works, but it is not safe (or so I have been told).
But when I try to use the AutoConnection, I get the following error:
QObject::connect: Cannot queue arguments of type 'object'
(Make sure 'object' is registered using qRegisterMetaType().)
I have Googled for eons trying to figure out a way how to use the qRegisterMetaType() in PySide, to no avail. All the resources I found online point to a C++ syntax/documentation.
If it makes any difference, the object in question is a dict most of the time.
I guess I have found an answer myself, well not exactly an answer, but a workable solution.
I switched all the Signals to the new-style syntax. In case anyone is wondering, I did that by defining a custom signal in my worker class. So my code looks something like this
class Worker(QThread):
alterTable = Signal(dict)
def __init__(self, parent=None):
....
self.alterTable.emit(parameters)
class GUI(QMainWindow):
def __init__(self, parent=None):
WorkerModule.Worker().alterTable.connect(self.myMethod)
For some reason the Signal has to be inside the QThread class; otherwise, Qt complains about "Signal has no attribute connect" error, which is very weird.
Incredibly late for the answer here, my apologies. Not enough reputation to add a comment to your accepted answer. I hope this might help new PySide/Pyside2 users who stumble upon your issue.
Issue: QObject::connect: Cannot queue arguments of type 'object'
Solution: self.connect(self.worker, SIGNAL("alterTable(PyObject)"), self.updateMainTable, Qt.AutoConnection)
Issue: Qt complains about "Signal has no attribute connect" error
Solution: The connect attribute is implemented in QObject, so you must first call the parent's init method through either QMainWindow.__init__(self) or super(GUI, self).__init__() (Py2) or also super().__init__() (Py3).
Cheers.