I need to check the signal for the presence of the listener, before it is emitted.
class Test(QObject):
test = pyqtSignal(str,dict)
def run(self):
if self.receivers(SIGNAL("test(str,dict)"):
self.test.emit('blablabla',{})`
The signal is connected to the slot right and successfully emits signals.
When checking the signature signal, the method QObject.receivers() shows that this signal is not connected.
I understood, reason was incorrect signature, I did not find a method, to specify the faithful signature of signal.
In pyqt5 SIGNAL is deprecated. It is replaced with signal attribute of each QObject
if QObject.receivers(QObject.signal) > 0:
print('signal connected')
To Check QPushButton signal clicked() is connected to any slot
button = QPushButton()
.
.
if button.receivers(button.clicked) > 0:
.....
The signature for your signal is "test(QString, PyQt_PyObject)".
So obviously, str is mapped to QString and other native python object types, dict, list... are mapped to the C++ type PyQt_PyObject.
The list of signal signatures can be obtained through the QMetaObject associated with your object:
test = Test()
metaobject = test.metaObject()
for i in range(metaobject.methodCount()):
print(metaobject.method(i).signature())
Related
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.
I bind 2 keys to call 2 methods of my class. Is it possible to call the some method and knowing which key was pressed?
def initGui(self):
self.keyAction = QAction("Test Plugin", self.iface.mainWindow())
self.iface.registerMainWindowAction(self.keyAction, self.toggle_key_1)
self.iface.addPluginToMenu("&Test plugins", self.keyAction)
QObject.connect(self.keyAction, SIGNAL("triggered()"), self.toogle_layer_1)
self.keyAction = QAction("Test Plugin", self.iface.mainWindow())
self.iface.registerMainWindowAction(self.keyAction, self.toggle_key_2)
self.iface.addPluginToMenu("&Test plugins", self.keyAction)
QObject.connect(self.keyAction, SIGNAL("triggered()"), self.toogle_layer_2)
Yes, you can know which object has triggered the signal from your slot (function) with using QObject::sender() function. As Qt docs say:
Returns a pointer to the object that sent the signal, if called in a
slot activated by a signal; otherwise it returns 0. The pointer is
valid only during the execution of the slot that calls this function
from this object's thread context.
Update:
For example, in your slot you can write:
def toogle_layer(self):
action = QtCore.QObject.sender()
if action == self.action1:
# do something
elif action == self.action2:
# do something else
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.
The sample code is like this:
class Something(gtk.Window):
def __init__(self):
...
treeview = gtk.TreeView(store)
tree_selection = treeview.get_selection()
tree_selection.set_mode(gtk.SELECTION_SINGLE)
tree_selection.connect("changed", self.onSelectionChanged)
...
def onSelectionChanged(self, tree_selection):
(model, pathlist) = tree_selection.get_selected()
self.selectedValue = model.get_value(pathlist, 0) - 1
How was the tree_selection passed in into onSelectionChanged function? I see many other similar uses, such as:
def onDestroy(self, widget):
gtk.main_quit()
What can we use this "widget" in the second param?
When you connect a handler (like onSelectionChanged) to a signal (like "changed"), the handler is added to the end of the list of signal handlers of that object.
When the object then emits the signal, it will call handlers (or "callbacks") in this list, pasing itself as the first argument.
This is true for all signals: the first argument is allways the object emitting the signal.
This way, you can e.g. call the get_selected() method of the TreeSelection object that called your onSelectionChanged method: you access it through the tree_selection parameter.
Using the pyside qt binding module inside a small python 2.7 project I want to find out the source of a signal. So inside a slot I want to have some means to ask by what signal this slot has actually been triggered with.
I figured out that this gives me a clean debug notation of the actual sender object:
sigItem = "<anonymous>" if not self.sender() else \
re.search('<(.+) object at .+>', repr(self.sender()), 0).group(1)
But all I found until now to identify the actual signal that caused this slot to be called is apparently the signals index inside the caller object:
sigIndex = self.senderSignalIndex()
So how can I find out the actual signals name?
You could use the index to get a QMetaMethod, but not much more. Apparently, Qt doesn't want you to know more.
from PyQt4 import QtCore
senderSignalId = None
class Sender(QtCore.QObject):
signal1 = QtCore.pyqtSignal()
signal2 = QtCore.pyqtSignal()
class Receiver(QtCore.QObject):
#QtCore.pyqtSlot()
def slot(self):
global senderSignalId
senderSignalId = self.senderSignalIndex()
sender = Sender()
receiver = Receiver()
sender.signal1.connect(receiver.slot)
sender.signal2.connect(receiver.slot)
sender.signal1.emit()
print sender.metaObject().method(senderSignalId).signature() // signal1()
sender.signal2.emit()
print sender.metaObject().method(senderSignalId).signature() // signal2()