~mccane/practical-programming/trunk

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
316
317
318
319
320
321
322
323
324
\chapter{Graphical user interface programming}

\section{Event driven programming}

\myidx{Graphical User Interface (GUI)}\ programming is very different
from the kind of programming we've been discussing so far. We might
call the programming we've done so far \emph{Sequence Driven
Programming}. In this style of program, the computer does something,
then does something else, perhaps waiting for the user to type
something in, or getting input from somewhere else, but essentially it
proceeds in a sequential fashion. GUI programs are different because
they are usually \myidx{Event Driven}. In this sort of program, there
is usually a lot more user interaction, and the program almost always
has to do something (redrawing itself on the screen if nothing else).

Before looking at actual GUI programs, it's worthwhile seeing how we
might simulate event driven programming using sequence driven
programming. After all, underneath the hood, at the system level, it
really is sequence driven programming that is happening. Consider the
Python program displayed in Figure \ref{fig-event-simulate}.

\begin{figure}
\pythonFile{source/nongui_event.py}
\caption{A simulated event loop. \label{fig-event-simulate}}
\end{figure}

The program in Figure \ref{fig-event-simulate} contains all the elements of an event-driven
program. It has an event loop that runs forever, a way of getting
events from the user, a function that processes events, and a function
that redraws the screen. Note also that we used a class for our
simulated event loop. This is not completely necessary, but generally
makes writing GUI applications a little easier.

\section{TkInter introduction}

Tkinter (Tk interface) is Python's default GUI toolkit, although there are many
others. It is reasonably simple and lightweight and should be
perfectly adequate for GUI programs of small to medium
complexity. Let's have a look at a simple Tkinter program:

\begin{minipage}{\textwidth}
\pythonFile{source/tkinter_hello1.py}
\end{minipage}

If you run\footnote{In general, Tkinter programs should not be run
  from within Idle. The easiest way is to use a terminal and run from
  the command line.} the above program, you should see a window which looks like
Figure \ref{fig-tk-hello1}. 
\begin{figure}[h]
\centering
\includegraphics[scale=1]{illustrations/Tk_HelloWorld.png}
\caption{Tkinter Hello World \label{fig-tk-hello1}}.
\end{figure}

The structure of the program above is
typical of a Tkinter program and has the following general steps:
\begin{enumerate}
\item define a class which encapsulates the user interface components
  of the application:
\begin{enumerate}
\item create the specific user interface components
\item specify where in the interface the elements should go (by
calling the \pythoninline{grid} method),
\end{enumerate}
\item create the root window (\pythoninline{root = Tk()}),
\item instantiate a member of the application class
\item enter the main loop.
\end{enumerate}
Note that
\pythoninline{root} and \pythoninline{label} are objects of class
\pythoninline{Tk} and \pythoninline{Label} respectively. These classes
are defined by Tkinter.

You may also have noticed the special form of some of the function
calls like \pythoninline{Label(parent, text='Hello World')}. The
parameter \pythoninline{text='Hello World'} is an example of a
\myidx{named~parameter}. Named parameters are extremely useful when
combined with \myidx{default~parameter~values}. 

\begin{minipage}{\textwidth}
Here's an example:
\begin{python}
def read_value_type(prompt='Enter a value> ', convert=float):
    val = input(prompt)
    return convert(val)

print read_value_type()
print read_value_type(prompt='Enter a float> ')
print read_value_type(convert=int)
print read_value_type(prompt='Enter a boolean> ', convert=bool)
\end{python}
\end{minipage}
The only limitation on default parameters is that they must come after
non-default parameters in the function definition.

One other piece of notation you should be familiar with is the term
\myidx{widget}. A widget is a GUI element. Every GUI element can be
called a widget. Tkinter has several different types of widgets.
The most useful ones are:
\begin{description}
\item[\pythoninline{Tk}:] the root widget,
\item[\pythoninline{Label}:] a widget containing fixed text,
\item[\pythoninline{Button}:] a button widget,
\item[\pythoninline{Entry}:] a widget for entry of single lines of
text,
\item[\pythoninline{Canvas}:] a widget for drawing shapes onto the
screen.
\end{description}

\section{Introducing callbacks}

To do anything really useful with GUI programming, we need some way of
associating an action with a widget. Usually, when the user presses a
button on a GUI she expects something to happen as a result. Let's
make our Hello World program a bit more complex, by adding two
buttons:

\begin{minipage}{\textwidth}
\pythonFile{source/tkinter_hello.py}
\end{minipage}

If we run the above program, we will get a window that looks like
Figure \ref{fig-hello-quit}. In both calls to the function
\pythoninline{Button}, there is a parameter called
\pythoninline{command}. The value of the \pythoninline{command} parameter is
the callback or function associated with the action of pressing the
button. So, if we press the \pythoninline{Hello} button, it should print `hi
there' to the system console because that's what the function
\pythoninline{say\_hi} does. If we press the \pythoninline{Quit} button, then the
special function \pythoninline{parent.quit} will be called which,
unsurprisingly, causes the application to terminate.
\begin{figure}[h]
\centering
\includegraphics[scale=1]{illustrations/Tkinter_Btn_Mac.png}
\caption{Tkinter Buttons \label{fig-hello-quit}}.
\end{figure}

Since we're writing a GUI program, it would be very unusual (and quite
disconcerting) to make use of the console at all. When we want to
give a message to the user, it is more usual to use a \myidx{dialog}
widget. The module \pythoninline{tkMessageBox} includes numerous
useful dialogs. The above program can be modified to use a
\pythoninline{showinfo} dialog box instead:

\begin{minipage}{\textwidth}
\pythonFile{source/tkinter_hello3.py}
\end{minipage}
Now when we press the \pythoninline{Hello} button, we should see something
similar to Figure \ref{fig-hello-dialog}.
\begin{figure}[h]
\centering
\includegraphics[scale=1]{illustrations/tkMessageBox_Mac.png}
\caption{Tkinter Hello World Dialog\label{fig-hello-dialog}}.
\end{figure}

\section{User input}
\label{sec-user_input}
Tkinter has several widgets available for getting input from the user.
Dialog boxes allow one sort of input, but most interfaces have some
form of input on the main window. There are two main widgets for doing
this: \pythoninline{Entry} and \pythoninline{Text}. We will use the
\pythoninline{Entry} widget for our simple application.

To make use of user input widgets, we need a mechanism for getting
information back out of the widget. The easiest way to do that is to
use the \pythoninline{get} method from the widget and subsequently
convert the string to the appropriate variable if needed.

\begin{minipage}{\textwidth}
Let's have a look at an example of how to use an \pythoninline{Entry}
widget:
\pythonFile{source/tkinter_hello4.py}
\end{minipage}

Note that when the callback
\pythoninline{say\_hi} is called, the string associated with the
\pythoninline{Entry} widget 
is retrieved and included as part of the dialog
message. The main window for this program can be seen in Figure
\ref{fig-entry}.
\begin{figure}[h]
\centering
\includegraphics[scale=1]{illustrations/Tk_EntryExample.png}
\caption{Tkinter Entry Example\label{fig-entry}}.
\end{figure}
 
\section{Mini-case study}

Let's write a simple GUI program that can convert from Fahrenheit to
Celsius. The conversion equation is quite simple:
\begin{equation}
c = \frac{5}{9}(f-32),
\end{equation}
where $f$ is the temperature in Fahrenheit and $c$ is the temperature
in Celsius.

For this program, we'll need an \pythoninline{Entry} widget, an output
\pythoninline{Label} widget, a \pythoninline{Button} widget to do the
conversion and a \pythoninline{Button} widget to quit the application.
The program,
\pythoninline{tk\_converter1.py}, is shown below: 

\begin{minipage}{\textwidth}
\pythonFile{source/tk_converter1.py}
\end{minipage}

The function \pythoninline{convert} does the conversion. Notice how it
first gets the value from \pythoninline{self.fahr\_input} which is what the user
entered into the entry field. Then it does the conversion, and
finally, it changes the text on the output field by calling the
\pythoninline{configure} method. If you type in and run the above
program, you should get an application that looks like Figure
\ref{fig-convert1}. \footnote{Note that if the user enters something that
isn't a number an error will occur (actually an exception). To be
thorough, we should encase the conversion in a try-except block.
Unfortunately, we don't have the time to cover exceptions in COMP150.}
\begin{figure}[h]
\centering
\includegraphics[scale=1]{illustrations/Tk_FtoC.png}
\caption{Fahrenheit to Celsius Converter\label{fig-convert1}}.
\end{figure}

\section{Glossary}
\begin{description}
\item[Graphical User Interface (GUI):] a program interface using
graphical output and input is often taken directly from the keyboard
and mouse.
\item[Event Driven:] A programming style where the flow of control of
the program is determined by external events such as mouse-clicks,
keyboard presses, etc.
\item[Global variables:] Variables that can be accessed from anywhere
within a program. The use of global variables should be limited as
much as possible.
\item[Objects:] A special type of compound variable that has specific
functions (called methods) associated with that variable type.
\item[Object-Oriented Programming:] Programming in a language that
supports objects.
\item[Named Parameter:] When making a function call it is possible to
  explicitly name the parameters as in
  \pythoninline{read\_value\_type(prompt="Enter a float> ")}.
\item[Default Parameter:] A parameter which is assigned a default
  value in a function definition.
\item[Widget:] A GUI element such as a window, button, label, etc.
\item[Dialog:] A transient widget often used for alerting the user or
getting information from the user.
\end{description}

\newpage
\section{Laboratory exercises}
\begin{enumerate}
\item Add a function called \pythoninline{convert\_f\_to\_c} to
\pythoninline{tk\_converter1.py}. \pythoninline{convert\_f\_to\_c} should
take the temperature in Fahrenheit as input and return the temperature
in Celsius. It should pass the following doctests:
\begin{python}
    def convert_f_to_c(self, fahr):
        """
        Convert from fahrenheit to celsius. The variable self is the
        object parameter.
        Note: when comparing floating values, == is usually inadequate. You
        normally have to test whether two floats are close enough.
        >>> testConvert = App(Tk())
        >>> c = testConvert.convert_f_to_c(-40)
        >>> abs(c+40.0)<1e-8
        True
        >>> c = testConvert.convert_f_to_c(100)
        >>> abs(c-37.7777777777)<1e-8
        True
        """
\end{python}
Note that this is the correct way to test equality of real numbers,
you should never use a direct comparison. You can have the doctests
tested each time you run the program by including the following code
just before the call to \pythoninline{root.mainloop}:
\begin{python}
import doctest
doctest.testmod()
\end{python}

\item Modify the function \pythoninline{convert} so that it
makes use of the new function \pythoninline{convert\_f\_to\_c}.

\item Create a new function called \pythoninline{convert\_c\_to\_f} and
devise appropriate doctests to test it.

\item In the \pythoninline{convert} function, add a parameter called
\pythoninline{forwardConvert} which can be either \pythoninline{True}
or \pythoninline{False}. Modify  \pythoninline{convert} to use an
\pythoninline{if-else} statement to 
call \pythoninline{convert\_f\_to\_c} if \pythoninline{forwardConvert} is
\pythoninline{True} and \pythoninline{convert\_c\_to\_f} if
\pythoninline{forwardConvert} is \pythoninline{False}.

\item Add a \pythoninline{Checkbutton} button to your interface with the
following code:
\begin{python}
self.forward_button = Checkbutton(parent, text="do forward conversion", 
                                  command=self.toggle_convert)
self.forward_button.grid(column=0,row=3)
\end{python}
You will also need to add a method called
\pythoninline{toggle\_convert} that allows the program to keep track of
whether the converter should do a forward convert or a backwards
convert (do this using a boolean variable). Finally, modify the
\pythoninline{convert} method so that the appropriate conversion
function is used (either \pythoninline{convert\_f\_to\_c} or
\pythoninline{convert\_c\_to\_f}).

\item \emph{Extension Exercise:}
Extend your temperature converter so that other conversions are also
handled. E.g. miles to kilometres, pounds to kilograms, litres to
gallons etc. You'll need to look up the conversion equations on the
web. Also, this exercise requires a bit of creativity in how you
structure your interface. See if you can come up with several options
before deciding on a particular look. 

\item \emph{Extension Exercise:} Using the code from
  section~\ref{sec-user_input} as a starting point write a program
  which reads mathematical expressions from the user and displays a
  message box containing the result.

\end{enumerate}

\submitreminder