Sending custom PyQt signals?

Question:

I’m practicing PyQt and (Q)threads by making a simple Twitter client. I have two Qthreads.

  1. Main/GUI thread.

  2. Twitter fetch thread – fetches data from Twitter every X minutes.

So, every X minutes my Twitter thread downloads a new set of status updates (a Python list). I want to hand this list over to the Main/GUI thread, so that it can update the window with these statuses.

I’m assuming that I should be using the signal / slot system to transfer the “statuses” Python list from the Twitter thread, to the Main/GUI thread. So, my question is twofold:

  1. How do I send the statuses from the Twitter thread?

  2. How do I receive them in the Main/GUI thread?

As far as I can tell, PyQt can by default only send PyQt-objects via signals / slots. I think I’m supposed to somehow register a custom signal which I can then send, but the documentation on this that I’ve found is very unclear to a newbie like me. I have a PyQt book on order, but it won’t arrive in another week, and I don’t want to wait until then. 🙂

I’m using PyQt 4.6-1 on Ubuntu

Update:

This is an excert from the code that doesn’t work. First, I try to “connect” the signal (“newStatuses”, a name I just made up) to the function self.update_tweet_list in the Main/GUI thread:

QtCore.QObject.connect(self.twit_in,
                       QtCore.SIGNAL("newStatuses (statuses)"),
                       self.update_tweet_list)

Then, in the Twitter thread, I do this:

self.emit(SIGNAL("newStatuses (statuses)"), statuses)

When this line is called, I get the following message:

QObject::connect: Cannot queue arguments of type 'statuses'
(Make sure 'statuses' is registered using qRegisterMetaType().)

I did a search for qRegisterMetaType() but I didn’t find anything relating to Python that I could understand.

Asked By: Enfors

||

Answers:

(Py)Qt signals and slots work cross-threads just the same as in a single thread. So there’s nothing really special to set up:

Define a slot (method) in the main thread, and connect the thread’s signal to this slot (the connection shall be also done in the main thread). Then, in the thread, when you want to, just emit the signal and it should work. Here’s a tutorial on using signals and slots with threading in PyQt.

I recommend you to try it on a small toy example first, before moving to your application.

Answered By: Eli Bendersky

Check out this question I asked a while back. There is a code example that might help you figure out what you need to do.

What you said about registering your signal makes me think of this code (from the aforementioned question):

class ProcessingThread(threading.Thread, QtCore.QObject):
    __pyqtSignals__ = ( "progressUpdated(str)",
                        "resultsReady(str)")

I’m passing strings in my example, but you should be able to replace str with list.

If it turns out that you can’t pass mutable objects, you can handle your results the way I do in my example (i.e. set a results variable in the thread, tell the main thread that they are ready, and have the main thread “pick them up”).

Update:

You get the message QObject::connect: Cannot queue arguments of type 'statuses' because you need to define the type of argument that you will pass when you emit your signal. The type you want to pass is list not statuses.

When you connect your signal it should look like this:

QtCore.QObject.connect(self.twit_in,
                       QtCore.SIGNAL("newStatuses(list)"),
                       self.update_tweet_list)

When you emit your signal it should look like this:

self.emit(SIGNAL("newStatuses(list)"), statuses)

given that statuses is a list. Note that you may want to emit a deep copy of your list depending on your situation.

Update 2:

Ok, using list as the type is not correct. From the PyQt4 help reference:

PyQt Signals and Qt Signals

Qt signals are statically defined as
part of a C++ class. They are
referenced using the
QtCore.SIGNAL() function. This
method takes a single string argument
that is the name of the signal and its
C++ signature. For example::

QtCore.SIGNAL("finished(int)")

The returned value is normally passed
to the QtCore.QObject.connect()
method.

PyQt allows new signals to be defined
dynamically. The act of emitting a
PyQt signal implicitly defines it.
PyQt v4 signals are also referenced
using the QtCore.SIGNAL()
function.

The PyQt_PyObject Signal Argument Type

It is possible to pass any Python
object as a signal argument by
specifying PyQt_PyObject as the
type of the argument in the signature.
For example::

QtCore.SIGNAL("finished(PyQt_PyObject)")

While this would normally be used for
passing objects like lists and
dictionaries as signal arguments, it
can be used for any Python type. Its
advantage when passing, for example,
an integer is that the normal
conversions from a Python object to a
C++ integer and back again are not
required.

The reference count of the object
being passed is maintained
automatically. There is no need for
the emitter of a signal to keep a
reference to the object after the call
to QtCore.QObject.emit(), even if
a connection is queued.

Answered By: tgray

From this example:

http://doc.qt.digia.com/4.5/qmetatype.html

 int id = QMetaType.type("MyClass");

You can write down in Python

from PyQt4 import QtCore    
id = QtCore.QMetaType.type('MyClass')

Edit

The answer extracted from the comment:

self.emit(SIGNAL("newStatuses(PyQt_PyObject)"), statuses)
Answered By: user111086

You can also do this, which is much more pythonic (and readable!).

# create a signal equivalent to "void someSignal(int, QWidget)"
someSignal = QtCore.pyqtSignal(int, QtGui.QWidget)

# define a slot with the same signature
@QtCore.pyqtSlot(int, QtGui.QWidget)
def someSlot(status, source):
    pass

# connect the signal to the slot
self.someSignal.connect(self.someSlot)
Answered By: Mark Visser

When you’re using these old-style signals/slots in PyQt, there’s actually no need to declare types at all. These should work:

QtCore.QObject.connect(self.twit_in,
                   QtCore.SIGNAL("newStatuses"),
                   self.update_tweet_list)

...

self.emit(SIGNAL("newStatuses"), statuses)

In this case, PyQt will just create a new signal type for you on the fly when you emit the signal. If you wanted to user the new-style signals, then it would look more like:

class TwitterThread(QThread):
    newStatuses = pyqtSignal(object)

....

self.newStatuses.emit(statuses)
Answered By: Luke
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.