PyQt signal between modules - python

I can not understand one's probably a very simple thing. How from one module (Main.py) make changes (call functions ) in other module (module2.py), which is connected as QDockWidget to MainWindow, and see these changes immediately?

You have to declare a signal in the class then connect the signal to a function.
class MyClass(QtCore.QObject): # Could be QWidget must be inherited from QObject
mysignal = QtCore.pyqtSignal(int, int, int, int) # types to pass values to the method call
...
myclass = MyClass()
other = QtGui.QMainWindow()
myclass.mysignal.connect(other.setGeometry)
myclass.mysignal.emit(0, 0, 1, 1)
http://pyqt.sourceforge.net/Docs/PyQt4/new_style_signals_slots.html

Related

'ERR: QApplication before a QWidget' happened while use QWidget as Class member

When I try to define two QWidget var as class members, an error information is reported as the following,
"QWidget: Must construct a QApplication before a QWidget"
To define QWidget obj as class members is for the reason that these two 'QWidget' obj need share data with each other, if I use the way like 'self.compUI1', they can not be accessed in class member functions.
Here is something (similar) what I planned to do,
`
class TabUISheet(QTabWidget):
#define UI component as member
compUI1 = PicUI()
compUI2 = TxtUI()
def
...
def fun1():
#here I need to get txt data
def fun2():
#here I need to update txt data to compUI1 (QWiget based)
`
Does that mean QWidgets can not be defined as class member?
Actually, 'TabUISheet' is called after calling 'QApplication()'
`
def main():
app = QApplication(sys.argv)
...
TabUISheet()
`
I have tried use self.compUI1, but failed because as I mentioned before this can be accessed by 'instance' rather than 'Class' or class member.
Not sure, if there is other way to realize that to share data between two QWidget based UI components.

Declaration of the custom Signals

In Qt, we can create custom signals by making them static variables. and then we use self.signame instead classname.signame.
So that creates an instance variable in the class.
I wanted to know the theory beyond this pattern.
here's some pseudo-code that I have tried that was recorded in most of the sources:
from PyQt5 import QtWidgets,QtCore
class test(QtWidgets.QApplication):
sig=QtCore.pyqtSignal([bool])
def __init__(self):
super().__init__([])
# self.sig=QtCore.pyqtSignal([bool]) #1
self.window=QtWidgets.QWidget()
self.sig.connect(lambda x=True:print('Hello World'))
self.bt=QtWidgets.QPushButton(self.window,text='test',clicked=self.sig)
self.window.setLayout(QtWidgets.QVBoxLayout())
self.window.layout().addWidget(self.bt)
self.window.show()
test().exec()
When i tred to access the signal test.sig instead of self.sig ,
from PyQt5 import QtWidgets,QtCore
class test(QtWidgets.QApplication):
def __init__(self):
super().__init__([])
self.sig=QtCore.pyqtSignal([bool])
self.window=QtWidgets.QWidget()
self.sig.connect(lambda x=True:print('Hello World'))
self.bt=QtWidgets.QPushButton(self.window,text='test',clicked=self.sig)
self.window.setLayout(QtWidgets.QVBoxLayout())
self.window.layout().addWidget(self.bt)
self.window.show()
test().exec()
i get this error:
test.sig.connect(lambda x=True:print('Hello World'))
AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect'
and when I tried to make the signal as an instance variable instead of the static variable I get that error (uncommenting the #1 in psudeo code)
I get the same error (as before).
May I know the reason behind this
Source: PyQt5
It should be noted that pyqtSignal is declared as an attribute of the class but it is not the same object that is used in the connection as indicated in the docs:
A signal (specifically an unbound signal) is a class attribute. When a
signal is referenced as an attribute of an instance of the class then
PyQt5 automatically binds the instance to the signal in order to
create a bound signal. This is the same mechanism that Python itself
uses to create bound methods from class functions.
A bound signal has connect(), disconnect() and emit() methods that
implement the associated functionality. It also has a signal attribute
that is the signature of the signal that would be returned by Qt’s
SIGNAL() macro.
In other words, sig = QtCore.pyqtSignal([bool]) is an unbound signal but self.sig is the bound signal and that can be verified with the following lines:
from PyQt5 import QtWidgets, QtCore
class test(QtWidgets.QApplication):
sig = QtCore.pyqtSignal([bool])
print(type(sig))
def __init__(self):
super().__init__([])
self.window = QtWidgets.QWidget()
print(type(self.sig))
self.sig.connect(lambda x=True: print("Hello World"))
self.bt = QtWidgets.QPushButton(self.window, text="test", clicked=self.sig)
self.window.setLayout(QtWidgets.QVBoxLayout())
self.window.layout().addWidget(self.bt)
self.window.show()
test().exec()
Output
<class 'PyQt5.QtCore.pyqtSignal'>
<class 'PyQt5.QtCore.pyqtBoundSignal'>
In conclusion, the attribute of the class "sig" is a pyqtSignal that does not have the connect method and that is used to construct the attribute "self.x" which is a pyqtBoundSignal that does have the connect method.

Using custom object (PyQt_PyObject like) in signal/slots

I manage to use PySide instead of PyQt because of licensing.
I need to pass custom objets between threads using the signal/slots mechanism. With PyQt, I can use the PyQt_PyObject type as signal argument but obviously, this type doesn't exists in PySide :
TypeError: Unknown type used to call meta function (that may be a signal): PyQt_PyObject
I tried to use object instead of PyQt_PyObject but things happen only with a DirectConnection type between signal and slot :
self.connect(dummyEmitter,
QtCore.SIGNAL("logMsgPlain(object)"),
self._logMsgPlain,
QtCore.Qt.DirectConnection)
With a QueuedConnection, I get an error :
QObject::connect: Cannot queue arguments of type 'object'
(Make sure 'object' is registered using qRegisterMetaType().)
I say "things happen" because it doesn't work so far. I now get errors due to the DirectConnection type :
QObject::startTimer: timers cannot be started from another thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
etc ...
How should I do ?
Is there a PyQt_PyObject type-like in PySide ?
EDIT:
This small exemple will fail :
from PySide import QtCore, QtGui
import sys
class Object(QtCore.QObject):
''' A dummy emitter that send a list to the thread '''
def emitSignal(self):
someList = [0, 1, 2, 3]
self.emit(QtCore.SIGNAL("aSignal(object)"), someList)
class Worker(QtCore.QObject):
def aSlot(self, value):
print "List: {}".format(value)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
worker = Worker()
obj = Object()
thread = QtCore.QThread()
worker.moveToThread(thread)
QtCore.QObject.connect(obj, QtCore.SIGNAL("aSignal(object)"), worker.aSlot)
# The exemple will pass with the line below uncommented
# But obviously, I can't use a DirectConnection with a worker thread and the GUI thread
# QtCore.QObject.connect(obj, QtCore.SIGNAL("aSignal(object)"), worker.aSlot, QtCore.Qt.DirectConnection)
thread.start()
obj.emitSignal()
app.exec_()
For now, the only solution I found is to switch to new style signal/slot syntax :
from PySide import QtCore, QtGui
import sys
class Object(QtCore.QObject):
aSignal = QtCore.Signal(object)
def emitSignal(self):
someList = [0, 1, 2, 3]
self.aSignal.emit(someList)
class Worker(QtCore.QObject):
def aSlot(self, value):
print "List: {}".format(value)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
worker = Worker()
obj = Object()
thread = QtCore.QThread()
worker.moveToThread(thread)
obj.aSignal.connect(worker.aSlot)
thread.start()
obj.emitSignal()
app.exec_()
But I would be interested to know if there is a solution with the old-style syntax but for now, it seems that there is not.

Python lambda functions connected to Qt signals don't run when created in a different thread

When a QObject is created in a different thread and moved back to the main thread using QObject.moveToThread, lambda signals "disconnect" (they won't fire). My guess is that regular slots are linked to the QObject that is moved to the main thread, so they run in the main thread's event loop, but lambda functions aren't linked to the QObject, so there is no event loop for them to run.
This can be seen in the following short Python code:
if __name__ == "__main__":
from traits.etsconfig.api import ETSConfig
ETSConfig.toolkit = 'qt4'
import threading
from PySide import QtGui, QtCore
class MyObject(QtCore.QObject):
def __init__(self, button):
super(MyObject, self).__init__()
button.clicked.connect(self.mySlot)
button.clicked.connect(lambda: self.mySlot('lambda'))
#
def mySlot(self, printing='object'):
print printing
#
myObj = None
# global variable to keep it in memory
def myThread(mainThread, button):
global myObj
myObj = MyObject(button)
myObj.moveToThread(mainThread)
#
if __name__ == '__main__':
appQT = QtGui.QApplication([])
#
myWidget = QtGui.QWidget()
myButton = QtGui.QPushButton('Press to see output')
myLayout = QtGui.QVBoxLayout(myWidget)
myLayout.addWidget(myButton)
myWidget.show()
#
mainThread = QtCore.QThread.currentThread()
if True:
# run myThread in a new thread
# prints only the first slot (object slot)
threading.Thread(target=myThread, args=[mainThread, myButton]).start()
else:
# run myThread in this thread
# prints both slots (object and lambda slots)
myThread(mainThread, myButton)
#
appQT.exec_()
You can see how the results differ from expected by changing the conditional from True to False.
When it is set to True, the output after clicking the button is:
object
When it is set to False, the output after clicking the button is:
object
lambda
My question is, can someone explain more precisely why it behaves in this way, and is there an easy way to keep the lambda slots working when moving the QObject back to the main thread?
Okay, I figured out some details about what is going on. It's still a partial answer, but I think it's more suitable as an answer rather than update to the question.
It seems that I was correct in my original question that the slots are linked to their instance object's QObject event loop (thread), but only if that slot is a bound method (has an object instance).
If you look into the PySide source code on Github, you'll see that it defines the receiver (the QObject that receives the signal) based on the type of slot it receives. So if you pass the QtSignal.connect() function a bound object method, the receiver is defined as the slotMethod.__self__ (which is PyMethod_GET_SELF(callback)). If you pass it a general callable object (for example a lambda function) which is not bound (no __self__ property), the receiver is simply set to NULL. The receiver tells Qt which event loop to send the signal to, so if it's NULL, it doesn't know to send the signal to the main event loop.
Here's a snippet of the PySide source code:
static bool getReceiver(QObject *source, const char* signal, PyObject* callback, QObject** receiver, PyObject** self, QByteArray* callbackSig)
{
bool forceGlobalReceiver = false;
if (PyMethod_Check(callback)) {
*self = PyMethod_GET_SELF(callback);
if (%CHECKTYPE[QObject*](*self))
*receiver = %CONVERTTOCPP[QObject*](*self);
forceGlobalReceiver = isDecorator(callback, *self);
} else if (PyCFunction_Check(callback)) {
*self = PyCFunction_GET_SELF(callback);
if (*self && %CHECKTYPE[QObject*](*self))
*receiver = %CONVERTTOCPP[QObject*](*self);
} else if (PyCallable_Check(callback)) {
// Ok, just a callable object
*receiver = 0;
*self = 0;
}
...
...
}
Does this help us fix our problem with the lambda functions? Not really... If we bind the lambda functions using the following (with types.MethodType), the behavior does not change:
import types
class MyObject(QtCore.QObject):
def __init__(self, button):
super(MyObject, self).__init__()
button.clicked.connect(self.mySlot)
thisLambda = lambda self=self : self.mySlot('hello')
self.myLambda = types.MethodType( thisLambda, self )
button.clicked.connect(self.myLambda)
#
def mySlot(self, printing='object'):
print printing
Output:
object
This binding is definitely part of the problem since I have demonstrated below that the same behavior occurs with non-bound global methods, and by binding them using types.MethodType(), it fixes the problem:
import types
def abc(self):
print 'global1'
def xyz(self):
print 'global2'
class MyObject(QtCore.QObject):
def __init__(self, button):
super(MyObject, self).__init__()
button.clicked.connect(self.mySlot)
self.xyz = types.MethodType( xyz, self )
button.clicked.connect(abc)
button.clicked.connect(self.xyz)
#
def mySlot(self, printing='object'):
print printing
Output:
object
global2
Anyways, it seems the simplest solution is just to not create the QObject in a separate thread in the first place, but this answer is a step towards understanding why it doesn't work properly.

Altering PySide.QtGui.QListWidget with an emitted signal from a multiprocessing.Pool async call results in Runtime Error?

I have:
from PySide.QtCore import Signal, QObject
from multiprocessing import Pool
def some_computation():
pass
# ..some computations
return 'result'
class MyClass(QObject):
my_signal = Signal()
def __init__(self):
self.mylistwidget = # ... QListWidget from somewhere
# bind the signal to my slot
self.my_signal.connect(self.on_my_signal)
# this is called after computation thread is finished
def my_callback_fct(result):
# ..bla bla
self.my_signal.emit()
# this is the function I call
def do_some_async_computation(self)
pool = Pool(processes=2)
pool.apply_async(target=some_computation, callback=my_callback_fct)
# this is the slot
def on_my_signal(self):
self.mylistwidget.clear()
I read around stackoverflow that in order to change the gui from a secondary execution thread one must use slot-signal mechanism, which is what I did in MyClass, although when I call do_some_async_computation I would expect the pool to initiate a secondary thread for some_computation function ..which happens, after the computation is finished the my_callback_fct is executed accordingly, which emits a my_signal signal that is connected to the on_my_signal slot, which is executed as expected, but when altering the self.mylistwidget it gives a Runtime Error / QWidget runtime error redundant repaint detected
I haven't observed your actual error, but in a similar scenario we use a QueuedConnection to ensure the signal is passed correctly from one thread to the other. This is done automagically for some people if the objects in question belong to different threads (QObject have a notion of the QThread that owns them). But in your case, all is done on one object, so Qt can't know. Do
from PyQt5.QtCore import Qt
...
self.my_signal.connect(self.on_my_signal, Qt.QueuedConnection)
I solved this by using a QtCore.QThread instead of multiprocessing.Pool. I was thinking about the mechanism you talked about #deets and I said to myself that it should be in in the same context in order for Qt.QueuedConnection to work, so that's why I wanted to go with QThread.
class MyComputationalThread(PySide.QtCore.QThread):
data_available = PySide.QtCore.Signal(str)
def run(self):
result = # ... do computations
serialized_result = json.dumps(result) # actually I use JSONEncoder here
self.data_available.emit(serialized_result)
... and in my MyClass:
class MyClass(QObject):
# ...
mythread = MyComputationalThread()
# ...
def __init__(self):
# ...
self.mythread.connect(do_something_with_serialized_data, PySide.QtCore.Qt.QueuedConnection)
# ...
It works like a charm.

Categories

Resources