forked from ralsina/pyqt-by-example
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tut2.txt
206 lines (114 loc) · 7.45 KB
/
tut2.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
==========================
PyQt by Example: Session 2
==========================
~~~~~~~~~~~~~~~
Plugging Things
~~~~~~~~~~~~~~~
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:: 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:: 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. That function is a bit gnarly because I wanted it to work with two different versions of 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: 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/blob/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/blob/master/session2/todo.py