If you haven't yet, check Session 1 first.
All files for this session are here: Session 2 at GitHub
Session 2: Plugging Things
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.
The first thing is that we need to use our backend, so we have to import todo.py.
We do this in line 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 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
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:
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!
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...
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:
The signal doesn't know what is connected to it (or if there is anything connected at all)
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.
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?
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:
Here you can see what changed between the old and new versions:
Generated by diff2html
© Yves Bailly, MandrakeSoft S.A. 2001
diff2html is licensed under the GNU GPL.
Last modified : Mon Mar 2 01:29:24 2009
Last modified : Thu Mar 5 02:03:03 2009
|1||# -*- coding: utf-8 -*-||1||# -*- coding: utf-8 -*-|
|3||"""The user interface for our app"""||3||"""The user interface for our app"""|
|5||import os,sys||5||import os,sys|
|7||# Import Qt modules||7||# Import Qt modules|
|8||from PyQt4 import QtCore,QtGui||8||from PyQt4 import QtCore,QtGui|
|10||# Import the compiled UI module||10||# Import the compiled UI module|
|11||from windowUi import Ui_MainWindow||11||from windowUi import Ui_MainWindow|
|13||# Import our backend|
|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):|
|18||# This is always the same||21||# This is always the same|
|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])|
|22||def main():||45||def main():|
|46||# Init the database before doing anything else|
|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)|
|28||# It's exec_ because exec is a reserved word in Python||54||# It's exec_ because exec is a reserved word in Python|
|32||if __name__ == "__main__":||58||if __name__ == "__main__":|
Generated by diff2html on Fri Mar 6 00:58:30 2009
Command-line: /home/ralsina/bin/diff2html session1/main.py session2/main.py