-
Notifications
You must be signed in to change notification settings - Fork 56
/
tut1.txt
315 lines (207 loc) · 11.9 KB
/
tut1.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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
===============
PyQt by Example
===============
Introduction
============
This series of tutorials is inspired by two things:
* LatinoWare 2008, where I presented this very app as an introduction to PyQt development.
* A lack of (in my very humble opinion) PyQt tutorials that show the way I prefer to develop applications.
The second item may sound a bit belligerent, but that's not the case. I am not saying the other tutorials are wrong, or bad, I just say they don't work the way I like to work.
I don't believe in teaching something and later saying "now I will shw you how it's really done". I don't believe in toy examples. I believe that you are smart enough to only learn things once, and learning the true thing the first time.
So, in this series, I will be developing a small TODO application using the tools and procedures I use in my actual development, except for the Eric IDE. That is because IDEs are personal preferences and for a project fo this scope really doesn't add much.
One other thing I will not add is unit testing. While very important, I think it would distract from actually *doing*. If that's a problem, it can be added in a later version of the tutorial.
Requirements
============
You must have installed the following programs:
* Python_: I am using 2.6, I expect 2.5 or even 2.4 will work, but I am not testing them.
* Elixir_: This is needed by the backend. It requires SQLAlchemy_ and we will be using SQLite_ as our database. If you install Elixir_ everything else should be installed automatically.
* PyQt_: I will be using version 4.4
* Your text editor of choice
This tutorial doesn't assume knowledge of Elixir, PyQt or databases, but does assume a working knowledge of Python. If you don't know python yet, this is not the right tutorial for you yet.
You can get the full code for this session here: Sources_ (click the "Download" button).
Since this tutorial is completely hosted in GitHub_ you are free to contribute improvements, modifications, even whole new sessions or features!
Session 1: The basics
=====================
The backend
-----------
The most recent version of this session (in RST format) is always available at GitHub's `master tree`_ as tut1.txt_
Since we are developing a TODO application, we need a backend that handles the storage, retrieval and general managing of TODO tasks.
To do that the simplest possible way, I will do it using Elixir_, "A declarative layer over the SQLAlchemy Object-Relational Mapper".
If that sounded very scary, don't worry. What that means is "a way to create objects that are automatically stored in a database".
Here is the code, with comments, for our backend, called todo.py_. Hopefully, we will not have to look at it again until much later in the tutorial!
.. code-block:: python
# -*- coding: utf-8 -*-
"""A simple backend for a TODO app, using Elixir"""
import os
from elixir import *
dbdir=os.path.join(os.path.expanduser("~"),".pyqtodo")
dbfile=os.path.join(dbdir,"tasks.sqlite")
# It's good policy to have your app use a hidden folder in
# the user's home to store its files. That way, you can
# always find them, and the user knows where everything is.
class Task(Entity):
"""
A task for your TODO list.
"""
# By inheriting Entity, we are using Elixir to make this
# class persistent, Task objects can easily be stored in
# our database, and you can search for them, change them,
# delete them, etc.
using_options(tablename='tasks')
# This specifies the table name we will use in the database,
# I think it's nicer than the automatic names Elixir uses.
text = Field(Unicode,required=True)
date = Field(DateTime,default=None,required=False)
done = Field(Boolean,default=False,required=True)
tags = ManyToMany("Tag")
# A task has the following:
#
# * A text ("Buy groceries"). Always try to use unicode
# in your app. Using anything else is *not worth
# the trouble*.
#
# * A date for when it's due.
#
# * A "Done" field. Is it done?
#
# * A list of tags. For example, "Buy groceries" could be
# tagged "Home" and "Important". It's ManyToMany because
# a task can have many tags and a tag can have many tasks.
def __repr__(self):
return "Task: "+self.text
# It's always nicer if objects know how to turn themselves
# into strings. That way you can help debug your program
# just by printing them. Here, our groceries task would
# print as "Task: Buy groceries".
# Since earlier I mentioned Tags, we need to define them too:
class Tag(Entity):
"""
A tag we can apply to a task.
"""
# Again, they go in the database, so they are an Entity.
using_options(tablename='tags')
name = Field(Unicode,required=True)
tasks = ManyToMany("Task")
def __repr__(self):
return "Tag: "+self.name
# They are much simpler objects: they have a name,
# a list of tagged tasks, and can convert themselves
# to strings.
saveData=None
# Using a database involves a few chores. I put them
# in the initDB function. Just remember to call it before
# trying to use Tasks or Tags!
def initDB():
# Make sure ~/.pyqtodo exists
if not os.path.isdir(dbdir):
os.mkdir(dbdir)
# Set up the Elixir internal thingamajigs
metadata.bind = "sqlite:///%s"%dbfile
setup_all()
# And if the database doesn't exist: create it.
if not os.path.exists(dbfile):
create_all()
# This is so Elixir 0.5.x and 0.6.x work
# Yes, it's kinda ugly, but needed for Debian
# and Ubuntu and other distros.
global saveData
import elixir
if elixir.__version__ < "0.6":
saveData=session.flush
else:
saveData=session.commit
# Usually, I add a main() function to all modules that
# does something useful, perhaps run unit tests. In this
# case, it demonstrates our backend's functionality.
# You can try it by running it like this::
#
# python todo.py
# No detailed comments in this one: study it yourself, it's not complicated!
def main():
# Initialize database
initDB()
# Create two tags
green=Tag(name=u"green")
red=Tag(name=u"red")
#Create a few tags and tag them
tarea1=Task(text=u"Buy tomatos",tags=[red])
tarea2=Task(text=u"Buy chili",tags=[red])
tarea3=Task(text=u"Buy lettuce",tags=[green])
tarea4=Task(text=u"Buy strawberries",tags=[red,green])
saveData()
print "Green Tasks:"
print green.tasks
print
print "Red Tasks:"
print red.tasks
print
print "Tasks with l:"
print [(t.id,t.text,t.done) for t in Task.query.filter(Task.text.like(ur'%l%')).all()]
if __name__ == "__main__":
main()
The Main Window
---------------
Now, let's start with the fun part: PyQt_!
I recommend using designer to create your graphical interfaces. Yes, some people complain about interface designers. I say you should spend your time writing code for the parts where there are no good tools instead.
And here is the Qt Designer file for it: window.ui_. Don't worry about all that XML, just open it on your designer ;-)
This is how it looks in designer:
.. figure:: window2.png
The main window, in designer.
What you see is a "Main Window". This kind of window lets you have a menu, toolbars, status bars, and is the typical window for a standard application.
The "Type Here" at the top is because the menu is still empty, and it's "inviting" you to add something to it.
The big square thing with "Task" "Date" and "Tags" is a widget called QTreeView, which is handy to display items with icons, several columns, and maybe a hierarchical structure (A tree, thus the name). We will use it to display our task list.
You can see how this window looks by using "Form" -> "Preview" in designer. THis is what you'll get:
.. figure:: window1.png
The main window preview, showing the task list.
You can try resizing the window, and this widget will use all available space and resize alongside the window. That's important: windows that can't handle resizing properly look unprofessional and are not adequate.
In Qt, this is done using layouts. In this particular case, since we have only one widget, what we do is click on the form's background and select "Layout" -> "Layout Horizontally" (Vertically would have had the exact same effect here).
When we do a configuration dialog, we will learn more about layouts.
Now, feel free to play with designer and this form. You could try changing the layout, add new things, change properties of the widgets, experiment at will, learning designer is worth the effort!
Using our Main Window
---------------------
We are now going to make this window we created part of a real program, so we can then start making it work.
First we need to compile our .ui file into python code. You can do this with this command::
pyuic4 window.ui -o windowUi.py
Now let's look at main.py_, our application's main file:
.. code-block:: python
# -*- coding: utf-8 -*-
"""The user interface for our app"""
import os,sys
# Import Qt modules
from PyQt4 import QtCore,QtGui
# Import the compiled UI module
from windowUi import Ui_MainWindow
# Create a class for our main window
class Main(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
# This is always the same
self.ui=Ui_MainWindow()
self.ui.setupUi(self)
def main():
# Again, this is boilerplate, it's going to be the same on
# almost every app you write
app = QtGui.QApplication(sys.argv)
window=Main()
window.show()
# It's exec_ because exec is a reserved word in Python
sys.exit(app.exec_())
if __name__ == "__main__":
main()
As you can see, this is not at all specific to our TODO application. Whatever was in that .ui file would work just fine with this!
The only interesting part is the Main class. That class uses the compiled ui file and is where we will put our application's user interface logic. You **never** edit the .ui file or the generated python file manually!
Let me put that in these terms: **IF YOU EDIT THE UI FILE (WITHOUT USING DESIGNER) OR THE GENERATED PYTHON FILE YOU ARE DOING IT WRONG! YOU FAIL! EPIC FAIL!**. I hope that got across, because there is at least one tutorial that tells you to do it. **DON'T DO IT!!!**,
You just put your code in this class and you will be fine.
So, if you run main.py, you will make the application run. It will do nothing interesting, because we need to attach the backend to our user interface, but that's session 2.
.. _main.py: http://github.com/ralsina/pyqt-by-example/blob/master/session1/main.py
.. _window.ui: http://github.com/ralsina/pyqt-by-example/blob/master/session1/window.ui
.. _Elixir: http://elixir.ematia.de
.. _python: http://www.python.org
.. _sqlalchemy: http://www.sqlalchemy.org
.. _pyqt: http://www.riverbankcomputing.co.uk/software/pyqt/intro
.. _todo.py: http://github.com/ralsina/pyqt-by-example/blob/master/session1/todo.py
.. _master tree: http://github.com/ralsina/pyqt-by-example/blob/master
.. _tut1.txt: http://github.com/ralsina/pyqt-by-example/blob/master/tut1.txt
.. _sources: http://github.com/ralsina/pyqt-by-example/tree/master/session1
.. _sqlite: http://www.sqlite.org
.. _github: http://www.github.org