--- category: '' date: 2009/03/03 13:09 description: '' link: '' priority: '' slug: BBS48 tags: kde, qt, programming, python, pyqt title: PyQt by Example (Session 2) type: text updated: 2009/03/03 13:09 url_type: '' --- Requirements ============ If you haven't yet, check `Session 1`_ first. .. _session 1: http://lateral.netmanagers.com.ar/stories/BBS47.html All files for this session are here: `Session 2 at GitHub`_ .. _Session 2 at GitHub: http://github.com/ralsina/pyqt-by-example/tree/master/session2 Session 2: Plugging Things ========================== In `Session 1`_ we created our application's main window, a skeleton application that displays said window, and a simple Elixir_ based backend. We did not, however, connect both things. And connecting things is the important step we are taking in this session. We will be working first on our main.py_. Loading Data ~~~~~~~~~~~~ The first thing is that we need to use our backend, so we have to import todo.py_. We do this in `line 13`_ .. _line 13: #F2_13 Then, in `line 25`_ we do the first real new work: we get the list of tasks and put them in our list. Let's examine that in detail. Here is the code from lines 25_ to 35_: .. code-block:: Python :linenos: # Let's do something interesting: load the database contents # into our task list widget for task in todo.Task.query().all(): tags=','.join([t.name for t in task.tags]) item=QtGui.QTreeWidgetItem([task.text,str(task.date),tags]) item.task=task if task.done: item.setCheckState(0,QtCore.Qt.Checked) else: item.setCheckState(0,QtCore.Qt.Unchecked) self.ui.list.addTopLevelItem(item) Remember our backend, todo.py_? In it we defined Task objects that are stored in a database. If you are not familiar with Elixir_, ``todo.Task.query().all()`` gets a list of all Task objects from the database. Then we assign the tags separated by commas to ``tags``, which will look something like ``"home,important"``. And now, we see our first Qt-related code. First: self.ui.list is a widget. Everything we put on the window using designer is accessible using self.ui.object_name and the object_name is set using designer. If you right-click on the big widget in our window you can see that the widget's object name is **list**: .. figure:: http://lateral.netmanagers.com.ar/static/tut2-object_name.png The object name is **list**. You can also see it (and change it) in the Object Inspector and in the Property Editor. That is not just a widget. It's a QTreeWidget, useful for showing fancy multi-column lists and trees. You can read a lot about this widget in its manual_ but let's do the very short version: * We create QTreeWidgetItem_ objects. These take a list of strings, which are displayed in the widget's columns. In this case, we are adding the task text ("Buy groceries"), the due date, and the tags. * We set item.task to task. This is so later on we know what task is described by a specific item. Not the most elegant way, perhaps, but by far the easiest. * We add each item to the widget using addTopLevelItem_ so they are all at the same level (not hierarchical, as childs and parents) * It will work as a list Then, if the task is done (``task.done==True``) we make a checkmark next to the task. If it's not done, we do not. For many simple programs, this is all you need to know about QTreeWidget. It's a rather complex widget, and very powerful, but this will do for our task. Investigate! So, what does it do? Let's run ``python main.py`` and find out! .. figure:: http://lateral.netmanagers.com.ar/static/tut2-window3.png The task list with our sample tasks. If your window is empty, try running ``python todo.py`` first, which will create some sample tasks. You can even check a task to mark it done! That's because we have also implemented (partial) saving of modified tasks... Saving Data ~~~~~~~~~~~ So, what we want is that when the user clicks on the checkbox, the task should be modified accordingly, and stored in the database. In most toolkits you would be looking for callbacks. Here things are a bit different. When a Qt widget wants to make you notice something, like "The user clicked this button" or "The Help menu is activated", or whatever, they *emit signals* (go along for a minute). In particular, here we are interested in the itemChanged signal of QTreeWidget: QTreeWidget.itemChanged ( item, column ) [signal] This signal is emitted when the contents of the column in the specified item changes. And, whenever you click on the checkbox, this signal is emitted. Why's that important? Because you can connect your own code to a signal, so that whenever that signal is emitted, your code is executed! Your code is called a *slot* in Qt slang. It's like a callback, except that: a) The signal doesn't know what is connected to it (or if there is anything connected at all) b) You can connect as many slots to a signal as you want. This helps keep your code `loosely coupled`_. We *could* define a method in the Main class, and connect it to the itemChanged signal, but there is no need because we can use AutoConnect. If you add to Main a method with a specific name, it will be connected to that signal. The name is on_objectname_signalname. Here is the code (lines 37_ to 42_): .. code-block:: python :linenos: def on_list_itemChanged(self,item,column): if item.checkState(0): item.task.done=True else: item.task.done=False todo.saveData() See how we use item.task to reflect the item's checkState (whether it's checked or not) into the task state? The ``todo.saveData()`` at the end makes sure all data is saved in the database via Elixir. AutoConnect is what you will use 90% of the time to add behaviour to your application. Most of the time, you will just create the window, add the widgets, and hook signals via AutoConnect. In some occasions that will not be enough. But we are not there yet. This was a rather short session, but be prepared for the next one: we will be doing **Important Stuff with Designer** (TM)! In the meantime, you may want to check these pages: * Learn about widgets_ available in Qt * Learn about `signals and slots`_ * `Session 3`_ .. _Session 3: http://lateral.netmanagers.com.ar/stories/BBS49.html .. _widgets: http://doc.trolltech.com/4.4/gallery.html .. _signals and slots: http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/pyqt4ref.html#signal-and-slot-support ----------------- Here you can see what changed between the old and new versions: .. raw:: html :file: pytut/session2/diff1.html .. _loosely coupled: http://en.wikipedia.org/wiki/Loosely_coupled .. _QTreeWidgetItem: http://doc.trolltech.com/4.4/qtreewidgetitem.html .. _addTopLevelItem: http://doc.trolltech.com/4.4/qtreewidget.html#addTopLevelItem .. _manual: http://doc.trolltech.com/4.4/qtreewidget.html#details .. _line 25: #F2_25 .. _25: #F2_25 .. _35: #F2_35 .. _37: #F2_37 .. _42: #F2_42 .. _Elixir: http://elixir.ematia.de .. _python: http://www.python.org .. _session 1: http://lateral.netmanagers.com.ar/stories/BBS47.html .. _main.py: http://github.com/ralsina/pyqt-by-example/tree/master/session2/main.py .. _sqlalchemy: http://www.sqlalchemy.org .. _sqlite: http://www.sqlite.org .. _pyqt: http://www.riverbankcomputing.co.uk/software/pyqt/intro .. _sources: http://github.com/ralsina/pyqt-by-example/tree/master/session2 .. _github: http://www.github.org .. _todo.py: http://github.com/ralsina/pyqt-by-example/tree/master/session2/todo.py