Read Programming Python Online

Authors: Mark Lutz

Tags: #COMPUTERS / Programming Languages / Python

Programming Python (49 page)

BOOK: Programming Python
11.86Mb size Format: txt, pdf, ePub
ads
Climbing the GUI Learning Curve

On to the code; let’s start out by quickly stepping through a few
small examples that illustrate basic concepts and show the windows they
create on the computer display. The examples will become progressively
more sophisticated as we move along, but let’s get a handle on the
fundamentals first.

“Hello World” in Four Lines (or Less)

The usual first
example for GUI systems is to show how to display a “Hello
World” message in a window. As coded in
Example 7-1
, it’s just four lines in
Python.

Example 7-1. PP4E\Gui\Intro\gui1.py

from tkinter import Label                               # get a widget object
widget = Label(None, text='Hello GUI world!') # make one
widget.pack() # arrange it
widget.mainloop() # start event loop

This is a complete Python tkinter GUI program. When this script is
run, we get a simple window with a label in the middle; it looks like
Figure 7-1
on my Windows 7
laptop (I stretched some windows in this book horizontally to reveal
their window titles; your platform’s window system may vary).

Figure 7-1. “Hello World” (gui1) on Windows

This isn’t much to write home about yet, but notice that this is a
completely functional, independent window on the computer’s display. It
can be maximized to take up the entire screen, minimized to hide it in
the system bar, and resized. Click on the window’s “X” box in the top
right to kill the window and exit the program.

The script that builds this window is also fully portable. Run
this script on your machine to see how it renders. When this same file
is run on Linux it produces a similar window, but it behaves according
to the underlying Linux window manager. Even on the same operating
system, the same Python code might yields a different look-and-feel for
different window systems (for instance, under KDE and Gnome on Linux).
The same script file would look different still when run on Macintosh
and other Unix-like window managers. On all platforms, though, its basic
functional behavior will be the same.

tkinter Coding Basics

The
gui1
script is a
trivial example, but it illustrates steps common to most
tkinter programs. This Python code does the following:

  1. Loads a widget class from the
    tkinter
    module

  2. Makes an instance of the imported
    Label
    class

  3. Packs (arranges) the new
    Label
    in its parent widget

  4. Calls
    mainloop
    to bring up
    the window and start the tkinter event loop

The
mainloop
method
called last puts the label on the screen and enters a
tkinter wait state, which watches for user-generated GUI events. Within
the
mainloop
function, tkinter
internally monitors things such as the keyboard and mouse to detect
user-
generated
events. In fact,
the tkinter
mainloop
function is
similar in spirit to the following pseudo-Python code:

def mainloop():
while the main window has not been closed:
if an event has occurred:
run the associated event handler function

Because of this model, the
mainloop
call in
Example 7-1
never returns to our
script while the GUI is displayed
on-screen.
[
25
]
When we write larger scripts, the only way we can get
anything done after calling
mainloop
is to register callback handlers to respond to events.

This is called
event-driven programming
, and
it is perhaps one of the most unusual aspects of GUIs. GUI programs take
the form of a set of event handlers that share saved information rather
than of a single main control flow. We’ll see how this looks in terms of
real code in later examples.

Note that for code in a script file, you really need to do steps 3
and 4 in the preceding list to open this script’s GUI. To display a
GUI’s window at all, you need to call
mainloop
; to display widgets within the
window, they must be packed (or otherwise arranged) so that the tkinter
geometry manager knows about them. In fact, if you call either
mainloop
or
pack
without calling the other, your window
won’t show up as expected: a
mainloop
without a
pack
shows an empty window,
and a
pack
without a
mainloop
in a script shows nothing since the
script never enters an event wait state (try it). The
mainloop
call is sometimes optional when
you’re coding interactively, but you shouldn’t rely on this in
general.

Since the concepts illustrated by this simple script are at the
core of most tkinter programs, let’s take a deeper look at some of them
before moving on.

Making Widgets

When
widgets are constructed in tkinter, we can specify how
they should be configured. The
gui1
script passes two arguments to the
Label
class constructor:

  • The first is a parent-widget object, which we want the new
    label to be attached to. Here,
    None
    means “attach the new
    Label
    to the default top-level window of
    this program.” Later, we’ll pass real widgets in this position to
    attach our labels to other container objects.

  • The second is a configuration option for the
    Label
    , passed as a keyword argument: the
    text
    option specifies a text
    string to appear as the label’s message. Most widget constructors
    accept multiple keyword arguments for specifying a variety of
    options (color, size, callback handlers, and so on). Most widget
    configuration options have reasonable defaults per platform, though,
    and this accounts for much of tkinter’s simplicity. You need to set
    most options only if you wish to do something custom.

As we’ll see, the parent-widget argument is the hook we use to
build up complex GUIs as widget trees. tkinter works on a
“what-you-build-is-what-you-get” principle—we construct widget object
trees as models of what we want to see on the screen, and then ask the
tree to display itself by calling
mainloop
.

Geometry Managers

The
pack
widget
method called by the
gui1
script invokes the packer geometry
manager
, one of three ways to control how widgets are arranged in
a window. tkinter geometry managers simply arrange one or more widgets
within a container (sometimes called a parent or master). Both top-level
windows and frames (a special kind of widget we’ll meet later) can serve
as containers, and containers may be nested inside other containers to
build hierarchical displays.

The packer geometry manager uses constraint option settings to
automatically position
widgets in a window. Scripts supply higher-level
instructions (e.g., “attach this widget to the top of its container, and
stretch it to fill its space vertically”), not absolute pixel
coordinates. Because such constraints are so abstract, the packer
provides a powerful and easy-to-use layout system. In fact, you don’t
even have to specify constraints. If you don’t pass any arguments to
pack
, you get default packing, which
attaches the widget to the top side of its container.

We’ll visit the packer repeatedly in this chapter and use it in
many of the examples in this book. In
Chapter 9
, we will also meet an
alternative
grid
geometry manager—a
layout system that arranges widgets within a container in tabular form
(i.e., by rows and columns) and works well for input forms. A third
alternative, called the
placer
geometry manager
system, is described in Tk documentation but not in this book; it’s less
popular than the
pack
and
grid
managers and can be difficult to use for
larger GUIs coded by hand.

Running GUI Programs

Like all Python
code, the module in
Example 7-1
can be started in a number
of
ways—
by running it
as a top-level program file:

C:\...\PP4E\Gui\Intro>
python gui1.py

by importing it from a Python session or another module
file:

>>>
import gui1

by running it as a Unix executable if we add the special
#!
line at the top:

%
gui1.py &

and in any other way Python programs can be launched on your
platform. For instance, the script can also be run by clicking on the
file’s name in a Windows file explorer, and its code can be typed
interactively at
the
>>>
prompt.
[
26
]
It can even be run from a C program by calling the
appropriate embedding API function (see
Chapter 20
for details on C
integration).

In other words, there are really no special rules to follow when
launching GUI Python code. The tkinter interface (and Tk itself) is
linked into the Python interpreter. When a Python program calls GUI
functions, they’re simply passed to the embedded GUI system behind the
scenes. That makes it easy to write command-line tools that pop up
windows; they are run the same way as the purely text-based scripts we
studied in the prior part of this book.

Avoiding DOS consoles on Windows

In Chapters
3
and
6
we noted that if a program’s name ends in a
.pyw
extension rather than a
.py
extension, the Windows Python port does not
pop up a DOS console box to serve as its standard streams when the
file is launched by clicking its filename icon. Now that we’ve finally
started making windows of our own, that filename trick will start to
become even more useful.

If you just want to see the windows that your script makes no
matter how it is launched, be sure to name your GUI scripts with a
.pyw
if they might be run on Windows. For
instance, clicking on the file in
Example 7-2
in a Windows
explorer creates just the window in
Figure 7-1
.

Example 7-2. PP4E\Gui\Intro\gui1.pyw

...same as gui1.py...

You can also avoid the DOS pop up on Windows by running the
program with the
pythonw.exe
executable, not
python.exe
(in fact,
.pyw
files are simply registered to be opened by
pythonw
). On Linux, the
.pyw
doesn’t hurt, but it isn’t necessary; there
is no notion of a streams pop up on Unix-like machines. On the other
hand, if your GUI scripts might run on Windows in the future, adding
an extra “w” at the end of their names now might save porting effort
later. In this book,
.py
filenames are still sometimes
used to pop up console windows for viewing printed messages on
Windows.

[
25
]
Technically, the
mainloop
call returns to your script only after the tkinter event loop exits.
This normally happens when the GUI’s main window is closed, but it
may also occur in response to explicit
quit
method calls that terminate nested
event loops but leave open the GUI at large. You’ll see why this
matters in
Chapter 8
.

[
26
]
Tip: As suggested earlier, when typing tkinter GUI code
interactively
, you may or may not need to call
mainloop
to display widgets. This
is required in the current IDLE interface, but not from a simple
interactive session running in a system console window. In either
case, control will return to the interactive prompt when you kill
the window you created. Note that if you create an explicit
main-window widget by calling
Tk()
and attach widgets to it (described
later), you must call this again after killing the window;
otherwise, the application window will not exist.

tkinter Coding Alternatives

As you might
expect, there are a variety of ways to code the
gui1
example. For instance, if you want to make
all your tkinter imports more explicit in your script, grab the whole
module and prefix all of its names with the module’s name, as in
Example 7-3
.

Example 7-3. PP4E\Gui\Intro\gui1b.py—import versus from

import tkinter
widget = tkinter.Label(None, text='Hello GUI world!')
widget.pack()
widget.mainloop()

That will probably get tedious in realistic examples, though—tkinter
exports dozens of widget classes and constants that show up all over
Python GUI scripts. In fact, it is usually easier to use a
*
to import everything from the tkinter module
by name in one shot. This is demonstrated in
Example 7-4
.

Example 7-4. PP4E\Gui\Intro\gui1c.py—roots, sides, pack in place

from tkinter import *
root = Tk()
Label(root, text='Hello GUI world!').pack(side=TOP)
root.mainloop()

The tkinter module goes out of its way to export only what we really
need, so it’s one of the few for which the
*
import form is relatively safe to
apply.
[
27
]
The
TOP
constant in the
pack
call here, for instance, is one of
those many names exported by the
tkinter
module. It’s simply a variable name
(
TOP="top"
) preassigned in
constants
, a module automatically loaded by
tkinter
.

When widgets are packed, we can specify which side of their parent
they should be attached to—
TOP
,
BOTTOM
,
LEFT
, or
RIGHT
. If no
side
option is sent to
pack
(as in prior examples), a widget is
attached to its parent’s
TOP
by
default. In general, larger tkinter GUIs can be constructed as sets of
rectangles, attached to the appropriate sides of other, enclosing
rectangles. As we’ll see later, tkinter arranges widgets in a rectangle
according to both their packing order and their
side
attachment options. When widgets are
gridded, they are assigned row and column numbers instead. None of this
will become very meaningful, though, until we have more than one widget in
a window, so let’s move on.

Notice that this version calls the
pack
method right away after creating the label,
without assigning it a variable. If we don’t need to save a widget, we can
pack it in place like this to eliminate a statement. We’ll use this form
when a widget is attached to a larger structure and never again
referenced. This can be tricky if you assign the
pack
result, though, but I’ll postpone an
explanation of why until we’ve covered a few more basics.

We also use a
Tk
widget class
instance, instead of
None
, as the
parent here.
Tk
represents the main
(“root”) window of the program—the one that starts when the program does.
An automatically created
Tk
instance is
also used as the default parent widget, both when we don’t pass any parent
to other widget calls and when we pass the parent as
None
. In other words, widgets are simply
attached to the main program window by default. This script just makes
this default behavior explicit by making and passing the
Tk
object itself. In
Chapter 8
, we’ll see that
Toplevel
widgets are typically used to generate
new pop-up windows that operate independently of the program’s main
window.

In tkinter, some widget methods are exported as functions, and this
lets us shave
Example 7-5
to
just three lines of code.

Example 7-5. PP4E\Gui\Intro\gui1d.py—a minimal version

from tkinter import *
Label(text='Hello GUI world!').pack()
mainloop()

The tkinter
mainloop
can be called with or without a widget (i.e., as a function
or method). We didn’t pass
Label
a
parent argument in this version, either: it simply defaults to
None
when omitted (which in turn defaults to the
automatically created
Tk
object). But
relying on that default is less useful once we start building larger
displays. Things such as labels are more typically attached to other
widget
containers.

Widget Resizing Basics

Top-level windows,
such as the one built by all of the coding variants we
have seen thus far, can normally be resized by the user; simply drag out
the window with your mouse.
Figure 7-2
shows how
our window looks when it is expanded.

Figure 7-2. Expanding gui1

This isn’t very good—the label stays attached to the top of the
parent window instead of staying in the middle on expansion—but it’s
easy to improve on this with a pair of
pack
options, demonstrated in
Example 7-6
.

Example 7-6. PP4E\Gui\Intro\gui1e.py—expansion

from tkinter import *
Label(text='Hello GUI world!').pack(expand=YES, fill=BOTH)
mainloop()

When widgets are packed, we can specify whether a widget should
expand to take up all available space, and if so, how it should stretch
to fill that space. By default, widgets are not expanded when their
parent is. But in this script, the names
YES
and
BOTH
(imported from the tkinter module)
specify that the label should grow along with its parent, the main
window. It does so in
Figure 7-3
.

Figure 7-3. gui1e with widget resizing

Technically, the packer geometry manager
assigns a size to each widget in a display based on what
it contains (text string lengths, etc.). By default, a widget can occupy
only its allocated space and is no bigger than its assigned size. The
expand
and
fill
options let us be more specific about
such things:

expand=YES
option

Asks the packer to expand the allocated space for the widget
in general into any unclaimed space in the widget’s parent.

fill
option

Can be used to stretch the widget to occupy all of its
allocated space.

Combinations of these two options produce different layout and
resizing effects, some of which become meaningful only when there are
multiple widgets in a window. For example, using
expand
without
fill
centers the widget in the expanded space,
and the
fill
option can specify
vertical stretching only (
fill=Y
),
horizontal stretching only (
fill=X
),
or both (
fill=BOTH
). By providing
these constraints and attachment sides for all widgets in a GUI, along
with packing order, we can control the layout in fairly precise terms.
In later chapters, we’ll find that the
grid
geometry manager uses a different
resizing protocol entirely, but it provides similar control when
needed.

All of this can be confusing the first time you hear it, and we’ll
return to this later. But if you’re not sure what an
expand
and
fill
combination will do, simply try it
out—this is Python, after all. For now, remember that the combination of
expand=YES
and
fill=BOTH
is perhaps the most common setting;
it means “expand my space allocation to occupy all available space on my
side, and stretch me to fill the expanded space in both directions.” For
our “Hello World” example, the net result is that the label grows as the
window is expanded, and so is always
centered.

Configuring Widget Options and Window Titles

So far, we’ve been
telling tkinter what to display on our label by passing
its text as a keyword argument in label constructor calls. It turns out
that there are two other ways to specify widget configuration options.
In
Example 7-7
, the
text
option of the label is set after it is
constructed, by assigning to the widget’s
text
key. Widget objects overload (intercept)
index operations such that options are also available as mapping keys,
much like a dictionary.

Example 7-7. PP4E\Gui\Intro\gui1f.py—option keys

from tkinter import *
widget = Label()
widget['text'] = 'Hello GUI world!'
widget.pack(side=TOP)
mainloop()

More commonly, widget options can be set after construction by
calling the
widget
config
method,
as in
Example 7-8
.

Example 7-8. PP4E\Gui\Intro\gui1g.py—config and titles

from tkinter import *
root = Tk()
widget = Label(root)
widget.config(text='Hello GUI world!')
widget.pack(side=TOP, expand=YES, fill=BOTH)
root.title('gui1g.py')
root.mainloop()

The
config
method (which can
also be called by its synonym,
configure
) can be called at any time after
construction to change the appearance of a widget on the fly. For
instance, we could call this label’s
config
method again later in the script to
change the text that it displays; watch for such dynamic
reconfigurations in later examples in this part of the book.

Notice that this version also calls a
root.title
method; this call sets the label
that appears at the top of the window, as pictured in
Figure 7-4
. In general terms,
top-level windows like the
Tk root
here export window-manager interfaces—i.e., things that have to do with
the border around the window, not its contents.

Figure 7-4. gui1g with expansion and a window title

Just for fun, this version also centers the label upon resizes by
setting the
expand
and
fill
pack options. In fact, this version makes
just about everything explicit and is more representative of how labels
are often coded in full-blown interfaces; their parents, expansion
policies, and attachments are usually spelled out rather than
defaulted.

One More for Old Times’ Sake

Finally, if you are a minimalist
and you’re nostalgic for old Python coding styles, you can
also program this “Hello World” example as in
Example 7-9
.

Example 7-9. PP4E\Gui\Intro\gui1-old.py—dictionary calls

from tkinter import *
Label(None, {'text': 'Hello GUI world!', Pack: {'side': 'top'}}).mainloop()

This makes the window in just two lines, albeit arguably gruesome
ones! This scheme relies on an old coding style that was widely used
until Python 1.3, which passed
configuration options in a dictionary instead of keyword
arguments.
[
28
]
In this scheme, packer options can be sent as values of
the key
Pack
(a class in the
tkinter module).

The dictionary call scheme still works and you may see it in old
Python code, but it’s probably best to not do this in code you type. Use
keywords to pass options, and use explicit
pack
method calls in your tkinter scripts
instead. In fact, the only reason I didn’t cut this example completely
is that dictionaries can still be useful if you want to compute and pass
a set of options dynamically.

On the other hand, the
func(*pargs,
**kargs)
syntax now also allows you to pass an explicit
dictionary of keyword arguments in its third argument slot:

options = {'text': 'Hello GUI world!'}
layout = {'side': 'top'}
Label(None, **options).pack(**layout) # keyword must be strings

Even in dynamic scenarios where widget options are determined at
run time, there’s no compelling reason to ever use the pre-1.3 tkinter
dictionary call form.

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

Other books

Do Not Disturb by Stephanie Julian
A Paradox in Retrograde by Faherty, John
Primal Instincts by Susan Sizemore
Blood and Stone by Chris Collett
Voices of Dragons by Carrie Vaughn
Lost at Sea by Jon Ronson
Punishment by Linden MacIntyre


readsbookonline.com Copyright 2016 - 2024