Section 19.3. Tkinter Examples


19.3. Tkinter Examples

19.3.1. Label Widget

In Example 19.1, we present tkhello1.py , the Tkinter version of "Hello World!" In particular, it shows you how a Tkinter application is set up and highlights the Label widget.

Example 19.1. Label Widget Demo ( tkhello1.py )

Our first Tkinter example is ... what else? "Hello World!"In particular, we introduce our first widget, the Label .

1  #!/usr/bin/env python
2
3

import

Tkinter
4
5  top = Tkinter.Tk()
6  label = Tkinter.Label(top, text='Hello World!')
7  label.pack()
8  Tkinter.mainloop()

In the first line, we create our top-level window. That is followed by our Label widget containing the all-too-famous string. We instruct the packer to manage and display our widget, and finally call mainloop() to run our GUI application. Figure 19-1 shows what you will see when you run this GUI application.

Figure 19-1. Tkinter Label widget ( tkhello1.py )


19.3.2. Button Widget

The next example is pretty much the same as the first. However, instead of a simple text label, we will create a button instead. In Example 19-2 is the source code for tkhello2.py .

Example 19.2. Button Widget Demo ( tkhello2.py )

This example is exactly the same as tkhello1.py except that rather than using a Label widget, we create a Button widget.

1  #!/usr/bin/env python
2
3

import

Tkinter
4
5  top = Tkinter.Tk()
6  quit = Tkinter.Button(top, text='Hello World!',
7      command=top.quit)
8  quit.pack()
9  Tkinter.mainloop()

The first few lines are identical. Things differ only when we create the Button widget. Our button has one additional parameter, the Tkinter.quit() method. This installs a callback to our button so that if it is pressed (and released), the entire application will exit. The final two lines are the usual pack() and entering of the mainloop() . This simple button application is shown in Figure 19-2.

Figure 19-2. Tkinter Label widget ( tkhello1.py )


19.3.3. Label and Button Widgets

We combine tkhello1.py and tkhello2.py into tkhello3.py , a script that has both a label and a button. In addition, we are providing more parameters now than before when we were comfortable using all the default arguments that are automatically set for us. The source for tkhello3.py is given in Example 19.3.

Example 19.3. Label and Button Widget Demo ( tkhello3.py )

This example features both a Label and a Button widget. Rather than primarily using default arguments when creating the widget, we are able to specify more now that we know more about Button widgets and how to configure them.

1  #!/usr/bin/env python
2
3

import

Tkinter
4  top = Tkinter.Tk()
5
6  hello = Tkinter.Label(top, text='Hello World!')
7  hello.pack()
8
9  quit = Tkinter.Button(top, text='QUIT',
10     command=top.quit, bg='red', fg='white')
11 quit.pack(fill=Tkinter.X, expand=1)
12
13 Tkinter.mainloop()

Besides additional parameters for the widgets, we also see some arguments for the packer. The fill parameter tells the packer to let the QUIT button take up the rest of the horizontal real estate, and the expand parameter directs the packer to visually fill out the entire horizontal landscape, stretching the button to the left and right sides of the window.

As you can see in Figure 19-3, without any other instructions to the packer, the widgets are placed vertically (on top of each other). Horizontal placement requires creating a new Frame object with which to add the buttons. That frame will take the place of the parent object as a single child object (see the buttons in the listdir.py module, Example 19.6 in Section 19.3.6).

Figure 19-3. Tkinter Label and Button widgets ( tkhello3.py )


19.3.4. Label , Button , and Scale Widgets

Our final trivial example, tkhello4.py , involves the addition of a Scale widget. In particular, the Scale is used to interact with the Label widget. The Scale slider is a tool which controls the size of the text font in the Label widget. The greater the slider position, the larger the font, and the same goes for a lesser position, meaning a smaller font. The code for Example 19.4

Example 19.4. Label , Button , and Scale Demo ( tkhello4.py )

Our final introductory widget example introduces the Scale widget and highlights how widgets can "communicate" with each other using callbacks [such as resize() ]. The text in the Label widget is affected by actions taken on the Scale widget.

1  #!/usr/bin/env python
2
3

from

Tkinter

import

*
4
5

def

resize(ev=None):
6      label.config(font='Helvetica -%d bold' % \
7          scale.get())
8
9  top = Tk()
10 top.geometry('250x150')
11
12 label = Label(top, text='Hello World!',
13     font='Helvetica -12 bold')
14 label.pack(fill=Y, expand=1)
15
16 scale = Scale(top, from_=10, to=40,
17     orient=HORIZONTAL, command=resize)
18 scale.set(12)
19 scale.pack(fill=X, expand=1)
20
21 quit = Button(top, text='QUIT',
22     command=top.quit, activeforeground='white',
23     activebackground='red')
24 quit.pack()
25
26 mainloop()

{% if main.adsdop %}{% include 'adsenceinline.tpl' %}{% endif %}

New features of this script include a resize() callback function (lines 5-7), which is attached to the Scale . This is the code that is activated when the slider on the Scale is moved, resizing the size of the text in the Label .

We also define the size (250 x 150) of the top-level window (line 10). The final difference between this script and the first three is that we import the attributes from the Tkinter module into our namespace with " from Tkinter import *." Although not recommended because it "pollutes" your namespace, we do it here mainly because this application involves a great number of references to Tkinter attributes. This would require use of their fully qualified names for each and every attribute access. By using the undesired shortcut, we are able to access attributes with less typing and have code that is easier to read, at some cost.

As you can see from Figure 19-4, both the slider mechanism as well as the current set value show up in the main part of the window. Figure 19-4 shows the state of the GUI after the user moves the scale/slider to avalue of 36.

Figure 19-4. Tkinter Label, Button, and Scale widgets ( tkhello4.py )


As you can see from the code, the initial setting for the scale when the application starts is 12 (line 18).

19.3.5. Partial Function Application Example

Before looking a longer GUI application, we wanted to review the Partial Function Application (PFA) as introduced back in Section 11.7.3 of Chapter 11.

PFAs were added to Python in version 2.5 and are one piece in a series of significant improvements in functional programming.

PFAs allow you to "cache" function parameters by effectively "freezing" those predetermined arguments, and then at runtime, when you have the remaining arguments you need, you can thaw them out, send in the final arguments, and have that function called with all parameters.

Best of all, PFAs are not limited to just functions. They will work with any "callable," any object that has a functional interface just by using parentheses, i.e., classes, methods , or callable instances. The use of PFAs fits perfectly into a situation where there are many callables and many of the calls feature the same arguments over and over again.

GUI programming makes a great use case because there is good probability that you want some consistency in GUI widget look-and-feel, and this consistency comes about when the same parameters are used to create like objects. We are now going to present an application where multiple buttons will have the same foreground and background colors. It would be a waste of typing to give the same arguments to the same instantiators every time we wanted a slightly different button: the foreground and background colors are the same, but only the text is slightly different.

We are going to use traffic road signs as our example with our application attempts creating textual versions of road signs by dividing them up into various categories of sign types like critical, warning, or informational (just like logging levels). The type of the sign determines their color layout when the signs are created. For example, critical signs have the text in bright red with a white backdrop, warning signs are in black text on a goldenrod background, and informational or regulatory signs feature black text on a white background. We have the "Do Not Enter" and "Wrong Way" signs, which are both "critical," plus "Merging Traffic" and " Railroad Crossing ," both of which are warnings. Finally, we have the regulatory "Speed Limit" and "One Way" signs.

The application creates the "signs," which are just buttons. When users press the buttons, they just pop up the corresponding Tk dialog, critical/error, warning, or informational. It is not too exciting, but how the buttons are built is. You will find our application featured here in Example 19.5.

Example 19.5. Road Signs PFA GUI Application ( pfaGUI2.py )

Create road signs with the appropriate foreground and background colors based on sign type. Use PFAs to help "templatize" common GUI parameters.

1  #!/usr/bin/env python
2
3

from

functools

import

partial

as

pto
4

from

Tkinter

import

Tk, Button, X
5

from

tkMessageBox

import

showinfo, showwarning, showerror
6
7  WARN = 'warn'
8  CRIT = 'crit'
9  REGU = 'regu'
10
11 SIGNS = {
12    'do not enter': CRIT,
13    'railroad crossing': WARN,
14    '55\nspeed limit': REGU,
15    'wrong way': CRIT,
16    'merging traffic': WARN,
17    'one way': REGU,
18 }
19
20 critCB =

lambda

: showerror('Error', 'Error Button Pressed!')
21 warnCB =

lambda

: showwarning('Warning',
22    'Warning Button Pressed!')
23 infoCB =

lambda

: showinfo('Info', 'Info Button Pressed!')
24
25 top = Tk()
26 top.title('Road Signs')
27 Button(top, text='QUIT', command=top.quit,
28    bg='red', fg='white').pack()
29
30 MyButton = pto(Button, top)
31 CritButton = pto(MyButton, command=critCB, bg='white', fg='red')
32 WarnButton = pto(MyButton, command=warnCB, bg='goldenrod1')
33 ReguButton = pto(MyButton, command=infoCB, bg='white')
34
35

for

eachSign

in

SIGNS:
36   signType = SIGNS[eachSign]
37   cmd = '%sButton(text=%r%s).pack(fill=X, expand=True)' % (
38       signType.title(), eachSign,
39       '.upper()'

if

signType == CRIT

else

'.title()')
40   eval(cmd)
41
42 top.mainloop()

When you execute this application, you will get a GUI that will look something like Figure 19.5.

Figure 19-5. Road signs PFA GUI application on XDarwin in MacOS X ( pfaGUI2.py )


Line-by-Line Explanation
Lines 118

We begin our application by importing functional.partial() , a few Tkinter attributes, and the Tk dialogs (lines 1-5). Next, we define some signs along with their categories (lines 7-18).

Lines 2028

The Tk dialogs are assigned as button callbacks, which we will use for each button created (lines 20-23). We then launch Tk, set the title, and create a QUIT button (lines 25-28).

Lines 3033

These lines represent our PFA magic. We use two levels of PFA. The first templatizes the Button class and the root window top . What this does is that every time we call MyButton , it will call Button (Tkinter.Button() creates a button.) with top as its first argument. We have "frozen" this into MyButton .

The second level of PFA is where we use our first one, MyButton , and templatize that . We create separate button types for each of our sign categories. When users create a critical button CritButton (by calling it, e.g., CritButton()) , it will then call MyButton along with the appropriate button callback and background and foreground colors, which means calling Button with top , callback, and colors. Do you see how it unwinds and goes down the layers until at the very bottom, it has the call that you would have originally had to make if this feature did not exist yet? We repeat with Warn-Button and ReguButton .

Lines 3542

With the setup completed, we look at our list of signs and create them. We put together a Python evaluatable string consisting of the correct button name , pass in the button label as the text argument, and pack() it. If it is a critical sign, then we CAPITALIZE the button text, otherwise we titlecase it. This last bit is done in line 39, demonstrating another feature introduced in Python 2.5, the temporary operator. Then we take each button creation string and execute it with eval() , creating the buttons one at a time and resulting in the graphic seen previously. Finally, we start the GUI by entering the main event loop.

This application uses several Python 2.5 features, so you will not be able to run this with an older version.

19.3.6. Intermediate Tkinter Example

We conclude this section with a larger example, listdir.py . This application is a directory tree traversal tool. It starts in the current directory and provides a file listing. Double-clicking on any other directory in the list causes the tool to change to the new directory as well as replace the original file listing with the files from the new directory. The source code is given as Example 19.6.

Example 19.6. File System Traversal GUI ( listdir.py )

This slightly more advanced GUI expands on the use of widgets, adding listboxes, text entry fields, and scrollbars to our repertoire . There are also a good number of callbacks such as mouse clicks, key presses, and scrollbar action.

1  #!/usr/bin/env python
2
3

import

os
4

from

time

import

sleep
5

from

Tkinter

import

*
6
7

class

DirList(object):
8
9

def

__init__(self, initdir=None):
10         self.top = Tk()
11         self.label = Label(self.top,
12             text='Directory Lister v1.1')
13         self.label.pack()
14
15 self.cwd = StringVar(self.top)
16

17 self.dirl = Label(self.top, fg='blue',
18             font=('Helvetica', 12, 'bold'))
19         self.dirl.pack()
20
21         self.dirfm = Frame(self.top)
22         self.dirsb = Scrollbar(self.dirfm)
23         self.dirsb.pack(side=RIGHT, fill=Y)
24         self.dirs = Listbox(self.dirfm, height=15,
25             width=50, yscrollcommand=self.dirsb.set)
26         self.dirs.bind('<Double-1>', self.setDirAndGo)
27         self.dirsb.config(command=self.dirs.yview)
28         self.dirs.pack(side=LEFT, fill=BOTH)
29         self.dirfm.pack()
30
31         self.dirn = Entry(self.top, width=50,
32             textvariable=self.cwd)
33         self.dirn.bind('<Return>', self.doLS)
34         self.dirn.pack()
35
36         self.bfm = Frame(self.top)
37         self.clr = Button(self.bfm, text='Clear',
38              command=self.clrDir,
39              activeforeground='white',
40              activebackground='blue')
41         self.ls = Button(self.bfm,
42             text='List Directory',
43             command=self.doLS,
44             activeforeground='white',
45             activebackground='green')
46         self.quit = Button(self.bfm, text='Quit',
47             command=self.top.quit,
48             activeforeground='white',
49             activebackground='red')
50         self.clr.pack(side=LEFT)
51         self.ls.pack(side=LEFT)
52         self.quit.pack(side=LEFT)
53         self.bfm.pack()
54
55

if

initdir:
56            self.cwd.set(os.curdir)
57            self.doLS()
58
59

def

clrDir(self, ev=None):
60         self.cwd.set('')
61
62

def

setDirAndGo(self, ev=None):
63         self.last = self.cwd.get()
64         self.dirs.config(selectbackground='red')
65         check = self.dirs.get(self.dirs.curselection())
66

if not

check:

67                  check = os.curdir
68              self.cwd.set(check)
69              self.doLS()
70
71

def

doLS(self, ev=None):
72              error = ''
73              tdir = self.cwd.get()
74

if not

tdir: tdir = os.curdir
75
76

if not

os.path.exists(tdir):
77                  error = tdir + ': no such file'
78

elif not

os.path.isdir(tdir):
79                  error = tdir + ': not a directory'
80
81

if

error:
82                  self.cwd.set(error)
83                  self.top.update()
84                  sleep(2)
85

if not

(hasattr(self, 'last') \
86

and

self.last):
87                      self.last = os.curdir
88                  self.cwd.set(self.last)
89                  self.dirs.config(\
90                      selectbackground='LightSkyBlue')
91                  self.top.update()
92

return

93
94              self.cwd.set(\
95                  'FETCHING DIRECTORY CONTENTS...')
96              self.top.update()
97              dirlist = os.listdir(tdir)
98              dirlist.sort()
99              os.chdir(tdir)
100             self.dirl.config(text=os.getcwd())
101             self.dirs.delete(0, END)
102             self.dirs.insert(END, os.curdir)
103             self.dirs.insert(END, os.pardir)
104

for

eachFile

in

dirlist:
105                 self.dirs.insert(END, eachFile)
106             self.cwd.set(os.curdir)
107             self.dirs.config(\
108                 selectbackground='LightSkyBlue')
109
110

def

main():
111     d = DirList(os.curdir)
112     mainloop()
113
114

if

__name__ == '__main__':
115     main()

In Figure 19-6, we present what this GUI looks like in a Windows environment.

Figure 19-6. List directory GUI application in Windows ( listdir.py )


The Unix version of this application is given in Figure 19-7.

Figure 19-7. List directory GUI application in Unix ( listdir.py )


Line-by-Line Explanation
Lines 15

These first few lines contain the usual Unix startup line and importation of the os module, the time.sleep() function, and all attributes of the Tkinter module.

Lines 913

These lines define the constructor for the DirList class, an object that represents our application. The first Label we create contains the main title of the application and the version number.

Lines 1519

We declare a Tk variable named cwd to hold the name of the directory we are onwe will see where this comes in handy later. Another Label is created to display the name of the current directory.

Lines 2129

This section defines the core part of our GUI, (the Listbox) dirs , which contain the list of files of the directory that is being listed. A Scrollbar is employed to allow the user to move through a listing if the number of files exceeds the size of the Listbox . Both of these widgets are contained in a Frame widget. Listbox entries have a callback ( setDirAndGo ) tied to them using the Listbox bind() method.

Binding means to tie a keystroke, mouse action, or some other event to a callback to be executed when such an event is generated by the user. setDirAndGo() will be called if any item in the Listbox is doubleclicked. The Scrollbar is tied to the Listbox by calling the Scrollbar .config() method.

Lines 3134

We then create a text Entry field for the user to enter the name of the directory he or she wants to traverse and see its files listed in the Listbox . We add a RETURN or Enter key binding to this text entry field so that the user can hit RETURN as an alternative to pressing a button. The same applies for the mouse binding we saw above in the Listbox . When the user doubleclicks on a Listbox item, it has the same effect as the user's entering the directory name manually into the text Entry field and pressing the "go" button.

Lines 3653

We then define a Button frame ( bfm ) to hold our three buttons, a "clear" button ( clr ), a "go" button ( ls ), and a "quit" button ( quit ). Each button has its own different configuration and callbacks, if pressed.

Lines 5557

The final part of the constructor initializes the GUI program, starting with the current working directory.

Lines 5960

The clrDir() method clears the cwd Tk string variable, which contains the current directory that is "active." This variable is used to keep track of what directory we are in and, more important, helps keep track of the previous directory in case errors arise. You will notice the ev variables in the callback functions with a default value of None . Any such values would be passed in by the windowing system. They may or may not be used in your callback.

Lines 6269

The setDirAndGo() method sets the directory to traverse to and issues the call to the method that makes it all happen, doLS() .

Lines 71108

doLS() is, by far, the key to this entire GUI application. It performs all the safety checks (e.g., is the destination a directory and does it exist?). If there is an error, the last directory is reset to be the current directory. If all goes well, it calls os.listdir() to get the actual set of files and replaces the listing in the Listbox . While the background work is going on to pull in the new directory's information, the highlighted blue bar becomes bright red. When the new directory has been installed, it reverts to blue.

Lines 110115

The last pieces of code in listdir.py represent the main part of the code. main() is executed only if this script is invoked directly, and when main() runs, it creates the GUI application, then calls mainloop() to start the GUI, which is passed control of the application.

We leave all other aspects of the application as an exercise to the reader, recommending that it is easier to view the entire application as a combination of a set of widgets and functionality. If you see the individual pieces clearly, then the entire script will not appear as daunting.

We hope that we have given you a good introduction to GUI programming with Python and Tkinter. Remember that the best way to get familiar with Tkinter programming is by practicing and stealing a few examples! The Python distribution comes with a large number of demonstration applications that you can study.

If you download the source code, you will find Tkinter demo code in Lib/lib-tk, Lib/idlelib , and Demo/tkinter . If you have installed the Win32 version of Python and C:\Python2x , then you can get access to the demo code in Lib\lib-tk and Lib\idlelib . The latter directory contains the most significant sample Tkinter application: the IDLE IDE itself. For further reference, there are several books on Tk programming, one specifically on Tkinter.