Read Programming Python Online

Authors: Mark Lutz

Tags: #COMPUTERS / Programming Languages / Python

Programming Python (76 page)

BOOK: Programming Python
4.02Mb size Format: txt, pdf, ePub
ads

Loading this file’s data into our GUI makes the dimensions of the
grid change accordingly—the class simply reruns its widget construction
logic after erasing all the old entry widgets with the
grid_forget
method. The
grid_forget
method unmaps gridded widgets and
so effectively erases them from the display. Also watch for the
pack_forget
widget and window
withdraw
methods used in the
after
event “alarm” examples of the next
section for other ways to erase and redraw GUI components.

Once the GUI is erased and redrawn for the new data,
Figure 9-39
captures the scene
after the file Load and a new Sum have been requested by the user in the
GUI.

Figure 9-39. Data file loaded, displayed, and summed

The
grid5-data2.txt
datafile has the same
dimensions but contains expressions in two of its columns, not just
simple numbers. Because this script converts input field values with the
Python
eval
built-in function, any
Python syntax will work in this table’s fields, as long as it can be
parsed and evaluated within the scope of the
onSum
method:

C:\...\PP4E\Gui\Tour\Grid>
type grid5-data2.txt
1 2 3 2*2 5 6
1 3-1 3 2<<1 5 6
1 5%3 3 pow(2,2) 5 6
1 2 3 2**2 5 6
1 2 3 [4,3][0] 5 6
1 {'a':2}['a'] 3 len('abcd') 5 6
1 abs(-2) 3 eval('2+2') 5 6

Summing these fields runs the Python code they contain, as seen in
Figure 9-40
. This can be
a powerful feature; imagine a full-blown spreadsheet grid, for
instance—field values could be Python code “snippets” that compute
values on the fly, call functions in modules, and even download current
stock quotes over the Internet with tools we’ll meet in the next part of
this book.

Figure 9-40. Python expressions in the data and table

It’s also a potentially
dangerous
tool—a
field might just contain an expression that erases your hard
drive!
[
36
]
If you’re not sure what expressions may do, either don’t
use
eval
(convert with more limited
built-in functions like
int
and
float
instead) or make sure your
Python is running in a process with restricted access permissions for
system components you don’t want to expose to the code you run.

Of course, this still is nowhere near a true spreadsheet program.
There are fixed column sums and file loads, for instance, but individual
cells cannot contain formulas based upon other cells. In the interest of
space, further mutations toward that goal are left as exercises.

I should also point out that there is more to gridding than we
have time to present fully here. For instance, by creating subframes
that have grids of their own, we can build up more sophisticated layouts
as component hierarchies in much the same way as nested frames arranged
with the packer. For now, let’s move on to one last widget survey
topic.

[
36
]
I debated showing this, but since understanding a danger is a
big part of avoiding it—if the Python process had permission to
delete files, passing the code string
__import__('os').system('rm –rf *')
to
eval
on Unix would delete all
files at and below the current directory by running a shell command
(and
'rmdir /S /Q .'
would have a
similar effect on Windows). Don’t do this! To see how this works in
a less devious and potentially useful way, type
__import__('math').pi
into one of the GUI
table’s cells—on Sum, the cell evaluates to pi (3.14159). Passing
"__import__('os').system('dir')"
to
eval
interactively proves the
point safely as well. All of this also applies to the
exec
built-in—
eval
runs expression strings and
exec
statements, but expressions are
statements (though not vice versa). A typical user of most GUIs is
unlikely to type this kind of code accidentally, of course,
especially if that user is always you, but be careful out
there!

Time Tools, Threads, and Animation

The last stop on our widget tour is perhaps the most unique. tkinter
also comes with a handful of tools that have to do with the event-driven
programming model, not graphics displayed on a computer screen.

Some GUI applications need to perform background activities
periodically. For example, to “blink” a widget’s appearance, we’d like to
register a callback handler to be invoked at regular time intervals.
Similarly, it’s not a good idea to let a long-running file operation block
other activity in a GUI; if the event loop could be forced to update
periodically, the GUI could remain responsive. tkinter comes with tools
for both scheduling such delayed actions and forcing screen
updates:

widget.after(
milliseconds,
function, *args
)

This tool
schedules the function to be called once by the GUI’s
event processing system after a number of milliseconds. This form of
the call does not pause the program—the callback function is
scheduled to be run later from the normal tkinter event loop, but
the calling program continues normally, and the GUI remains active
while the function call is pending. As also discussed in
Chapter 5
, unlike the
threading
module’s
Timer
object,
widget.after
events are dispatched in the
main GUI thread and so can freely update the GUI.

The
function
event handler argument
can be any callable Python object: a function, bound method, lambda
and so on. The
milliseconds
timer
duration argument is an integer which can be used to specify both
fractions and multiples of a second; its value divided by 1,000
gives equivalent seconds. Any
args
arguments are passed by position to
function
when it is later
called.

In practice, a
lambda
can
be used in place of individually-listed arguments to make the
association of arguments to function explicit, but that is not
required. When the function is a method, object state information
(attributes) might also provide its data instead of listed
arguments. The
after
method
returns an ID that can be passed to
after_cancel
to cancel the callback. Since
this method is so commonly used, I’ll say more about it by example
in a moment.

widget.after(
milliseconds
)

This tool pauses the calling program for a number of
milliseconds—for example, an argument of 5,000 pauses the caller for
5 seconds. This is essentially equivalent to Python’s library
function
time.sleep(
seconds
)
, and both calls can be used to add a
delay in time-sensitive displays (e.g., animation programs such as
PyDraw and the simpler examples ahead).

widget.after_idle(
function,
*args
)

This tool
schedules the function to be called once when there
are no more pending events to process. That is,
function
becomes an idle handler, which is
invoked when the GUI isn’t busy doing anything else.

widget.after_cancel(
id
)

This tool cancels a
pending
after
callback event before it occurs;
id
is
the return value of an
after
event scheduling call.

widget.update()

This tool forces tkinter
to process all pending events in the event queue,
including geometry resizing and widget updates and redraws. You can
call this periodically from a long-running callback handler to
refresh the screen and perform any updates to it that your handler
has already requested. If you don’t, your updates may not appear
on-screen until your callback handler exits. In fact, your display
may hang completely during long-running handlers if not manually
updated (and handlers are not run in threads, as described in the
next section); the window won’t even redraw itself until the handler
returns if covered and uncovered by another.

For instance, programs that animate by repeatedly moving an
object and pausing must call for an update before the end of the
animation or only the final object position will appear on-screen;
worse, the GUI will be completely inactive until the animation
callback returns (see the simple animation examples later in this
chapter, as well as PyDraw in
Chapter 11
).

widget.update_idletasks()

This tool
processes any pending idle events. This may sometimes
be safer than
after
, which has
the potential to set up race (looping) conditions in some scenarios.
Tk widgets use idle events to display themselves.

_tkinter.createfilehandler(
file,
mask, function
)

This tool
schedules the function to be called when a file’s
status changes. The function may be invoked when the file has data
for reading, is available for writing, or triggers an exception. The
file
argument is a Python file or
socket object (technically, anything with a
fileno()
method) or an integer file
descriptor;
mask
is
tkinter.READABLE
or
tkinter.WRITABLE
to specify the mode; and
the callback
function
takes two arguments—the file
ready to converse and a mask. File handlers are often used to
process pipes or sockets, since normal input/output requests can
block the caller.

Because this call is not available on Windows, it won’t be
used in this book. Since it’s currently a Unix-only alternative,
portable GUIs may be better off using
after
timer loops to poll for data and
spawning threads to read data and place it on queues if needed—see
Chapter 10
for more details. Threads
are a much more general solution to nonblocking data
transfers.

widget.wait_variable(var)
widget.wait_window(win)
widget.wait_visibility(win)

These tools
pause the caller until a tkinter variable changes its
value, a window is destroyed, or a window becomes visible. All of
these enter a local event loop, such that the application’s
mainloop
continues to handle events. Note
that
var
is a tkinter variable
object (discussed earlier), not a simple Python variable. To use for
modal dialogs, first call
widget.focus()
(to
set input focus) and
widget.grab()
(to make a window be the
only one active).

Although we’ll put some of these to work in examples, we won’t go
into exhaustive details on all of these tools here; see other Tk and
tkinter documentation for more information.

Using Threads with tkinter GUIs

Keep in mind
that for many programs, Python’s thread support that we
discussed in
Chapter 5
can serve some of
the same roles as the tkinter tools listed in the preceding section and
can even make use of them. For instance, to avoid blocking a GUI (and
its users) during a long-running file or socket transfer, the transfer
can simply be run in a spawned thread, while the rest of the program
continues to run normally. Similarly, GUIs that must watch for inputs on
pipes or sockets can do so in spawned threads or
after
callbacks, or some combination thereof,
without blocking the GUI itself.

If you do use threads in tkinter programs, however, you need to
remember that only the main thread (the one that built the GUI and
started the
mainloop
) should
generally make GUI calls. At the least, multiple threads should not
attempt to update the GUI at the same time. For example, the
update
method described in the preceding
section has historically caused problems in threaded GUIs—if a spawned
thread calls this method (or calls a method that does), it can sometimes
trigger very strange and even spectacular program crashes.

In fact, for a simple and more vivid example of the lack of thread
safety in tkinter GUIs, see and run the following files in the book
examples distribution package:

...\PP4E\Gui\Tour\threads-demoAll-frm.py
...\PP4E\Gui\Tour
threads-demoAll-win.py

These scripts are takeoffs of the prior chapter’s Examples
8-32
and
8-33
, which run the construction of four
GUI demo components in parallel threads. They also both crash
horrifically on Windows and require forced shutdown of the program.
While some GUI operations appear to be safe to perform in parallel
threads (e.g., see the canvas moves in
Example 9-32
), thread safety is
not guaranteed by tkinter in general. (For further proof of tkinter’s
lack of thread safety, see the discussion of threaded update loops in
the next chapter, just after
Example 10-28
; a thread there that
attempts to pop up a new window also makes the GUI fail
resoundingly.)

This GUI thread story is prone to change over time, but it imposes
a few structural constraints on programs. For example, because spawned
threads cannot usually perform GUI processing, they must generally
communicate with the main thread using global variables or shared
mutable objects such as queues, as required by the application. A
spawned thread which watches a socket for data, for instance, might
simply set global variables or append to shared queues, which in turn
triggers GUI changes in the main thread’s periodic
after
event callbacks. The main thread’s timer
events process the spawned thread’s results.

Although some GUI operations or toolkits may support multiple
threads better than others, GUI programs are generally best structured
as a main GUI thread and non-GUI “worker” threads this way, both to
avoid potential collisions and to finesse the thread safety issue
altogether. The PyMailGUI example later in the book, for instance, will
collect and dispatch callbacks produced by threads and stored on a
queue.

Also remember that irrespective of thread safety of the GUI
itself, threaded GUI programs must follow the same principles of
threaded programs in general—as we learned in
Chapter 5
, such programs must still synchronize
access to mutable state shared between threads, if it may be changed by
threads running in parallel. Although a
producer
/consumer thread model based upon
queues can alleviate many thread issues for the GUI itself, a program
that spawns non-GUI threads to update shared information used by the GUI
thread may still need to use thread locks to avoid concurrent update
issues.

We’ll explore GUI threading in more detail in
Chapter 10
, and we’ll meet more realistic
threaded GUI programs in
Part IV
,
especially in
Chapter 14
’s PyMailGUI. The
latter, for instance, runs long-running tasks in threads to avoid
blocking the GUI, but both restricts GUI updates to the main thread and
uses locks to prevent overlap of operations that may change shared
caches.

Using the after Method

Of all the event tools
in the preceding list, the
after
method may be the most interesting. It
allows scripts to schedule a callback handler to be run at some time in
the future. Though a simple device, we’ll use this often in later
examples in this book. For instance, in
Chapter 11
, we’ll meet a clock program that uses
after
to wake up 10 times per second
and check for a new time, and we’ll see an image slideshow program that
uses
after
to schedule the next photo
display (see PyClock and PyView). To illustrate the basics of scheduled
callbacks,
Example 9-27
does something a bit different.

Example 9-27. PP4E\Gui\Tour\alarm.py

# flash and beep every second using after() callback loop
from tkinter import *
class Alarm(Frame):
def __init__(self, msecs=1000): # default = 1 second
Frame.__init__(self)
self.msecs = msecs
self.pack()
stopper = Button(self, text='Stop the beeps!', command=self.quit)
stopper.pack()
stopper.config(bg='navy', fg='white', bd=8)
self.stopper = stopper
self.repeater()
def repeater(self): # on every N millisecs
self.bell() # beep now
self.stopper.flash() # flash button now
self.after(self.msecs, self.repeater) # reschedule handler
if __name__ == '__main__': Alarm(msecs=1000).mainloop()

This script builds the window in
Figure 9-41
and periodically calls both the
button widget’s
flash
method to make
the button flash momentarily (it alternates colors quickly) and the
tkinter
bell
method to call
your system’s sound interface. The
repeater
method
beeps and flashes once and schedules a callback to be
invoked after a specific amount of time with the
after
method.

Figure 9-41. Stop the beeps!

But
after
doesn’t pause the
caller: callbacks are scheduled to occur in the background, while the
program performs other processing—technically, as soon as the Tk event
loop is able to notice the time rollover. To make this work,
repeater
calls
after
each time through, to reschedule the
callback. Delayed events are one-shot callbacks; to repeat the event as
a loop, we need to reschedule it anew.

The net effect is that when this script runs, it starts beeping
and flashing once its one-button window pops up. And it keeps beeping
and flashing. And beeping. And flashing. Other activities and GUI
operations don’t affect it. Even if the window is iconified, the beeping
continues, because tkinter timer events fire in the background. You need
to kill the window or press the button to stop the alarm. By changing
the
msecs
delay, you can make this
beep as fast or as slow as your system allows (some platforms can’t beep
as fast as others…). This may or may not be the best demo to launch in a
crowded office, but at least you’ve been warned.

Hiding and redrawing widgets and windows

The button
flash
method
flashes the widget, but it’s easy to dynamically change
other appearance options of widgets, such as buttons, labels, and
text, with the widget
config
method. For instance, you can
also achieve a flash-like effect by manually reversing foreground and
background colors with the widget
config
method in scheduled
after
callbacks. Largely for fun,
Example 9-28
specializes the
alarm to go a step further.

Example 9-28. PP4E\Gui\Tour\alarm-hide.py

# customize to erase or show button on after() timer callbacks
from tkinter import *
import alarm
class Alarm(alarm.Alarm): # change alarm callback
def __init__(self, msecs=1000): # default = 1 second
self.shown = False
alarm.Alarm.__init__(self, msecs)
def repeater(self): # on every N millisecs
self.bell() # beep now
if self.shown:
self.stopper.pack_forget() # hide or erase button now
else: # or reverse colors, flash...
self.stopper.pack()
self.shown = not self.shown # toggle state for next time
self.after(self.msecs, self.repeater) # reschedule handler
if __name__ == '__main__': Alarm(msecs=500).mainloop()

When this script is run, the same window appears, but the button
is erased or redrawn on alternating timer events. The widget
pack_forget
method erases (unmaps) a drawn widget and
pack
makes it show up again;
grid_forget
and
grid
similarly hide and show widgets in a
grid. The
pack_forget
method is
useful for dynamically drawing and changing a running GUI. For
instance, you can be selective about which components are displayed,
and you can build widgets ahead of time and show them only as needed.
Here, it just means that users must press the button while it’s
displayed, or else the noise keeps going.

Example 9-29
goes
even further. There are a handful of methods for hiding and unhiding
entire top-level windows:

  • To hide and unhide the entire window instead of just one
    widget within it, use the
    top-level window widget
    withdraw
    and
    deiconify
    methods. The
    withdraw
    method, demonstrated in
    Example 9-29
    , completely
    erases the window and its icon (use
    iconify
    if you
    want the window’s icon to appear during a hide).

  • The
    lift
    method
    raises a window above all its siblings or relative
    to another you pass in. This method is also known as
    tkraise
    , but not
    raise
    —its name in Tk—because
    raise
    is a reserved word in
    Python.

  • The
    state
    method
    returns or changes the window’s current state—it
    accepts
    normal
    ,
    iconic
    ,
    zoomed
    (full screen), or
    withdrawn
    .

Experiment with these methods on your own to see how they
differ. They are also useful to pop up prebuilt dialog windows
dynamically, but are perhaps less practical here.

Example 9-29. PP4E\Gui\Tour\alarm-withdraw.py

# same, but hide or show entire window on after() timer callbacks
from tkinter import *
import alarm
class Alarm(alarm.Alarm):
def repeater(self): # on every N millisecs
self.bell() # beep now
if self.master.state() == 'normal': # is window displayed?
self.master.withdraw() # hide entire window, no icon
else: # iconify shrinks to an icon
self.master.deiconify() # else redraw entire window
self.master.lift() # and raise above others
self.after(self.msecs, self.repeater) # reschedule handler
if __name__ == '__main__': Alarm().mainloop() # master = default Tk root

This works the same, but the entire window appears or disappears
on beeps—you have to press it when it’s shown. You could add lots of
other effects to the alarm, and their timer-based callbacks technique
is widely applicable. Whether your buttons and windows should flash
and disappear, though, probably depends less on tkinter technology
than on your
users’ patience.

BOOK: Programming Python
4.02Mb size Format: txt, pdf, ePub
ads

Other books

delirifacient by trist black
Lottery Boy by Michael Byrne
Secret Magdalene by Longfellow, Ki
Paladin of Souls by Lois McMaster Bujold
The Fall of Rome by Beth Ciotta
Merline Lovelace by Countess In Buckskin
Tumbling Blocks by Earlene Fowler
Beside a Narrow Stream by Faith Martin
The Culture Code by Rapaille, Clotaire