My application exits only by right-clicking the tray icon, and pressing "Quit":
class DialogUIAg(QDialog):
...
self.quitAction = QAction("&Quit", self, triggered=qApp.quit)
The Module below is the application's starting point:
#!/usr/bin/env python
import imgAg_rc
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import appLogger
from runUIAg import *
class Klose:
""" Not sure if i need a Class for it to work"""
def closingStuff(self):
print("bye")
#pyqtSlot()
def noClassMethod():
print("bye-bye")
app = QApplication(sys.argv)
QApplication.setQuitOnLastWindowClosed(False)
k = Klose()
app.connect(app, SIGNAL("aboutToQuit()"), k,SLOT("closingStuff()")) #ERROR
app.connect(app, SIGNAL("aboutToQuit()"), k.closingStuff) # Old-Style
app.connect(app, SIGNAL("aboutToQuit()"), noClassMethod) # Old-Style
app.aboutToQuit.connect(k.closingStuff) # New-Style
app.aboutToQuit.connect(noClassMethod) # New-Style
winUIAg = DialogUIAg()
winUIAg.show()
app.exec_()
My intention is to execute a block of code, when the application is aboutToQuit.
This is the Error i am getting:
$ ./rsAg.py
Traceback (most recent call last):
File "./rsAgent.py", line 20, in <module>
app.connect(app, SIGNAL("aboutToQuit()"), k,SLOT("closingStuff()"))
TypeError: arguments did not match any overloaded call:
QObject.connect(QObject, SIGNAL(), QObject, SLOT(), Qt.ConnectionType=Qt.AutoConnection): argument 3 has unexpected type 'Klose'
QObject.connect(QObject, SIGNAL(), callable, Qt.ConnectionType=Qt.AutoConnection): argument 3 has unexpected type 'Klose'
QObject.connect(QObject, SIGNAL(), SLOT(), Qt.ConnectionType=Qt.AutoConnection): argument 3 has unexpected type 'Klose'
I am new to python and Qt and i would appreciate your help.
EDIT:
I forgot to mention versions (python: 3.2, pyQt: 4.8.4)
We dont need a class to define a Slot. Any method can be a Slot, by using the #pyqtSlot() decorator.
I added noClassMethod() in the code.
#Mat , your suggestion helped me go further. Now i found 3 other ways of doing it. I guess its about old-style vs new-style.
I will not delete the Error message, for possible future readers.
Thanks to everyone :-)
The PyQt signal/slot syntax isn't entirely identical to the C++ one.
Try with:
class Klose:
def closingStuff(self):
print("bye")
...
app.connect(app, SIGNAL("aboutToQuit()"), k.closingStuff)
Not sure it is necessary in PyQt, but signals and slots are generally expected to come from/go to QObjects. New-style signals and slots could be of interest if your version of PyQt is recent enough.
In PyQt5, new-style signal: app.aboutToQuit.connect(...)
def app_aboutToQuit():
print('app_aboutToQuit()')
app = QtWidgets.QApplication(sys.argv)
app.aboutToQuit.connect(app_aboutToQuit)
Related
I am writing a package to send/receive frames over a CAN bus monitored by a GUI, using pyqt5.
Here is the process :
Create A new QThread object (called tx_thread)separate from main window thread.
Create a TxWorker (class inheriting from QObject), called tx_worker.
Move it to tx_thread
Setup signal/slot connexion
Start tx_thread
Create a TxJob object (class inheriting from QObject)
Move it to tx_thread
Pass it to tx_worker through signal/slot connexion, so that tx_worker handles the TxJob by itself afterwards.
I have used the above process many times for other cases and it usually works perfectly.
However, in this case I get an error : it seems that the TxJob object "loses" its type during the call through signal/slot and is reverted to a mere QObject without the additional TxJob attributes. That is very weird to me because I specified the type of signal parameter and slot signature.
Does anyone understand what the issue is and what I should do to fix it ?
-- EDIT --
I have found a workaround to avoid the error : I send constructor parameters instead of the object in the signal emit method, then I create the TxJob object from within the receiving thread.
Nevertheless, I want to understand why my first approach did not work, for the sake of my knowledge in python and/or Qt. I would be glad to get any leads on this.
--
I condensed my code into the following files to reproduce the issue.
When executing test_main.py, I get :
Before emitting signal, type is : <class 'test_tx_job.TxJob'>
After entering slot, type is : <class 'PyQt5.QtCore.QObject'>
'QObject' object has no attribute 'set_active'
The error arises when executing job.set_active(True) in method add_tx_job() of class TxWorker
test_main.py
import sys
from test_iocan import IoCan
from PyQt5.QtWidgets import QApplication
def main():
app = QApplication(sys.argv)
io_can = IoCan()
io_can.new_tx_task()
io_can.show()
sys.exit(app.exec_()) # Start event loop
if __name__ == '__main__':
main()
test_iocan.py
from test_tx_job import TxJob
from test_tx_worker import TxWorker
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QMainWindow
class IoCan(QMainWindow):
sig_add_tx_job = pyqtSignal(TxJob)
def __init__(self):
super(IoCan, self).__init__()
self.tx_thread = QThread(parent=self) # Step 0
self.tx_worker = TxWorker() # Step 1
self.tx_worker.moveToThread(self.tx_thread) # Step 2
self.sig_add_tx_job.connect(self.tx_worker.add_tx_job) # Step 3
self.tx_thread.start(priority=QThread.TimeCriticalPriority) # Step 4
def closeEvent(self, event):
"""Override of the close event default method."""
# Needed to cleanly terminate threads
self.threads_stop()
super(IoCan, self).closeEvent(event)
def new_tx_task(self):
new_job = TxJob() # Step 5
new_job.moveToThread(self.tx_thread) # Step 6
print('Before emitting signal, type is : ' + str(type(new_job)))
self.sig_add_tx_job.emit(new_job) # Step 7
def threads_stop(self):
self.tx_thread.quit()
self.tx_thread.wait()
test_tx_worker.py
from test_tx_job import TxJob
from PyQt5.QtCore import QObject, pyqtSlot
class TxWorker(QObject):
def __init__(self):
super(TxWorker, self).__init__()
self.tx_jobs = []
#pyqtSlot(TxJob)
def add_tx_job(self, job: TxJob): # Called at step 7 in tx_thread
print('After entering slot, type is : ' + str(type(job)))
try:
job.set_active(True)
except AttributeError as err:
print(err.args[0])
else:
print(type(job))
self.tx_jobs.append(job)
test_tx_job.py
from PyQt5.QtCore import QObject
class TxJob(QObject):
def __init__(self):
super(TxJob, self).__init__()
self._is_active = False
def set_active(self, value: bool):
self._is_active = value
My PyQt application no longer prints the error (stderr?) to the console.
I use QtDesigner and import the UI like this:
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
from PyQt5.uic import loadUiType
Ui_MainWindow, QMainWindow = loadUiType("test.ui")
class Main(QMainWindow, Ui_MainWindow):
"""Main window"""
def __init__(self,parent=None):
super(Main, self).__init__(parent)
self.setupUi(self)
self.pushButton.clicked.connect(self.testfunc)
def testfunc(self):
print(9/0)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
test.ui contains a QPushButton and a label. When I call testfunc (which obviously gives an error) in a non-Qt application, I get the error message, traceback, etc. When I execute this code, it just exits.
I wrote a PyQt application without QtDesigner before and it printed the errors to the console as expected. What's the difference with QtDesigner and inheritance?
This is probably due to changes in the way exceptions are dealt with in PyQt-5.5. To quote from the PyQt5 Docs:
In PyQt v5.5 an unhandled Python exception will result in a call to
Qt’s qFatal() function. By default this will call abort() and the
application will terminate. Note that an application installed
exception hook will still take precedence.
When I run your example in a normal console, this is what I see:
$ python test.py
Traceback (most recent call last):
File "test.py", line 213, in testfunc
print(9/0)
ZeroDivisionError: division by zero
Aborted (core dumped)
So the main difference is that the application will now immediately abort when encountering an unhandled exception (i.e. just like a normal python script would). Of course, you can still control this behaviour by using a try/except block or globally by overriding sys.excepthook.
If you're not seeing any traceback, this may be due to an issue with the Python IDE you're using to run your application.
PS:
As a bare minimum, the old PyQt4 behaviour of simply printing the traceback to stdout/stderr can be restored like this:
def except_hook(cls, exception, traceback):
sys.__excepthook__(cls, exception, traceback)
if __name__ == "__main__":
import sys
sys.excepthook = except_hook
I've been using python's traceback module in conjunction with a try/except statement to make sure the traceback is printed before exiting:
https://docs.python.org/3/library/traceback.html
Spefically, I use traceback.print_exc()
The following minimal example crashes in pyqt 5.7.1 on windows (copy-paste this in a .py file and run):
from PyQt5.QtWidgets import QListWidgetItem, QListWidget, QApplication
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal
class MyListItem(QListWidgetItem):
def __init__(self, obj):
QListWidgetItem.__init__(self, 'example')
obj.sig_name_changed.connect(self.__on_list_item_name_changed)
def __on_list_item_name_changed(self, new_name: str):
self.setText(new_name)
class MyListItem2(QListWidgetItem, QObject):
def __init__(self, obj):
QListWidgetItem.__init__(self, 'example')
QObject.__init__(self)
obj.sig_name_changed.connect(self.pyqt_slot)
#pyqtSlot(str)
def __on_list_item_name_changed(self, new_name: str):
self.setText(new_name)
class Data(QObject):
sig_name_changed = pyqtSignal(str)
class SearchPanel(QListWidget):
def __init__(self, parent=None):
QListWidget.__init__(self, parent)
obj = Data()
hit_item = MyListItem(obj) # OK
hit_item = MyListItem2(obj) # crashes
self.addItem(hit_item)
obj.sig_name_changed.emit('new_example')
app = QApplication([])
search = SearchPanel()
search.show()
app.exec()
Now just comment out the line that says "crashes", and it works fine. Moreover, the list widget shows 'new_example', showing that the signal went through.
Is there a way to make it work with MyListItem2? i.e. I want to be able to decorate the slot with pyqtSlot, which in turn requires (in PyQt 5.7) that I derive item from QObject.
The intent here is that each item in the list has several characteristics that can change (icon, font, text color) based on signals from associated Data instance (each instance actually "lives", in the Qt sense of the term, in a second thread of our application).
This has got nothing to do with pyqtSlot.
The actual problem is that you are trying to inherit from two Qt classes, and that is not generally supported. The only exceptions to this are Qt classes that implement interfaces, and Qt classes that share a common base-class (e.g. QListWidget and QWidget). However, only the former is offically supported, and there are several provisos regarding the latter (none of which are relevant here).
So a Python class that inherits from both QListWidgetItem and QObject just will not work. The main problem occurs when PyQt tries to access attributes that are not defined by the top-level base-class (even when the attribute does not exist). In earlier PyQt versions, this would simply raise an error:
>>> class MyListItem2(QListWidgetItem, QObject): pass
...
>>> x = MyListItem2()
>>> x.objectName()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: could not convert 'MyListItem2' to 'QObject'
>>> x.foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: could not convert 'MyListItem2' to 'QObject'
which makes it clear that (in C++ terms) a MyListItem2(QListWidgetItem) cannot be cast to a QObject. Unfortunately, it seems that more recent versions of PyQt5 no longer raise this error, and instead just immediately dump core (which presumably is a bug).
If you really need to use pyqtSlot, one suggestion would be to use composition rather than subclassing. So perhaps something like this:
class ListItemProxy(QObject):
def __init__(self, item, obj):
QObject.__init__(self)
self._item = item
obj.sig_name_changed.connect(self.__on_list_item_name_changed)
#pyqtSlot(str)
def __on_list_item_name_changed(self, new_name: str):
self._item.setText(new_name)
class MyListItem2(QListWidgetItem):
def __init__(self, obj):
QListWidgetItem.__init__(self, 'example')
self._proxy = ListItemProxy(self, obj)
I'm working on a pyqt-gui, which should have the possibility to refresh data loading every chosen time (for example every 2 minutes). During this loop, the gui should be able to respond to events etc.
The code looks like this:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.uic import *
class TeleModul (QMainWindow):
def __init__(self, *args):
QWidget.__init__(self, *args)
loadUi ("telemodul.ui",self)
.....
def on_ButtonSuchen_clicked (self):
QTimer.singleShot(60000, self.RefreshData())
def RefreshData(self):
...do something ...
Clicking on ButtonSuchen evokes an error:
TypeError: arguments did not match any overloaded call:
QTimer.singleShot(int, QObject, SLOT()): argument 2 has unexpected
type 'NoneType' QTimer.singleShot(int, callable): argument 2 has
unexpected type 'NoneType'
What's the fault or what's the best way to integrate this loop?
Pass the callable, not the result of calling it:
QTimer.singleShot(60000, self.RefreshData)
I have an object that should signal that a value has changed by emitting a signal with the new value as an argument. The type of the value can change, and so I'm unsure of how to write the signal type. I know that I can acconmplish this using old-style signals like this:
self.emit(SIGNAL("valueChanged(PyQt_PyObject)"), newvalue)
but how would I write this using new-style signals?
I am aware of a previous question related to this but no "real" answer was given there.
First, the object you're emitting from needs the signal defined as an attribute of its class:
class SomeClass(QObject):
valueChanged = pyqtSignal(object)
Notice the signal has one argument of type object, which should allow anything to pass through. Then, you should be able to emit the signal from within the class using an argument of any data type:
self.valueChanged.emit(anyObject)
I'm a beginner and this is the first question I'm attempting to answer, so apologies if I have misunderstood the question...
The following code emits a signal that sends a custom Python object, and the slot uses that
class to print "Hello World".
import sys
from PyQt4.QtCore import pyqtSignal, QObject
class NativePythonObject(object):
def __init__(self, message):
self.message = message
def printMessage(self):
print(self.message)
sys.exit()
class SignalEmitter(QObject):
theSignal = pyqtSignal(NativePythonObject)
def __init__(self, toBeSent, parent=None):
super(SignalEmitter, self).__init__(parent)
self.toBeSent = toBeSent
def emitSignal(self):
self.theSignal.emit(toBeSent)
class ClassWithSlot(object):
def __init__(self, signalEmitter):
self.signalEmitter = signalEmitter
self.signalEmitter.theSignal.connect(self.theSlot)
def theSlot(self, ourNativePythonType):
ourNativePythonType.printMessage()
if __name__ == "__main__":
toBeSent = NativePythonObject("Hello World")
signalEmitter = SignalEmitter(toBeSent)
classWithSlot = ClassWithSlot(signalEmitter)
signalEmitter.emitSignal()