PyQt by Example (Session 2)

Requirements

If you haven’t yet, check Ses­sion 1 first.

All files for this ses­sion are here: Ses­sion 2 at GitHub

Session 2: Plugging Things

In Ses­sion 1 we cre­at­ed our ap­pli­ca­tion’s main win­dow, a skele­ton ap­pli­ca­tion that dis­plays said win­dow, and a sim­ple Elixir based back­end.

We did not, how­ev­er, con­nect both things. And con­nect­ing things is the im­por­tant step we are tak­ing in this ses­sion.

We will be work­ing first on our main.py.

Loading Data

The first thing is that we need to use our back­end, so we have to im­port to­do.py.

We do this in line 13

Then, in line 25 we do the first re­al new work: we get the list of tasks and put them in our list.

Let’s ex­am­ine that in de­tail. Here is the code from lines 25 to 35:

 1  # Let's do something interesting: load the database contents
 2  # into our task list widget
 3  for task in todo.Task.query().all():
 4      tags=','.join([t.name for t in task.tags])
 5      item=QtGui.QTreeWidgetItem([task.text,str(task.date),tags])
 6      item.task=task
 7      if task.done:
 8          item.setCheckState(0,QtCore.Qt.Checked)
 9      else:
10          item.setCheckState(0,QtCore.Qt.Unchecked)
11      self.ui.list.addTopLevelItem(item)

Re­mem­ber our back­end, to­do.py? In it we de­fined Task ob­jects that are stored in a data­base. If you are not fa­mil­iar with Elixir, to­­do.­­Task.­­query().al­l() gets a list of all Task ob­jects from the data­base.

Then we as­sign the tags sep­a­rat­ed by com­mas to tags, which will look some­thing like "home­,im­por­tan­t".

And now, we see our first Qt-re­lat­ed code.

First: self­.ui.list is a wid­get. Ev­ery­thing we put on the win­dow us­ing de­sign­er is ac­ces­si­ble us­ing self­.ui.ob­jec­t_­name and the ob­jec­t_­name is set us­ing de­sign­er. If you right-click on the big wid­get in our win­dow you can see that the wid­get’s ob­ject name is list:

http://lateral.netmanagers.com.ar/static/tut2-object_name.png

The ob­ject name is list.

You can al­so see it (and change it) in the Ob­ject In­spec­tor and in the Prop­er­ty Ed­i­tor.

That is not just a wid­get. It’s a QTreeWid­get, use­ful for show­ing fan­cy mul­ti­-­col­umn lists and trees.

You can read a lot about this wid­get in its man­u­al but let’s do the very short ver­sion:

  • We cre­ate QTreeWid­getItem ob­ject­s. These take a list of strings, which are dis­played in the wid­get’s col­umns. In this case, we are adding the task text (“Buy gro­ceries”), the due date, and the tags.
  • We set item.­task to task. This is so lat­er on we know what task is de­scribed by a spe­cif­ic item. Not the most el­e­gant way, per­hap­s, but by far the eas­i­est.
  • We add each item to the wid­get us­ing ad­dTo­pLevelItem so they are all at the same lev­el (not hi­er­ar­chi­cal, as childs and par­ents)
  • It will work as a list

Then, if the task is done (task.­­done==True) we make a check­mark next to the task. If it’s not done, we do not.

For many sim­ple pro­gram­s, this is all you need to know about QTreeWid­get. It’s a rather com­plex wid­get, and very pow­er­ful, but this will do for our task. In­ves­ti­gate!

So, what does it do? Let’s run python main.py and find out!

http://lateral.netmanagers.com.ar/static/tut2-window3.png

The task list with our sam­ple tasks.

If your win­dow is emp­ty, try run­ning python to­do.py first, which will cre­ate some sam­ple tasks.

You can even check a task to mark it done! That’s be­cause we have al­so im­ple­ment­ed (par­tial) sav­ing of mod­i­fied tasks…

Saving Data

So, what we want is that when the us­er clicks on the check­box, the task should be mod­i­fied ac­cord­ing­ly, and stored in the data­base.

In most tool­kits you would be look­ing for call­back­s. Here things are a bit dif­fer­en­t. When a Qt wid­get wants to make you no­tice some­thing, like “The us­er clicked this but­ton” or “The Help menu is ac­ti­vat­ed”, or what­ev­er, they emit sig­nals (go along for a min­ute).

In par­tic­u­lar, here we are in­ter­est­ed in the item­Changed sig­nal of QTreeWid­get:

QTreeWid­get.item­Changed ( item, col­umn ) [sig­nal]

This sig­nal is emit­ted when the con­tents of the col­umn in the spec­i­fied item changes.

And, when­ev­er you click on the check­box, this sig­nal is emit­ted. Why’s that im­por­tan­t? Be­cause you can con­nect your own code to a sig­nal, so that when­ev­er that sig­nal is emit­ted, your code is ex­e­cut­ed! Your code is called a slot in Qt slang.

It’s like a call­back, ex­cept that:

  1. The sig­nal does­n’t know what is con­nect­ed to it (or if there is any­thing con­nect­ed at al­l)
  2. You can con­nect as many slots to a sig­nal as you wan­t.

This helps keep your code loose­ly cou­pled.

We could de­fine a method in the Main class, and con­nect it to the item­Changed sig­nal, but there is no need be­cause we can use Au­to­Con­nec­t. If you add to Main a method with a spe­cif­ic name, it will be con­nect­ed to that sig­nal. The name is on_ob­ject­name_sig­nal­name.

Here is the code (lines 37 to 42):

1  def on_list_itemChanged(self,item,column):
2      if item.checkState(0):
3          item.task.done=True
4      else:
5          item.task.done=False
6      todo.saveData()

See how we use item.­task to re­flect the item’s check­State (whether it’s checked or not) in­to the task state?

The to­do.save­Data() at the end makes sure all da­ta is saved in the data­base via Elixir.

Au­to­Con­nect is what you will use 90% of the time to add be­hav­iour to your ap­pli­ca­tion. Most of the time, you will just cre­ate the win­dow, add the wid­get­s, and hook sig­nals via Au­to­Con­nec­t.

In some oc­ca­sions that will not be enough. But we are not there yet.

This was a rather short ses­sion, but be pre­pared for the next one: we will be do­ing Im­por­tant Stuff with De­sign­er (T­M)!

In the mean­time, you may want to check these pages:


Here you can see what changed be­tween the old and new ver­sion­s:

Modified lines:  34
Added line:  13, 14, 15, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48
Removed line:  None
Generated by diff2html
© Yves Bailly, MandrakeSoft S.A. 2001
diff2html is licensed under the GNU GPL.

  session1/main.py     session2/main.py
  34 lines
754 bytes
Last modified : Mon Mar 2 01:29:24 2009

    60 lines
1557 bytes
Last modified : Thu Mar 5 02:03:03 2009

1 # -*- coding: utf-8 -*-   1 # -*- coding: utf-8 -*-
2   2
3 “”“The user interface for our app”“”   3 “”“The user interface for our app”“”
4   4
5 import os,sys   5 import os,sys
6   6
7 # Import Qt modules   7 # Import Qt modules
8 from PyQt4 import QtCore,QtGui   8 from PyQt4 import QtCore,QtGui
9   9
10 # Import the compiled UI module   10 # Import the compiled UI module
11 from windowUi import Ui_MainWindow   11 from windowUi import Ui_MainWindow
12   12
      13 # Import our backend
      14 import todo
      15
13 # Create a class for our main window   16 # Create a class for our main window
14 class Main(QtGui.QMainWindow):   17 class Main(QtGui.QMainWindow):
15     def __init__(self):   18     def __init__(self):
16         QtGui.QMainWindow.__init__(self)   19         QtGui.QMainWindow.__init__(self)
17   20
18         # This is always the same   21         # This is always the same
19         self.ui=Ui_MainWindow()   22         self.ui=Ui_MainWindow()
20         self.ui.setupUi(self)   23         self.ui.setupUi(self)
21   24
      25         # Let’s do something interesting: load the database contents
      26         # into our task list widget
      27         for task in todo.Task.query().all():
      28             tags=’,’.join([t.name for t in task.tags])
      29             item=QtGui.QTreeWidgetItem([task.text,str(task.date),tags])
      30             item.task=task
      31             if task.done:
      32                 item.setCheckState(0,QtCore.Qt.Checked)
      33             else:
      34                 item.setCheckState(0,QtCore.Qt.Unchecked)
      35             self.ui.list.addTopLevelItem(item)
      36
      37     def on_list_itemChanged(self,item,column):
      38         if item.checkState(0):
      39             item.task.done=True
      40         else:
      41             item.task.done=False
      42         todo.saveData()
      43
      44
22 def main():   45 def main():
      46     # Init the database before doing anything else
      47     todo.initDB()
      48
23     # Again, this is boilerplate, it’s going to be the same on   49     # Again, this is boilerplate, it’s going to be the same on
24     # almost every app you write   50     # almost every app you write
25     app = QtGui.QApplication(sys.argv)   51     app = QtGui.QApplication(sys.argv)
26     window=Main()   52     window=Main()
27     window.show()   53     window.show()
28     # It’s exec_ because exec is a reserved word in Python   54     # It’s exec_ because exec is a reserved word in Python
29     sys.exit(app.exec_())   55     sys.exit(app.exec_())
30   56
31   57
32 if __name__ == “__main__”:   58 if __name__ == “__main__”:
33     main()   59     main()
34   60

Generated by diff2html on Fri Mar 6 00:58:30 2009
Command-line:
/home/ralsina/bin/diff2html session1/main.py session2/main.py

Comments

Comments powered by Disqus
Contents © 2000-2013 Roberto Alsina
Share