Harsh J

Memoirs of a QWERTY Keyboard

Archive for the ‘Tips’ tag

PyQt FAQ – Custom Signals, JPEG, Mouse Hovers and More

leave a comment

Note: If you’re new to using PyQt but are interested in great cross-platform GUI application development please read the PyQt Introduction article.

I actually think of this more as an FSQ than an FAQ. Frequently Searched Questions on my blog.

These are the contents of this article:


Lets start with the most searched item.

Writing Custom Signals in PyQt

Writing custom signals that you want to ‘emit’ or trigger during certain events is an easy task. You need to create your signal object first. This is done using the PyQt.QtCore provided pyqtSignal() function. Its signature is given as:

QtCore.pyqtSignal([optional argument types], [optional name])

We need to create a signal object using this signal as a property in our class, then call its connect and emit methods to actually use it. If I were to explain diagrammatically:

Creating Custom Signals using PyQt

Creating Custom Signals using PyQt

The following code sample explains three types of signals:

  • Signals with no arguments (simpleSig)
  • Signals with some arguments (2, argumentSig)
  • Signals with multiple-type (overloaded) arguments (int and QString, doubleSig)
# -*- coding: utf-8 -*-
from PyQt4.QtGui import *
from PyQt4.QtCore import *

import sys

class MyWidget(QWidget):

    # Defining signals below:

    # No arguments, its a simple signal that can
    # be emitted.
    simpleSig = pyqtSignal()

    # A signal that requires two parameters.
    # (One is an integer, other is a Python list type)
    # You can have one or more than two as well.
    argumentSig = pyqtSignal(int, list)

    # A signal that allows two different sets of params.
    # One is if you send an int, the other is for QString.
    # This signal is also optionally named using 'name'
    # for use in more dynamic purposes.
    doubleSig = pyqtSignal((int,), (QString,), name='doubles')

    def __init__(self, parent=None):
        # Construct the parent, never forget this!
        super(MyWidget, self).__init__(parent)

        # Connect our signals to their slots
        self.simpleSig.connect(self.simpleSlot)
        self.argumentSig.connect(self.argumentSlot)
        # We have same slot for the double signals
        # (aka) overloaded signals.
        # Cause we can check the argument from within too!
        self.doubleSig['int'].connect(self.doubleSlot)
        self.doubleSig['QString'].connect(self.doubleSlot)

        # Connect our button to a slot which
        # will call the signals.
        self.button = QPushButton('Press me', self)
        self.button.clicked.connect(self.buttonClicked)

    def buttonClicked(self, checked=False):
        # Emit the various signals to use them.
        self.simpleSig.emit()
        self.argumentSig.emit(2, [1,2,3])
        # Overloaded signals have the following format
        # for emitting specifically.
        self.doubleSig['int'].emit(42)
        self.doubleSig['QString'].emit('Hitchhiking a ride.')

    # Defining custom-signal slots below:

    def simpleSlot(self):
        # Takes no arguments
        print "Simple custom signal:",
        print "No arguments in this type of signal."

    def argumentSlot(self, *args):
        # ( Can also be 'argumentSlot(self, intArg, listArg)' )
        # Has two arguments anyway, which is collected into one
        # using '*' (A Python feature)
        print "Multi-argument signal:",
        print "Two arguments were passed: value: '%d' of %s and value: '%s' of %s." % (args[0], type(args[0]), args[1], type(args[1]))

    def doubleSlot(self, someArgument):
        # This slot handles both doubleSig(int)
        # and doubleSig(QString)
        print "Overloaded signal:",
        print "An argument was passed and it was of '%s' and value: '%s'." % (type(someArgument), someArgument)

if __name__=='__main__':
    app = QApplication(sys.argv)
    widget = MyWidget()
    widget.show()
    app.exec_()

The output upon running this example code would be (printed in stdout):

Simple custom signal: No arguments in this type of signal.
Multi-argument signal: Two arguments were passed: value: '2' of <type 'int'> and value: '[1, 2, 3]' of <type 'list'>.
Overloaded signal: An argument was passed and it was of '<type 'int'>' and value: '42'
Overloaded signal: An argument was passed and it was of '<class 'PyQt4.QtCore.QString'>' and value: 'Hitchhiking a ride.'

I hope this makes it clear on how to create your own/custom signals and to piggyback data on it via arguments (using their class/type to create them).

References: PyQt Documentation

Saving an Image as JPEG using QImage

JPEG is a wonderful, lossy image format. PyQt supports JPEG saving if a plugin called libjpeg exists. Anyhow to save a QImage as a JPEG image, do the following:

# Create a 50 pixel square image
# (or load your own using a filename).
img = QImage(50, 50, QImage.Format_RGB32)
# Lets draw a red square on this.
painter = QPainter(img)
painter.setBrush(QBrush(QColor('#ff0000')))
painter.drawRect(0,0,50,50)
painter.end()
# Save it as JPEG. Extension mention does
# all internal magic. 'jpg' also allowed.
img.save("sample.jpeg")

The above code snippet creates an image ‘sample.jpeg’ that looks like below:

A QImage saved JPEG - With a shape drawn using QPainter

A QImage saved JPEG - With a shape drawn using QPainter

There it is, as simple as that! Can be done via QPixmap too.

References: QImage, QPainter (Lots of other shape/figure drawing help here)

Tracking Mouse Hover Events with PyQt

Mouse hover events (known as the mouseMoveEvent(event) in PyQt) are tracked by default if the mouse is pressed and dragged. However if you’d like to also track mouse move events upon simple hover also (without a press requirement) you will need to enable the mouseTracking property of the QWidget (also available in all its derivatives, naturally).

So, to enable mouse tracking / mouse hover tracking for your widget you will need to call widget.setMouseTracking(True). The following code snippet is again a simple program that prints co-ordinates of the mouse pointer upon hover (without requiring a press held).

from PyQt4.QtGui import *
from PyQt4.QtCore import *

import sys

class MyWidget(QWidget):

    def __init__(self, parent=None):
        super(MyWidget, self).__init__(parent)

        # Enable mouse hover event tracking
        self.setMouseTracking(True)

    def mouseMoveEvent(self, event):
        # This is a QWidget provided event
        # that fires for every mouse move event
        # (In our case the move is also the hover)
        print "Mouse Pointer is currently hovering at: ", event.pos()

if __name__=='__main__':
    app = QApplication(sys.argv)
    widget = MyWidget()
    widget.show()
    app.exec_()

It should spew out some output like:

Mouse Pointer is currently hovering at:  PyQt4.QtCore.QPoint(415, 66)
Mouse Pointer is currently hovering at:  PyQt4.QtCore.QPoint(420, 60)
Mouse Pointer is currently hovering at:  PyQt4.QtCore.QPoint(424, 56)
Mouse Pointer is currently hovering at:  PyQt4.QtCore.QPoint(427, 51)
Mouse Pointer is currently hovering at:  PyQt4.QtCore.QPoint(433, 41)
Mouse Pointer is currently hovering at:  PyQt4.QtCore.QPoint(438, 36)

An additional way, apart from QWidget.setMouseTracking(True) is to set a Qt.Hover attribute via QWidget.setAttribute(attribute) and then filtering this event out. If you’d like me to give an example for this method too, post a comment. I think the mouse tracking solution does it for detecting mouse hover events over your widgets as the other way might look ugly.

References: QMouseEvent

Bonus Tips

Screen Size

Getting the screen size of the desktop (the display resolution in other words) is simple. You need to access the desktop widget using QApplication.desktop() [Its static so you don't need your QApplication object] and then pulling out the size() from it. That is, do:

QApplication.desktop().size()

References: QApplication

Setting the Window Size

Um, am not sure what’s so ambiguous about setting the size/geometry of a widget in PyQt but to generally set any widget’s size, be it your QMainWindow object or a simple inner QWidget derivative, all you have to do is to set its geometry property like so:

# x and y are co-ordinates where the widget/window has to appear
# and width and height are the sizes you're looking to set
# (You can keep position same by using widgetObj.x()/y() to get current
# values of x/y and passing them back in).
widgetObj.setGeometry(x, y, width, height)

References: QWidget

End

That’s all for now folks, keep the searches or comment-queries coming if you still have some! (And oh, please do refer Qt Assistant too, it’s the right thing to do).

Written by Harsh

May 6th, 2010 at 10:46 pm