threading: It is not safe to use pixmaps outside the GUI thread
Asked Answered
J

4

5

I'm building a music player, that checks the status with SqueezePlay, which is a SqueezeBox controller app. To cut a long story short, I'm checking the status of Squeezeplay ever 5 seconds by using threading. If the song title changes, I let it update the labels (Qlabel, album artwork (QPixmap), etc. However, when I ask it to update it via threading, I'm getting It is not safe to use pixmaps outside the GUI thread .

How can I do threading but still set the QPixmap?

Sample code:

#self.sq.getArtwork() returns variable with the image
coverArt = self.sq.getArtwork()
coverPixMap = QtGui.QPixmap()
coverPixMap.loadFromData(coverArt)
self.albumArt.setPixmap(coverPixMap)

Many thanks!

Update: I tried the following with Emit, but it doesn't work, can someone take a look what I'm doing wrong?

def setNewArtwork(self, image):
    coverPixMap = QtGui.QPixmap()
    coverPixMap.convertFromImage(image)
    icon = QtGui.QIcon(coverPixMap)
    item.setIcon(icon)

def getNewArtwork(self):
    coverArt = self.sq.getArtwork()
    icon = QtGui.QImage(coverArt)
    self.emit(QtCore.SIGNAL('setNewArtwork(QImage)'), icon)
Juliettejulina answered 27/12, 2011 at 21:1 Comment(4)
use QImage instead of QPixmapMcdowell
self.emit(QtCore.SIGNAL('setNewArtwork(QImage)'), icon) Should this line have QPixmap instead of QImage, since you're using pixmaps? I don't know how hard the types are enforced. Also make sure that you're using a queued connection, if such a concept exist in PyQT. This ensures that the slot is run in the receiver's thread rather than the caller's thread.Arber
When you say it "doesn't work" with signals, in what way doesn't it work? What goes wrong? The same thing? Something else?Arber
@FLX: did you find a solution? I am interested.Guilty
G
7

All graphical Qt operations should happen in the main thread. Other threads are not really allowed to call Qt graphical operations (including probably pixmaps).

They could emit Qt signals to the main thread. Or simply (on Linux) write into a pipe, and have the main thread wait for input on that pipe.

Of course, you have to define the signals (and also the slots) you want. In C++ code, you need to mark them with signals: (or slots:) and your C++ code should be processed by the moc. I don't know what is the Python counterpart (perhaps python reflection abilities might be enough, I really don't know). You then have to connect signals to slots, with a queued connection. I have no idea how to do that in Python.

Gymnastic answered 27/12, 2011 at 21:3 Comment(4)
I'm kinda new to this, how would this be accomplished? I could really use some example code :)Juliettejulina
I added some links. Qt documentation is much better than what I could explain in a few minutes!Gymnastic
Thanks Basile, I've tried it but it doesn't work; see my example in the original post - what am I doing stupidly wrong?Juliettejulina
You probably have to define your signals and slots, and connect signals to slots. You should find out how to do that in Python (or code your application in C++, or with GTK...)Gymnastic
H
4

To answer the question regarding how to emit the signal in python:

Unlike C++, when emitting a user-defined PyQt signal (as opposed to a Qt one), the signature should be omitted.

So, to emit the signal, do something like this:

thread.emit(QtCore.SIGNAL('newArtworkAvailable'), icon)

And to connect to the signal, do something like this:

widget.connect(thread, QtCore.SIGNAL('newArtworkAvailable'),
               widget.setNewArtwork)

And just to be clear:

For this to work, the non-gui thread must emit the signal, which is then received by the appropriate widget in the main gui thread. Creating a QImage in the non-gui thread should be okay, but never attempt to call any gui-related methods outside of the main thread.

NB:

I have used old-style signal syntax here because that is what you appear to be using. However, you might want to look at PyQt's new-style signal and slot support as it is much more flexible and pythonic.

Huntsman answered 28/12, 2011 at 0:8 Comment(0)
S
1

you probably need to send all your drawing jobs to the main thread.

Secco answered 27/12, 2011 at 21:7 Comment(0)
E
0

I've tried sth, please let me know if it rings a bell, i've done sth similar for mine (but im far from python for a while, so i may have made mistakes as well, if so, sorry.)

class MyThread(QThread, ui):
    def __init__(self, ui):
        super(MyThread, self).__init__(self)
        self.ui = ui

    def run(self):
       coverArt = self.ui.getArtwork()
       coverPixMap = QtGui.QPixmap()
       coverPixmap.convertFromImage(QtGui.QIcon(coverArt))
       icon = QtGui.QImage(coverPixMap)
       self.ui.item.setIcon(icon)  // set icon
       self.ui.singerLabel.setText("Singer")  // update label 

# your gui class
class YourInterface(QtGui.QWidget):
    def __init__(self):
       QtGui.QWidget.__init__(self)
        myThread = MyThread(self)
        self.myButton.clicked.connect(myThread.run)
        # all other stuff
        #
        #
Embarkment answered 27/12, 2011 at 23:5 Comment(1)
This doesn't work. It would return something like: QPixmap: It is not safe to use pixmaps outside the GUI threadGuilty

© 2022 - 2024 — McMap. All rights reserved.