Friday, 8 January 2010

Simple PySide / PyQt tutorial, aimed at Maemo development

I recently met an acquaintance around Maemo (hi Khertan!) who was having problems coming to grips with how to use Qt in general. He's a python developer currently, with exposure to Gtk+, so I thought I'd write up some help for him to read over.

I hope they help you, with whatever you're struggling with! :)

The Code
--------

# Written by Robin Burchell  
# No licence specified or required, but please give credit where it's due, and please let me know if this helped you.
# Feel free to contact with corrections or suggestions.
#
# We're using PySide, Nokia's official LGPL bindings.
# You can however easily use PyQt (Riverside Computing's GPL bindings) by commenting these and fixing the appropriate imports.
from PySide.QtCore import *
from PySide.QtGui import *
#from PyQt4 import *
#from PyQt4.QtCore import *
#from PyQt4.QtGui import *
import sys
 
# In Qt, everything is a class.
# We can subclass stuff to do whatever we want - it seems a bit strange at first,
# but it keeps all related functionality in one place, which helps to keep you organised.
#
# We're subclassing 'QWidget' here. QWidget is the base class for all graphical elements in Qt.
# You can do all sorts of cool things like drawing your widget yourself, but we won't get into that.
#
# See also:
#   http://doc.trolltech.com/4.6/qwidget.html
class MyMainWindow(QWidget):
    def __init__(self):
        # first things first, we need to initialise the Qt parent, otherwise it won't work properly.
        #
        # A QWidget instance with no parent (None here) is a top level window.
        QWidget.__init__(self, None)
 
        # Now we start getting into building our interface. The first thing we need to learn about are layouts.
        # Layouts tell Qt where to put widgets, and how they fit with other widgets.
        # Unlike Gtk+, Qt layouts are *not* widgets - important difference to keep in mind.
        #
        # There are many types of layouts (from the complex, like QGridLayout) 
        # to the simple, like what we're using here.
        # See also:
        #   http://doc.trolltech.com/4.6/qlayout.html
        #   http://doc.trolltech.com/4.6/qvboxlayout.html
        vbox = QVBoxLayout()
 
        # Now let's create a button that can be pushed, and add it to our layout.
        #   http://doc.trolltech.com/4.6/qpushbutton.html
        b = QPushButton("Push me!")
        vbox.addWidget(b)
 
        # Let's make something happen when the button is pushed!
        self.connect(b, SIGNAL("clicked()"), self.buttonPushed)
 
        # Set the layout on the window: this means that our button will actually be displayed.
        self.setLayout(vbox)
 
    def buttonPushed(self):
        # Let's display a pretty dialog for fun.
        # Note that QDialog is themed to look very pretty in Maemo, think of e.g. the display of online IM accounts.
        # See also:
        #   http://doc.trolltech.com/4.6/qdialog.html
        d = QDialog(self)
 
        # Create another vertical layout, with a push button and a label, simple.
        vbox = QVBoxLayout()
 
        # First, the label:
        # QLabel is a simple class to display text (though it can of course do other cool things if you make it)
        #   http://doc.trolltech.com/4.6/qlabel.html
        l = QLabel("Hi there. You clicked a button!")
        vbox.addWidget(l)
 
        # Now the button.
        b = QPushButton("Close window")
 
        # Note that you can redirect signals and slots in a very cool way.
        # This redirects the button click action to close the window :)
        self.connect(b, SIGNAL("clicked()"), d.close)
 
        # Add the button to the box
        vbox.addWidget(b)
 
        # Display our layout
        d.setLayout(vbox)
 
        # Show the window!
        d.show()
 
if __name__ == '__main__':
    # QApplication controls things relating to a Qt application, like the main loop.
    # You should always instantiate it before using any QtGui related stuff.
    # See also:
    #   http://doc.trolltech.com/4.6/qapplication.html
    app = QApplication(sys.argv)
 
    # Create an instance (calling __init__, too, of course) of our window subclassing QWidget
    w = MyMainWindow()
 
    # Show our window
    w.show()
 
    # Main event loop of Qt. This won't return until the program exits.
    app.exec_()
    sys.exit()

13 comments:

  1. Very nice tutorial(s), I'm also reading the other two posts. Can I give you my little idea for your blog? You should try to use something (a plugin) to highlight the syntax of the source code, just like I do on my blog: http://www.andreagrandi.it/2009/08/13/writing-python-bindings-of-existing-c-libraries-%E2%80%93-3-%E2%80%93-building-and-installing-with-distutils/
    ReplyDelete
  2. @Andrea: I was thinking this when I was about to publish this one, and did briefly look into it - but it was about 7am my time and I hadn't slept, so I decided to get back to it later on ;).

    I don't know whether blogspot supports such things, but I'll devote some time into looking for one.
    ReplyDelete
  3. Excellent tutorial! Thanks for posting. I still don't get this PyQt vs. PySide thing although reading you, it doesn't seem to matter too much (just change a couple of imports).

    L.
    ReplyDelete
  4. @Lyderic: they're API-compatible, so really, there isn't a great deal of difference.

    The difference that there is, comes from licensing: pyside is licensed under LGPL, meaning that you can write non-GPL applications using it.

    PyQt uses GPL, which means that any applications using it must be GPL.

    This difference is actually the whole reason PySide exists. Nokia tried to convince PyQt's authors to re-licence it, but they didn't want to.
    ReplyDelete
  5. Thanks for the explanation. I guess that if I don't care about licensing (my app would be GPL anyway), I am better going with PyQt as they seem to be more stable and have been around longer.
    L.
    ReplyDelete
  6. Yeah. I'd go with PyQt for serious applications right now. PySide is improving, but it's still not quite there yet.

    It does of course have a larger developer community as far as I know, so I do expect that it will improve rapidly.
    ReplyDelete
  7. thanks a lot for this nice tutor... it was really helpful.. i have a question tried searching on line but was not a able find answer.. so here it goes

    - if i have 2 buttons and i need to send them to one function for connect.. how can i find which button was clicked ?

    from PySide.QtCore import *
    from PySide.QtGui import *

    import sys

    class MyMainWindow(QWidget):
    def __init__(self):
    QWidget.__init__(self, None)
    self.vbox = QVBoxLayout()
    self.l = QLabel("Loading...")
    #self.l.setAlignment(Qt.AlignBottom | Qt.AlignRight)
    self.vbox.addWidget(self.l)

    self.setLayout(self.vbox)

    def getOptions(self):
    print "getting options from server..."
    #todo - write code here to load..

    self.l.setText("Select your option");

    b = QPushButton("A")
    self.vbox.addWidget(b)
    self.connect(b, SIGNAL("clicked()"), self.buttonPushed)

    b = QPushButton("B")
    self.vbox.addWidget(b)
    self.connect(b, SIGNAL("clicked()"), self.buttonPushed)


    def buttonPushed(self):
    print "how do i know which button was pressed"

    if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MyMainWindow()
    w.show()
    w.getOptions()
    app.exec_()
    sys.exit()
    ReplyDelete
  8. @Akshath: Try sender().


    def buttonPushed(self):
    print("I just clicked button %s" %( self.sender().text())
    ReplyDelete
  9. Hi there

    Your tutorials have been extremely helpful for getting starting using PySide. But now - after hours of searching the internet without any luch - I'm stuck with a problem I hope you are able to help with.

    self.connect(b, SIGNAL("clicked()"), self.buttonPushed)

    The above calls the buttonPushed method. But what if I want to pass some additional arguments to buttonPushed? Say, the signature for buttonPushed is

    def buttonPushed(self, textToDisplay)

    How do I call buttonPushed with a value for textToDisplay?

    Does it all make any sense?
    ReplyDelete
  10. @mmm: hi there!

    you're certainly not alone in wanting this functionality, but unfortunately, Qt doesn't really offer it at present.

    there is QSignalMapper, but that is for fairly specialised usage.

    other than that, you'll have to just get what you want inside your slot instead of as a parameter.
    ReplyDelete
  11. Nice tutorial, really like it. How do I search for all your PySide/PyQt post from your blog?
    ReplyDelete
  12. I found the answer myself, search google with:
    pyqt site:blog.rburchell.com
    ReplyDelete
  13. Great tutorial!

    In response to:
    >How do I call buttonPushed with a value for textToDisplay?

    I am a complete newbie to Qt but came up with a really nice solution after lots of searching:

    import functools
    button_function = functools.partial(textToDisplay, "text_to_print")
    def buttonPushed(self, button_function)

    This seems to be the standard way.
    ReplyDelete