Tkinter Programming (PDFDrive)
Tkinter Programming (PDFDrive)
Jan Bodnar
Preface iv
1 Introduction 1
1.1 Tkinter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Simple example . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.3 Centering a window . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4 Colours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.5 Fonts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.6 Styles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2 Layout management 15
2.1 Absolute positioning . . . . . . . . . . . . . . . . . . . . . . . . 15
2.2 Row of buttons . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.3 Corner buttons . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.4 Rows of buttons . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.5 New folder with pack . . . . . . . . . . . . . . . . . . . . . . . 23
2.6 Windows with pack . . . . . . . . . . . . . . . . . . . . . . . . 25
2.7 Calculator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.8 New folder with grid . . . . . . . . . . . . . . . . . . . . . . . . 30
2.9 Windows with grid . . . . . . . . . . . . . . . . . . . . . . . . . 32
3 Events 35
3.1 The command parameter . . . . . . . . . . . . . . . . . . . . . 35
3.2 Binding events . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.3 Unbinding events . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.4 Multiple event handlers . . . . . . . . . . . . . . . . . . . . . . 40
3.5 Event object . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.6 Event source . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.7 Binding widget class . . . . . . . . . . . . . . . . . . . . . . . . 45
3.8 Custom event . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.9 Protocols . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
3.10 Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.11 Floating window . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.12 Splash screen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.13 Notifications . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
i
CONTENTS ii
4 Widgets 64
4.1 Button . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.2 Label . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.3 Message . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
4.4 Separator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.5 Frame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.6 LabelFrame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.7 Checkbutton . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.8 Radiobutton . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
4.9 Entry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
4.10 Scale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.11 Spinbox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.12 OptionMenu . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.13 Combobox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.14 Scrollbar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
4.15 Notebook . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
4.16 PanedWindow . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
4.17 Progressbar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
6 Listbox 107
6.1 Item selection . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
6.2 Multiple selection . . . . . . . . . . . . . . . . . . . . . . . . . 109
6.3 Attaching a scrollbar . . . . . . . . . . . . . . . . . . . . . . . 111
6.4 Adding and removing items . . . . . . . . . . . . . . . . . . . . 113
6.5 Sorting items . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
6.6 Reordering items by dragging . . . . . . . . . . . . . . . . . . . 117
7 Text 121
7.1 Simple example . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
7.2 Fonts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
7.3 Selecting text . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
7.4 Image . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
7.5 Undo, redo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
7.6 Cut, copy, and paste . . . . . . . . . . . . . . . . . . . . . . . . 130
7.7 Searching text . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
7.8 Spell checking . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
7.9 Opening, saving files . . . . . . . . . . . . . . . . . . . . . . . . 138
8 Treeview 146
8.1 Simple example . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
8.2 Row colours . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
8.3 Hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
8.4 Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
CONTENTS iii
9 Canvas 171
9.1 Lines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
9.2 Line joins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
9.3 Line caps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
9.4 Cubic line . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
9.5 Colours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
9.6 Shapes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
9.7 Image . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
9.8 Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
9.9 Dragging items . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
9.10 Arkanoid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
Bibliography 191
Preface
1. Introduction
2. Layout management
3. Events
4. Widgets
7. Text
8. Treeview
9. Canvas
iv
About the author
v
Chapter 1
Introduction
1.1 Tkinter
Tkinter is a default Python GUI toolkit. It is a Python binding to Tk, which
is the original GUI library for the Tcl language. Tkinter is implemented as a
Python wrapper around a complete Tcl interpreter embedded in the Python
interpreter. Python’s tkinter module talks to Tk, and the Tk API in turn
interfaces with the underlying window system: Microsoft Windows, X Windows
on Unix or Linux, or Macintosh.
There are several other popular Python GUI toolkits. The most popular are
wxPython, PyQt, and PyGTK.1
"""
ZetCode Tkinter e-book
1
CHAPTER 1. INTRODUCTION 2
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Simple")
self.pack(fill=BOTH , expand=True)
root = Tk()
root.geometry("250 x150 +300+300")
app = Example(root)
root.mainloop ()
While this code is very small, the application window can do quite a lot. It can
be resized, maximized, or minimized. All the complexity that comes with it has
been hidden from the application programmer.
from tkinter import Tk , BOTH
from tkinter.ttk import Frame
Here we import Tk and Frame classes. The first class is used to create a root
window. The latter is a container for other widgets. The tkinter.ttk module
provides access to the Tk themed widget set.
class Example(Frame ):
Our Example class inherits from the Frame container widget. In the __init__()
constructor method we call the constructor of our inherited class.
self.parent = parent
We save a reference to the parent widget. The parent widget is the Tk root
window in our case.
self.initUI ()
The pack() method is one of the three geometry managers in Tkinter. It orga-
nizes widgets into horizontal and vertical boxes. Here we put the Frame widget,
accessed via the self attribute, to the Tk root window. It is expanded in both
directions. In other words, it takes the whole client space of the root window.
root = Tk()
The root window is created. The root window is a main application window in
Tkinter programs. It has a title bar and borders. These are provided by the
window manager. The root window must be created before any other widgets.
root.geometry("250 x150 +300+300")
The geometry() method sets the size for the window and positions it on the
screen. The first two parameters are the width and height of the window. The
last two parameters are x and y screen coordinates.
app = Example(root)
Finally, we enter the mainloop. The event handling starts from this point. The
mainloop receives events from the window system and dispatches them to the
application widgets. It is terminated when we click on the close button of the
titlebar or call the quit() method.
Figure 1.1 shows a small window created with the code example.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
self.centerWindow ()
def initUI(self ):
self.parent.title("Centered window")
self.pack(fill=BOTH , expand=True)
def centerWindow(self ):
w = 290
h = 150
sw = self.parent.winfo_screenwidth ()
sh = self.parent.winfo_screenheight ()
x = (sw - w)/2
y = (sh - h)/2
self.parent.geometry('%dx%d+%d+%d' % (w, h, x, y))
root = Tk()
ex = Example(root)
root.mainloop ()
We need to determine the size of the window and the size of the screen to
position a window in the center of the screen.
w = 290
CHAPTER 1. INTRODUCTION 5
h = 150
These are the width and height values of the application window.
sw = self.parent.winfo_screenwidth ()
sh = self.parent.winfo_screenheight ()
Finally, the geometry() method is used to place the window in the center of the
screen.
1.4 Colours
In computers, colours are represented by various colour models. The most
common are RGB and HSV models. Tkinter uses RGB model in which colours
are represented by combinations of red, green, and blue values. In addition,
Tkinter has built-in named colours.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
CHAPTER 1. INTRODUCTION 6
def initUI(self ):
self.parent.title("Colours")
self.pack(fill=BOTH , expand=True)
i = 0
for col in COLOURS:
lbl = Label(self , text=col , background=col)
lbl.grid(row=0, column=i)
i += 1
root = Tk()
ex = Example(root)
root.geometry("+300+300")
root.mainloop ()
In the COLOURS list, we specify nine colour values in RGB model. The red, green,
and blue parts of the colour value can be specified in four, eight, or twelve bits.
i = 0
for col in COLOURS:
lbl = Label(self , text=col , background=col)
lbl.grid(row=0, column=i)
i += 1
"""
ZetCode Tkinter e-book
COLOURS = ['snow ', 'ghost white ', 'white smoke ', 'gainsboro ',
'floral white ', 'old lace ', 'linen ', 'antique white ',
'papaya whip ', 'blanched almond ', 'bisque ', 'peach puff ',
'navajo white ', 'lemon chiffon ', 'mint cream ', 'azure ',
'alice blue ', 'lavender ', 'lavender blush ', 'misty rose ',
'dark slate gray ', 'dim gray ', 'slate gray ', 'light slate gray ',
'gray ', 'light grey ', 'midnight blue ', 'navy ', 'cornflower blue ',
'dark slate blue ', 'slate blue ', 'medium slate blue ',
'light slate blue ', 'medium blue ', 'royal blue ', 'blue ',
'dodger blue ', 'deep sky blue ', 'sky blue ', 'light sky blue ',
'steel blue ', 'light steel blue ', 'light blue ', 'powder blue ',
'pale turquoise ', 'dark turquoise ', 'medium turquoise ',
'turquoise ', 'cyan ', 'light cyan ', 'cadet blue ',
'medium aquamarine ', 'aquamarine ', 'dark green ',
'dark olive green ', 'dark sea green ', 'sea green ',
'medium sea green ', 'light sea green ', 'pale green ',
'spring green ', 'lawn green ', 'medium spring green ',
'green yellow ', 'lime green ', 'yellow green ', 'forest green ',
'olive drab ', 'dark khaki ', 'khaki ', 'pale goldenrod ',
'light goldenrod yellow ', 'light yellow ', 'yellow ', 'gold ',
'light goldenrod ', 'goldenrod ', 'dark goldenrod ', 'rosy brown ',
'indian red', 'saddle brown ', 'sandy brown ', 'dark salmon ',
'salmon ', 'light salmon ', 'orange ', 'dark orange ', 'coral ',
'light coral ', 'tomato ', 'orange red', 'red', 'hot pink ',
'deep pink ', 'pink ', 'light pink ', 'pale violet red', 'maroon ',
'medium violet red', 'violet red', 'medium orchid ', 'dark orchid ',
'dark violet ', 'blue violet ', 'purple ', 'medium purple ', 'thistle ',
'snow2 ', 'snow3 ', 'snow4 ', 'seashell2 ', 'seashell3 ', 'seashell4 ',
'AntiqueWhite1 ', 'AntiqueWhite2 ', 'AntiqueWhite3 ', 'AntiqueWhite4 ',
'bisque2 ', 'bisque3 ', 'bisque4 ', 'PeachPuff2 ', 'PeachPuff3 ',
'PeachPuff4 ', 'NavajoWhite2 ', 'NavajoWhite3 ', 'NavajoWhite4 ',
'LemonChiffon2 ', 'LemonChiffon3 ', 'LemonChiffon4 ', 'cornsilk2 ',
'cornsilk3 ', 'cornsilk4 ', 'ivory2 ', 'ivory3 ', 'ivory4 ',
'honeydew2 ', 'honeydew3 ', 'honeydew4 ', 'LavenderBlush2 ',
'LavenderBlush3 ', 'LavenderBlush4 ', 'MistyRose2 ', 'MistyRose3 ',
'MistyRose4 ', 'azure2 ', 'azure3 ', 'azure4 ', 'SlateBlue1 ',
'SlateBlue2 ', 'SlateBlue3 ', 'SlateBlue4 ', 'RoyalBlue1 ',
'RoyalBlue2 ', 'RoyalBlue3 ', 'RoyalBlue4 ', 'blue2 ', 'blue4 ',
'DodgerBlue2 ', 'DodgerBlue3 ', 'DodgerBlue4 ', 'SteelBlue1 ',
'SteelBlue2 ', 'SteelBlue3 ', 'SteelBlue4 ', 'DeepSkyBlue2 ',
'DeepSkyBlue3 ', 'DeepSkyBlue4 ', 'SkyBlue1 ', 'SkyBlue2 ', 'SkyBlue3 ',
'SkyBlue4 ', 'LightSkyBlue1 ', 'LightSkyBlue2 ', 'LightSkyBlue3 ',
'LightSkyBlue4 ', 'SlateGray1 ', 'SlateGray2 ', 'SlateGray3 ',
'SlateGray4 ', 'LightSteelBlue1 ', 'LightSteelBlue2 ',
'LightSteelBlue3 ', 'LightSteelBlue4 ', 'LightBlue1 ', 'LightBlue2 ',
'LightBlue3 ', 'LightBlue4 ', 'LightCyan2 ', 'LightCyan3 ',
'LightCyan4 ', 'PaleTurquoise1 ', 'PaleTurquoise2 ', 'PaleTurquoise3 ',
'PaleTurquoise4 ', 'CadetBlue1 ', 'CadetBlue2 ', 'CadetBlue3 ',
'CadetBlue4 ', 'turquoise1 ', 'turquoise2 ', 'turquoise3 ',
'turquoise4 ', 'cyan2 ', 'cyan3 ', 'cyan4 ', 'DarkSlateGray1 ',
'DarkSlateGray2 ', 'DarkSlateGray3 ', 'DarkSlateGray4 ',
'aquamarine2 ', 'aquamarine4 ', 'DarkSeaGreen1 ', 'DarkSeaGreen2 ',
CHAPTER 1. INTRODUCTION 8
class Example(Frame ):
self.parent = parent
self.initUI ()
CHAPTER 1. INTRODUCTION 9
def initUI(self ):
self.parent.title("Named colours")
self.pack(fill=BOTH , expand=True)
r = 0
c = 0
if r > 36:
r = 0
c += 1
root = Tk()
ex = Example(root)
root.geometry("+30+30")
root.mainloop ()
if r > 36:
r = 0
c += 1
1.5 Fonts
Tkinter has a tkinter.font module for working with fonts. It has some built-in
fonts such as TkTooltipFont, TkDefaultFont, or TkTextFont. The tkinter.font
module has a names() method to get the names of all defined fonts and a
families() method to retrieve all font families on the current platform.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Fonts")
self.pack(fill=BOTH , expand=True)
root = Tk()
ex = Example(root)
root.geometry("+300+300")
root.mainloop ()
A specific font is created with the Font class. If the font is not available on the
platform, Tkinter reverts to some default font.
label2 = Label(self , text=txt , font="TkTextFont")
label2.grid(row=1, column =0)
1.6 Styles
Tk 8.5 introduced widget styles; a new tkinter.ttk module is available. It
contains all widgets on which it is possible to apply styles. (Some widgets,
including Text, Listbox, Canvas, and Spinbox do not support styles yet.)
A style is the description of the appearance of a widget class. A widget class
is used by Tk to identify the type of a particular widget. Each widget class has
a default style. The default style name of a widget is ‘T’ prefixed to the widget
name; for example, TButton for a button widget, TLabel for a label widget, or
TRadioButton for a radio button. (The Treeview widget is an exception, its
default style is called Treeview.)
A theme is a complete look and feel, customizing the appearance of all the
widgets. Each theme comes with a predefined set of styles, but we can customize
the built-in styles or create our own new styles.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
CHAPTER 1. INTRODUCTION 13
self.parent.title("Default style")
self.pack(fill=BOTH , expand=True)
s = Style ()
s.configure('TLabel ', background='dark sea green ',
font =('Helvetica ', '16'))
lbl1.pack ()
lbl2.pack ()
root = Tk()
ex = Example(root)
root.geometry("250 x100 +300+300")
root.mainloop ()
In the example, we change the default style of the label widget class.
from tkinter.ttk import Frame , Label , Style
From the tkinter.ttk module, we import the Label widget and the Style class.
The Style class is used to work with Tkinter styles.
s = Style ()
We modify the TLabel style; it is a default style for all label widgets.
lbl1 = Label(self , text="zetcode.com")
lbl2 = Label(self , text="spoznaj.sk")
Two labels are created; both automatically have the modified style.
def initUI(self ):
self.parent.title("Custom styles")
self.pack(fill=BOTH , expand=True)
notebook = Notebook(self)
s = Style ()
s.configure('Tab1.TFrame ', background='midnight blue ')
s.configure('Tab2.TFrame ', background='lime green ')
s.configure('Tab3.TFrame ', background='khaki ')
A Notebook widget is created; it contains three frames in three tabs. Each frame
has a different background specified with a custom style.
s = Style ()
s.configure('Tab1.TFrame ', background='midnight blue ')
s.configure('Tab2.TFrame ', background='lime green ')
s.configure('Tab3.TFrame ', background='khaki ')
A Frame widget is created. The style option specifies the custom style used.
Figure 1.6 shows a Notebook widget with a frame having a lime green back-
ground. The colour was set with a custom style.
Chapter 2
Layout management
When we design the GUI of our application, we decide what widgets we will
use and how we will organize those widgets in the application. To organize our
widgets, we use specialized non-visible objects called layout managers.
There are two kinds of widgets: containers and their children. The containers
group children into suitable layouts.
Tkinter has three built-in layout managers: the place, pack, and grid man-
agers. The place geometry manager positions widgets in absolute coordinates.
The pack geometry manager organizes widgets in horizontal and vertical boxes.
It is a very simple manager; to create more complex layouts, we can combine
several frames each having some partial layout solved with the pack manager.
The grid geometry manager puts widgets in a two-dimensional grid. It is the
most complex and most capable layout manager in Tkinter.
It is possible to use the pack and the grid manager together, but each must
have its own parent.
"""
ZetCode Tkinter e-book
15
CHAPTER 2. LAYOUT MANAGEMENT 16
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Absolute positioning")
self.pack(fill=BOTH , expand=True)
style = Style ()
style.configure("TFrame", background="#333")
bard = Image.open("bardejov.jpg")
bardejov = ImageTk.PhotoImage(bard)
label1 = Label(self , image=bardejov)
label1.image = bardejov
label1.place(x=20, y=20)
rot = Image.open("rotunda.jpg")
rotunda = ImageTk.PhotoImage(rot)
label2 = Label(self , image=rotunda)
label2.image = rotunda
label2.place(x=40, y=160)
minc = Image.open("mincol.jpg")
mincol = ImageTk.PhotoImage(minc)
label3 = Label(self , image=mincol)
label3.image = mincol
label3.place(x=170, y=50)
root = Tk()
root.geometry("300 x280 +300+300")
app = Example(root)
root.mainloop ()
We use Image and ImageTk from the Python Imaging Library (PIL) module.
self.pack(fill=BOTH , expand=True)
This line makes the parent widget, the frame, take the whole area of its toplevel
window. The three labels with their images are placed on the frame.
style = Style ()
style.configure("TFrame", background="#333")
We create an image object and a photo image object from an image in the
current working directory.
label1 = Label(self , image=bardejov)
We must keep the reference to the image to prevent image from being garbage
collected.
label1.place(x=20, y=20)
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Row of buttons")
self.pack(fill=BOTH , expand=True)
root = Tk()
root.geometry("380 x120 +300+300")
app = Example(root)
root.mainloop ()
The side parameter of each button is set to LEFT; the buttons form a line. The
padx parameter puts some horizontal space between the buttons.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
CHAPTER 2. LAYOUT MANAGEMENT 20
def initUI(self ):
self.parent.title("Buttons")
self.pack(fill=BOTH , expand=True)
root = Tk()
root.geometry("300 x200 +300+300")
app = Example(root)
root.mainloop ()
The buttons are placed in the corner using pack’s side and anchor parameters.
closeButton.pack(anchor=S, side=RIGHT , padx=5, pady =5)
The Close button is anchored to the south with the anchor parameter and put
to the right with the side parameter.
Figure 2.3
Figure 2.3 shows two push buttons in the bottom-right corner of the window.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Rows of buttons")
self.pack(fill=BOTH , expand=True)
frame1 = Frame(self)
frame1.pack(padx=5, pady =5)
frame2 = Frame(self)
frame2.pack ()
root = Tk()
root.geometry("380 x120 +300+300")
app = Example(root)
root.mainloop ()
The example creates two rows of buttons. Each row is placed in a single frame
widget. The frames are placed on the base parent frame.
frame1 = Frame(self)
frame1.pack(padx=5, pady =5)
The first four buttons are organized within the first frame widget.
frame2 = Frame(self)
frame2.pack ()
Figure 2.4 shows two rows of buttons managed by two pack managers in two
frame widgets.
CHAPTER 2. LAYOUT MANAGEMENT 23
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("New folder")
self.pack(fill=BOTH , expand=True)
frame1 = Frame(self)
frame1.pack(fill=X)
entry = Entry(frame1)
entry.pack(side=LEFT , fill=X, padx=5, expand=True)
frame2 = Frame(self)
frame2.pack(fill=BOTH , expand=True)
frame3 = Frame(self)
frame3.pack(fill=X)
root = Tk()
root.geometry("330 x300 +300+300")
app = Example(root)
root.mainloop ()
To create the layout, we need four frames. Note that in addition to managing
the children inside their frames, we also manage the frames within its base
parent frame.
self.pack(fill=BOTH , expand=True)
The base frame occupies the whole area of the root window.
frame1 = Frame(self)
frame1.pack(fill=X)
entry = Entry(frame1)
entry.pack(side=LEFT , fill=X, padx=5, expand=True)
In the first frame we put the label and the entry. The combination of the fill
and the expand parameters makes the entry widget stretch horizontally.
frame2 = Frame(self)
frame2.pack(fill=BOTH , expand=True)
The second frame is occupied by the text widget. Both the frame and its child
fill their alotted area in both directions.
frame3 = Frame(self)
frame3.pack(fill=X)
The third frame contains two buttons. The buttons are placed at the right side
of their container.
CHAPTER 2. LAYOUT MANAGEMENT 25
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
CHAPTER 2. LAYOUT MANAGEMENT 26
def initUI(self ):
self.parent.title("Windows")
self.pack(fill=BOTH , expand=True)
frame1 = Frame(self)
frame1.pack(fill=X)
frame2 = Frame(self)
frame2.pack(fill=BOTH , expand=True)
frame22 = Frame(frame2)
frame22.pack(side=LEFT , fill=Y)
frame3 = Frame(self)
frame3.pack(fill=X)
root = Tk()
root.geometry("330 x300 +300+300")
app = Example(root)
root.mainloop ()
The first frame has one single label widget. It is positioned to the left and has
some spacing around its borders.
frame2 = Frame(self)
frame2.pack(fill=BOTH , expand=True)
The text widget goes into the left part of the second frame. It fills the bulk of
the layout’s area.
frame22 = Frame(frame2)
frame22.pack(side=LEFT , fill=Y)
The two buttons go into the right part of the central frame. They go to the top
of the container.
frame3 = Frame(self)
frame3.pack(fill=X)
Finally, two buttons are placed at the bottom frame. One is placed to the left
side of the container, the other one to the right side.
2.7 Calculator
In the following example, the grid geometry manager is used to create a simple
calculator layout.
CHAPTER 2. LAYOUT MANAGEMENT 28
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Calculator")
entry = Entry(self)
entry.grid(row=0, column=0, columnspan =4, sticky=W+E)
cls = Button(self , text="Cls")
cls.grid(row=1, column =0)
bck = Button(self , text="Back")
bck.grid(row=1, column =1)
lbl = Button(self)
lbl.grid(row=1, column =2)
clo = Button(self , text="Close")
clo.grid(row=1, column =3)
sev = Button(self , text="7")
sev.grid(row=2, column =0)
eig = Button(self , text="8")
eig.grid(row=2, column =1)
nin = Button(self , text="9")
CHAPTER 2. LAYOUT MANAGEMENT 29
self.pack ()
root = Tk()
app = Example(root)
root.mainloop ()
In the example, the grid manager organizes twenty buttons and one entry wid-
get.
Style (). configure("TButton", padding =(0, 5, 0, 5),
font='serif 10')
We configure the Button widget to have a specific font and to have some internal
padding.
self.columnconfigure (0, pad =3)
...
self.rowconfigure (0, pad =3)
The Entry widget is placed in the first row and column and it spans all four
columns. Widgets may not occupy all the space allotted by cells in the grid.
The sticky parameter expands the widget in a given direction. In our case we
ensure that the entry widget is expanded from left to right.
cls = Button(self , text="Cls")
cls.grid(row=1, column =0)
The Cls button is placed in the second row and first column. Note that the
rows and columns start from zero.
self.pack ()
The pack() method shows the frame widget and gives it initial size. If no other
parameters are given, the size will be just enough to show all children. This
method packs the frame widget to the toplevel root window, which is also a
container. The grid geometry manager is used to organize buttons inside the
frame widget.
"""
ZetCode Tkinter e-book
CHAPTER 2. LAYOUT MANAGEMENT 31
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("New folder")
self.pack(fill=BOTH , expand=True)
entry = Entry(self)
entry.grid(row=0, column=1, columnspan =4, padx=5, sticky=W+E)
root = Tk()
root.geometry("330 x300 +300+300")
app = Example(root)
root.mainloop ()
The layout consists of one label, one entry, one text widget, and two buttons.
self.columnconfigure (1, weight =1)
self.rowconfigure (1, weight =1)
CHAPTER 2. LAYOUT MANAGEMENT 32
These two lines make the second column and row take all the extra space. This
way the text widget occupies the bulk area of the window.
lbl = Label(self , text="Name:")
lbl.grid(row=0, column =0, padx =5)
The label widget is placed in the top-left cell of the grid. The padx option puts
some horizontal space after the label.
entry = Entry(self)
entry.grid(row=0, column =1, columnspan =4, padx=5, sticky=W+E)
The entry widget goes next to the label. With the columnspan option, we span
the cell in which the widget resides across four columns. The sticky parameter
makes the entry horizontally fill the enlarged cell.
txt = Text(self , width =20, height =10)
txt.grid(row=1, column =0, columnspan =5, padx=5, pady=5,
sticky=E+W+N+S)
The text widgets goes into the second row. It spans five columns and is stretched
within its cell in all sides. The stretching is done with the sticky parameter. The
width and height parameters specify the size of the text widget in characters,
measured according to the current font size. The weight option set previously
makes it grow and shrink horizontally and vertically.
okBtn = Button(self , text="OK")
okBtn.grid(row=3, column =3, sticky=E)
closeBtn = Button(self , text="Close")
closeBtn.grid(row=3, column =4, padx=5, sticky=E)
The two buttons go below the text widget, into the right side of the window.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Windows")
self.pack(fill=BOTH , expand=True)
area = Text(self)
area.grid(row=1, column =0, columnspan =2, rowspan =4,
padx=5, sticky=E+W+S+N)
root = Tk()
root.geometry("350 x300 +300+300")
app = Example(root)
root.mainloop ()
In this example, we use a label widget, a text widget, and four buttons.
self.columnconfigure (1, weight =1)
self.columnconfigure (3, pad =7)
self.rowconfigure (3, weight =1)
self.rowconfigure (5, pad =7)
We define some space among widgets in the grid. The weight parameter makes
the second column and fourth row growable. This row and column is occupied
CHAPTER 2. LAYOUT MANAGEMENT 34
The label widget is created and put into the grid. If no column and row is
specified, then the first column or row is assumed. The label sticks to the west
and it has some padding around its borders.
area = Text(self)
area.grid(row=1, column=0, columnspan =2, rowspan =4,
padx=5, sticky=E+W+S+N)
The text widget is created and starts from the second row and first column. It
spans two columns and four rows. There is a 4 px space between the widget
and the left border of the root window. Finally, the widget sticks to all the four
sides. So when the window is resized, the text widget grows in all directions.
abtn = Button(self , text="Activate")
abtn.grid(row=1, column =3)
These two buttons go below the text widget; the Help button takes the first
column, the Ok Button takes the last column.
Chapter 3
Events
"""
ZetCode Tkinter e-book
35
CHAPTER 3. EVENTS 36
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Commands")
self.pack(fill=BOTH , expand=True)
cb = Checkbutton(self , text="Checkbutton",
command=self.onButton2Click)
cb.pack(side=LEFT)
def onButton1Click(self ):
def onButton2Click(self ):
print("Checkbutton clicked")
root = Tk()
root.geometry("250 x150 +300+300")
app = Example(root)
root.mainloop ()
In the example, Button and Checkbutton widgets use the command parameter to
execute a function.
cb = Checkbutton(self , text="Checkbutton",
command=self.onButton2Click)
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
ret = messagebox.askquestion("Question",
"Are you sure to quit?", default=messagebox.NO)
if (ret == "yes"):
self.quit ()
else:
return
root = Tk()
root.geometry("300 x200 +300+300")
app = Example(root)
root.mainloop ()
In the example, we bind the Esc key to the quitApp(). The method shows a
confirmation dialog.
CHAPTER 3. EVENTS 38
The <Escape> event is triggered when the Esc key is pressed. The event is bound
to the quitApp() method.
def quitApp(self , e):
ret = messagebox.askquestion("Question",
"Are you sure to quit?", default=messagebox.NO)
if (ret == "yes"):
self.quit ()
else:
return
The quitApp() method shows a dialog with Yes and No buttons. Clicking the
Yes button the application is terminated with the quit() method.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.var = BooleanVar ()
cb = Checkbutton(self , text="Bind event", variable=self.var ,
command=lambda : self.onBind(btn))
cb.grid(row=0, column =1)
if (self.var.get() == True ):
w.bind("<Button -1>", self.onClick)
else:
w.unbind("<Button -1>")
print("clicked")
root = Tk()
root.geometry("300 x200 +300+300")
app = Example(root)
root.mainloop ()
There are two widgets in the example: a Button and a Checkbutton. The check
button binds and unbinds the <Button-1> event of the button.
cb = Checkbutton(self , text="Bind event", variable=self.var ,
command=lambda : self.onBind(btn))
if (self.var.get() == True ):
w.bind("<Button -1>", self.onClick)
else:
w.unbind("<Button -1>")
Depending on the state of the check button, we bind or unbind the <Button-1>
event.
CHAPTER 3. EVENTS 40
Figure 3.1 shows a selected check box. Clicking on the button prints a message
to the terminal.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
CHAPTER 3. EVENTS 41
self.parent.title("Button")
self.pack(fill=BOTH , expand=True)
root = Tk()
root.geometry("300 x200 +300+300")
app = Example(root)
root.mainloop ()
Second event handler is bound to the <Button-1> event. Both methods are called
when the button is pressed.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
x = 0
y = 0
x = e.x
y = e.y
self.updateLabel(x, y)
root = Tk()
root.geometry("300 x200 +300+300")
app = Example(root)
root.mainloop ()
x = e.x
y = e.y
self.updateLabel(x, y)
The second parameter of the onMotion() method is the event object. We deter-
mine the x and y coordinates and pass them to the updateLabel() method. The
coordinates track the distance of the mouse pointer from the upper-left corner
of the application window.
Figure 3.2 shows the coordinates of the mouse pointer in the upper-left corner
of the window. The coordinates are relative to the application window.
"""
ZetCode Tkinter e-book
"""
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Event source")
self.pack(fill=BOTH , expand=True)
s = Style ()
s.configure("Stat.TLabel", padding =5)
self.svar = StringVar ()
w = e.widget
if (w._name == "okbtn"):
self.svar.set("OK button clicked")
elif (w._name == "applybtn"):
self.svar.set("Apply button clicked")
root = Tk()
root.geometry("300 x200 +300+300")
app = Example(root)
root.mainloop ()
In the example, we have two buttons and a label. The label serves as a statusbar.
Both buttons launch the same event handler. Inside the handler, we identify
which button was pressed.
okBtn = Button(self , text="OK", name="okbtn")
With the name parameter, we give a widget a name. It is used to identify the
event source.
okBtn.bind("<Button -1>", self.onClick)
...
applyBtn.bind("<Button -1>", self.onClick)
w = e.widget
...
Figure 3.3 shows a message in the statusbar. It says that the Apply button is
the source of the event.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Class bind")
self.pack(fill=BOTH , expand=True)
print(e.widget)
def onButton1Click(self ):
def onButton2Click(self ):
def onButton3Click(self ):
root = Tk()
root.geometry("300 x200 +300+300")
app = Example(root)
root.mainloop ()
The bind_class() method binds the TButton class to the onButtonsClick() event
handler.
"""
ZetCode Tkinter e-book
def __init__(self ):
CHAPTER 3. EVENTS 48
self.r = 0
def generate(self ):
def getRandom(self ):
return self.r
class Example(Frame ):
self.parent = parent
self.mr = MyRandom ()
self.initUI ()
def initUI(self ):
self.parent.title("Custom event")
self.pack(fill=BOTH , expand=True)
self.lvar = StringVar ()
def onClick(self ):
self.mr.generate ()
self.event_generate('<<Random >>')
self.lvar.set(self.mr.getRandom ())
root = Tk()
root.geometry("300 x200 +300+300")
app = Example(root)
root.mainloop ()
The example has a button which generates a random number. Each time a num-
ber is generated, a custom event called <<Random>> is triggered. The <<Random>>
CHAPTER 3. EVENTS 49
event is bound to the updateLabel() method, which displays the number in the
label widget.
class MyRandom( object ):
def __init__(self ):
self.r = 0
def generate(self ):
def getRandom(self ):
return self.r
The MyRandom object generates a random number. Its getRandom() method re-
turns the generated random number.
self.parent.bind("<<Random >>", self.updateLabel)
self.mr.generate ()
self.event_generate('<<Random >>')
self.lvar.set(self.mr.getRandom ())
In the updateLabel() method, we retrieve the random number with the help of
the getRandom() method and set it to the label’s control variable.
3.9 Protocols
Protocols are interactions between the application and the window manager.
The WM_DELETE_WINDOW protocol defines what happens when the user explicitly
closes a window using the window manager. The protocol() method installs a
handler for the given protocol.
"""
ZetCode Tkinter e-book
1 stackoverflow.com/a/23195921/2008247
CHAPTER 3. EVENTS 50
class Example(Frame ):
self.parent = parent
self.parent.protocol("WM_DELETE_WINDOW", self.onDeleteWindow)
self.initUI ()
def initUI(self ):
self.parent.title("Protocol")
self.pack(fill=BOTH , expand=True)
def onDeleteWindow(self ):
ret = messagebox.askquestion(title="Question",
message="Are you sure to quit?", default=messagebox.NO)
if (ret == "yes"):
self.quit ()
else:
return
root = Tk()
root.geometry("300 x200 +300+300")
app = Example(root)
root.mainloop ()
3.10 Animation
The after() method registers a callback that is invoked after given delay. The
delay is expressed in milliseconds. The method can be used to create animations.
"""
ZetCode Tkinter e-book
DELAY = 850
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Animation")
self.pack(fill=BOTH , expand=True)
self.i = 0
self.img_names = ("one.png", "two.png", "three.png",
"four.png", "five.png", "six.png", "seven.png",
"eight.png", "nine.png")
img = Image.open(self.img_names[self.i])
num = ImageTk.PhotoImage(img)
self.label = Label(self , image=num)
self.label.pack(pady =30)
self.after(DELAY , self.doCycle)
def doCycle(self ):
self.i += 1
return
img = ImageTk.PhotoImage(Image.open(self.img_names[self.i]))
self.label.configure(image=img)
self.label.image = img
self.after(DELAY , self.doCycle)
root = Tk()
root.geometry("300 x200 +300+300")
app = Example(root)
root.mainloop ()
self.i += 1
img = ImageTk.PhotoImage(Image.open(self.img_names[self.i]))
self.label.configure(image=img)
self.label.image = img
...
The after() does not repeatedly call its callback; therefore, we need to register
a new callback inside the callback.
"""
ZetCode Tkinter e-book
BITMAP = """
#define grip_width 15
#define grip_height 29
static unsigned char grip_bits [] = {
0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 ,
0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 ,
0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 ,
CHAPTER 3. EVENTS 54
0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 ,
0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 ,
0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 };
"""
class Example(Toplevel ):
self.initUI ()
def initUI(self ):
self.overrideredirect (True)
bitmap = BitmapImage(data=BITMAP)
self.grip.pack(side="left", fill="y")
self.label.pack(side="right", fill="both", padx=3, expand=True)
self.geometry("+300+300")
self.x = e.x
self.y = e.y
e.x = None
e.y = None
self.quit ()
CHAPTER 3. EVENTS 55
root = Tk()
app = Example(root)
root.withdraw ()
root.mainloop ()
The example shows a toplevel window without a titlebar. The window is moved
by its grip located in the left corner. We move it by left clicking on the grip
and moving the mouse. A right click closes the window.
BITMAP = """
#define grip_width 15
#define grip_height 29
static unsigned char grip_bits [] = {
0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 ,
0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 ,
0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 ,
0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 ,
0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 ,
0x00 , 0x00 , 0x55 , 0x55 , 0x00 , 0x00 , 0x55 , 0x55 };
"""
A grip is a bitmap image. This bitmap is a plain text binary image format.
self.overrideredirect(True)
The toplevel window contains two labels: one label holds a bitmap and the other
one a text message.
self.grip.bind("<ButtonPress -1>", self.startMove)
self.grip.bind("<ButtonRelease -1>", self.stopMove)
self.grip.bind("<B1 -Motion >", self.onMotion)
self.x = e.x
self.y = e.y
The withdraw() method removes the root window from the screen without de-
stroying it.
Figure 3.5 shows how the floating window looks like. The left part of the window
is the grip which is used to move the window.
"""
ZetCode Tkinter e-book
class MySplash(Toplevel ):
self.delay = 3000
self.parent = parent
self.initUI ()
def initUI(self ):
self.splash_image = PhotoImage(file="splash.png")
w = self.splash_image.width ()
h = self.splash_image.height ()
self.overrideredirect (True)
x = (self.parent.winfo_screenwidth () - w) / 2
y = (self.parent.winfo_screenheight () - h) / 2
self.geometry('{0}x{1}+{2}+{3} '.format(w, h, int(x), int(y)))
self.after(self.delay , self.close)
def close(self ):
self.parent.build_app ()
self.destroy ()
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.pack(fill=BOTH , expand=True)
self.parent.title('Application ')
self.parent.withdraw ()
self.showSplashScreen ()
def build_app(self ):
self.parent.deiconify ()
def showSplashScreen(self ):
MySplash(self)
CHAPTER 3. EVENTS 58
root = Tk()
root.geometry('250 x200 +300+300 ')
app = Example(root)
root.mainloop ()
A splash screen contains a PNG image which is displayed before the main ap-
plication window.
class MySplash(Toplevel ):
self.delay = 3000
self.parent = parent
self.initUI ()
...
self.splash_image = PhotoImage(file="splash.png")
w = self.splash_image.width ()
h = self.splash_image.height ()
...
With the after() method, we create a single-shot timer. After delay ms, we
call the close() method.
def close(self ):
self.parent.build_app ()
self.destroy ()
Inside the close() method, we call the parent’s build_app() method and destroy
the splash window.
self.parent.withdraw ()
self.showSplashScreen ()
The root window is displayed first. With the withdraw() method we remove it
from the screen and create and show the splash screen window.
def build_app(self ):
self.parent.deiconify ()
At the end of the splash screen’s lifetime, the build_app() method is invoked.
The GUI of the main application is built. The deiconify() method shows the
main window, removed with the withdraw() method, back on the screen.
3.13 Notifications
The following program shows small notification windows. It works with these
two events: <Button-1> and <ButtonRelease-1>.
"""
ZetCode Tkinter e-book
class NotifyWindow(Toplevel ):
self.overrideredirect (True)
self.el = elapsed
self.screen_x = screen_x
self.screen_y = screen_y
self.initUI ()
def initUI(self ):
s = Style ()
s.configure('TLabel ', background='sky blue ')
self.after(self.el , self.destroyWin)
def destroyWin(self ):
self.destroy ()
class Example(Frame ):
self.parent = parent
self.initUI ()
CHAPTER 3. EVENTS 61
def initUI(self ):
self.parent.title("Notify")
self.pack(fill=BOTH , expand=True)
self.start = time.time ()
screen_x = e.x_root
screen_y = e.y_root
self.stop = time.time ()
el = self.stop - self.start
el_ms = el * 1000
el_r = round(el_ms)
self.createNotificationWindow (el_r , screen_x , screen_y)
root = Tk()
root.geometry("350 x250 +300+300")
app = Example(root)
root.mainloop ()
The program tracks the time between button press and button release events.
The elapsed time is shown in a small notification window. The window is
displayed just above the mouse pointer and its duration reflects the time of
a mouse click. (A mouse click consists of a mouse press and mouse release
actions.)
class NotifyWindow(Toplevel ):
self.overrideredirect (True)
self.el = elapsed
self.screen_x = screen_x
self.screen_y = screen_y
CHAPTER 3. EVENTS 62
self.initUI ()
...
With the winfo_height() method we get the height of the notification window.
Since the method is called in its constructor, the object creation is not yet
finished and we do not have the dimensions set. Therefore, we call the update()
method before calling the winfo_height() method.
self.geometry('+{0}+{1} '.format(self.screen_x ,
self.screen_y -h))
This line positions the notification window just above the mouse pointer.
self.after(self.el , self.destroyWin)
After some delay, we call the destroyWin() method, which destroys the notifica-
tion window. The delay is equal to the duration of a mouse click that triggered
the notification window.
self.bind("<Button -1>", self.onPress)
self.bind("<ButtonRelease -1>", self.onRelease)
In the main application, we listen to mouse press and mouse release events.
def onPress(self , e):
self.start = time.time ()
In the onPress() method, we store the start time of the mouse click.
def onRelease(self , e):
screen_x = e.x_root
screen_y = e.y_root
self.stop = time.time ()
el = self.stop - self.start
el_ms = el * 1000
el_r = round(el_ms)
self. createNotificationWindow (el_r , screen_x , screen_y)
In the onRelease() method, we compute the elapsed time between a mouse press
and a mouse release. We also get the x and y screen coordinates of a mouse
pointer. These values are passed to the createNotificationWindow() method,
CHAPTER 3. EVENTS 63
Figure 3.7 shows small, blue notifications which display the elapsed time of
recent mouse clicks.
Chapter 4
Widgets
Widgets are basic building blocks of a GUI application. Over the years, several
widgets became a standard in all toolkits on all OS platforms; for example a
button, a check box, or a scroll bar. Some of them might have different names.
For instance, a check box is called a check button in Tkinter. Tkinter has a
small set of widgets which cover basic programming needs.
4.1 Button
Button is a rectangular widget used to perform an action. The action is triggered
by a mouse click. Button displays text or an image.
def initUI(self ):
self.parent.title("Button")
self.pack(fill=BOTH , expand =1)
In the example we show one button on the window. Clicking on the button
terminates the applications.
quitButton = Button(self , text="Quit",
command=self.quit)
A Button widget is created. The text option specifies the string displayed by
the button. The command option points to the function or method which is
invoked when the button is pressed. The quit() method ends the application’s
mainloop.
64
CHAPTER 4. WIDGETS 65
Figure 4.1 shows a Button widget in the top-left area of the window.
4.2 Label
Label is a standard widget used to display a text or image.
def initUI(self ):
self.parent.title("Label")
self.pack(fill=BOTH , expand =1)
self.label = Label(self ,
text="This is the end of the world as we know it.")
self.label.pack(pady =15)
An instance of a Label widget is created. The text option specifies the text to
be displayed by the label.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Label")
self.img = Image.open("tatras.jpg")
tatras = ImageTk.PhotoImage(self.img)
label = Label(self , image=tatras)
label.pack ()
self.pack ()
def setGeometry(self ):
w, h = self.img.size
self.parent.geometry (("%dx%d+300+300") % (w, h))
root = Tk()
ex = Example(root)
ex.setGeometry ()
root.mainloop ()
By default, the Label widget can display only a limited set of image types. To
display a JPG image, we must use the Python Imaging Library (PIL) module.
self.img = Image.open("tatras.jpg")
tatras = ImageTk.PhotoImage(self.img)
We create an image from the image file in the current working directory. Later
we create a photo image from the image.
label = Label(self , image=tatras)
w, h = self.img.size
self.parent.geometry (("%dx%d+300+300") % (w, h))
4.3 Message
Message is a variant of the Label, designed to display multiline messages.
def initUI(self ):
self.parent.title("Message")
self.pack(fill=BOTH , expand =1)
The example shows a quote from Brian Tracy on the window. The text is
displayed using bigger italic font.
msg = Message(self , text=quote)
With the config() method, we change the background colour and set a specific
font for the text.
4.4 Separator
Separator is a horizontal or vertical bar that separates other widgets.
def initUI(self ):
self.parent.title("Separator")
self.pack(fill=BOTH , expand =1)
CHAPTER 4. WIDGETS 69
4.5 Frame
Frame is a basic container widget. It serves as a parent widget and is used mainly
to group other widgets into more complex layouts.
def initUI(self ):
self.style = Style ()
self.style.theme_use("alt")
self.parent.title("Frames")
In the example, we show four frames. Each of them has a different decoration.
self.style = Style ()
self.style.theme_use("alt")
The default theme does not support all reliefs; therefore, we choose the alt
theme which does.
self.grid(padx=5, pady =5)
A Frame widget is created. The relief is set to SUNKEN. The frames are empty
by default and in order to visually separate them, we choose different reliefs.
frame1.grid(row=0, column=0, padx=5, pady =5)
The first frame is set to the first row and column of the grid manager.
CHAPTER 4. WIDGETS 71
4.6 LabelFrame
LabelFrame is a frame widget which draws a border around its child widgets. It
is used to group a number of related widgets.
def initUI(self ):
self.parent.title("LabelFrame")
self.pack(fill=BOTH , expand=True)
In the example, there are two labels and two entry widgets inside a LabelFrame.
labFrame = LabelFrame(self , text="Personal info")
A LabelFrame widget is created. Its text option specifies the description of the
frame.
nameEntry = Entry(labFrame)
CHAPTER 4. WIDGETS 72
4.7 Checkbutton
Checkbutton is a widget that has two states: on and off. It is used to denote
some boolean property. The Checkbutton widget provides a check box with a
text label. Each Checkbutton widget should be associated with a variable.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Checkbutton")
self.pack(fill=BOTH , expand=True)
CHAPTER 4. WIDGETS 73
self.var = BooleanVar ()
def onClick(self ):
if self.var.get() == True:
self.master.title("Checkbutton")
else:
self.master.title("")
root = Tk()
root.geometry("250 x150 +300+300")
app = Example(root)
root.mainloop ()
A single Checkbutton is placed on the window. It shows or hides the title of the
window.
self.var = BooleanVar ()
self.var.set(True)
The widget is located on the window with the place() method at the given
coordinates.
CHAPTER 4. WIDGETS 74
Figure 4.8 shows a selected Checkbutton. The title of the window is visible
accordingly.
4.8 Radiobutton
Radiobutton allows the user to select a value from a mutually exclusive set of
options.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Radiobutton")
self.pack(fill=BOTH , expand=True)
self.rvar = IntVar ()
self.lvar = StringVar ()
self.lvar.set("...")
lbl = Label(self , textvariable=self.lvar)
lbl.pack(pady =35, anchor="s")
def onRadioSelect(self ):
val = self.rvar.get()
if (val == 1):
self.lvar.set("Male")
else:
self.lvar.set("Female")
root = Tk()
ex = Example(root)
root.geometry("300 x200 +300+300")
root.mainloop ()
In the example we have two Radiobutton widgets. The text of the selected radio
button is displayed in a label widget.
self.rvar = IntVar ()
The first Radiobutton is created. The value parameter specifies the value asso-
ciated with the radio button. It is later used to tell between the radio buttons.
self.lvar = StringVar ()
self.lvar.set("...")
lbl = Label(self , textvariable=self.lvar)
This is the Label widget which displays the text of the currently selected radio
button.
def onRadioSelect(self ):
val = self.rvar.get()
if (val == 1):
CHAPTER 4. WIDGETS 76
self.lvar.set("Male")
else:
self.lvar.set("Female")
In the onRadioSelect() method we find out the currently selected radio button.
We query the radio button’s value. The label widget is updated with its set()
method.
Figure 4.9 shows two Radiobutton widgets on the window. The selected radio
button’s value is displayed in the label.
4.9 Entry
Entry allows the user to enter or edit a single line of plain text.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
CHAPTER 4. WIDGETS 77
self.initUI ()
def initUI(self ):
self.parent.title("Entry")
self.pack(fill=BOTH , expand =1)
evar = StringVar ()
evar.trace("w", self.onChanged)
self.entry = Entry(self , textvariable=evar)
self.entry.pack(side=LEFT , padx =15)
self.lvar = StringVar ()
self.lvar.set("...")
lbl = Label(self , text="...", textvariable=self.lvar)
lbl.pack(side=LEFT)
self.lvar.set(self.entry.get ())
root = Tk()
ex = Example(root)
root.geometry("250 x100 +300+300")
root.mainloop ()
The example shows the entered text in an adjacent label. The label is updated
immediately as the letters are typed.
evar = StringVar ()
evar.trace("w", self.onChanged)
self.entry = Entry(self , textvariable=evar)
self.lvar.set(self.entry.get ())
In the onChanged() method, we update the label’s control variable. (The pa-
rameters of the method are not important for us.) The Entry's get() method
retrieves the entered data.
CHAPTER 4. WIDGETS 78
Figure 4.10 shows the Entry widget. The nearby label reflects the content of the
Entry widget.
The following example explores a few methods of the Entry widget.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Entry")
self.pack(fill=BOTH , expand=True)
self.entry = Entry(self)
self.entry.grid(row=0, column =0, columnspan =2,
padx =10, pady =15, sticky=W)
self.chvar = BooleanVar ()
self.chvar.set(False)
def onClear(self ):
def onSelectAll(self ):
def onDeselect(self ):
self.entry.select_clear ()
def onChangeReadability(self ):
if self.chvar.get() == True:
self.entry.config(state=DISABLED)
else:
self.entry.config(state=NORMAL)
root = Tk()
ex = Example(root)
root.geometry("300 x140 +300+300")
root.mainloop ()
In the example, we have an Entry widget and four buttons. The buttons clear
the text, select and deselect the text, and disable the widget, making it read-
only.
def onClear(self ):
The delete() method clears the text. The END flag corresponds to the position
just after the last character in the entry.
def onSelectAll(self ):
The select_range() method selects the specified range of the entry’s text.
def onDeselect(self ):
self.entry.select_clear ()
if self.chvar.get() == True:
self.entry.config(state=DISABLED)
else:
self.entry.config(state=NORMAL)
4.10 Scale
Scale is a widget that lets the user graphically select a value by sliding a knob
within a bounded interval. A Scale can be horizontal or vertical. The pro-
grammer can set the minimum and maximum value of the Scale and set its
resolution.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Scale")
self.pack(fill=BOTH , expand =1)
self.var = IntVar ()
self.label = Label(self , text=0, textvariable=self.var)
self.label.pack(side=LEFT)
v = int(float(val))
self.var.set(v)
root = Tk()
ex = Example(root)
root.geometry("250 x100 +300+300")
root.mainloop ()
We have two widgets in the above script: a scale and a label. A value from the
scale widget is shown in the label widget.
scale = Scale(self , from_=0, to=100, command=self.onScale)
A Scale widget is created. We provide the lower and upper bounds. The from
is a regular Python keyword that is why there is an underscore. When we move
the knob of the scale, the onScale() method is called.
self.var = IntVar ()
self.label = Label(self , text=0, textvariable=self.var)
An integer value holder and label widget are created. The value from the holder
is shown in the label widget.
def onScale(self , val):
v = int(float(val))
CHAPTER 4. WIDGETS 82
self.var.set(v)
The onScale() method receives a currently selected value from the scale widget
as a parameter. The value is first converted to a float and then to integer.
Finally, the value is set to the value holder of the label widget.
Figure 4.12 shows a label with a value chosen by the adjacent scale widget.
4.11 Spinbox
Spinbox allows the user to choose a value by clicking the up/down buttons
or pressing up/down on the keyboard to increase/decrease the value currently
displayed. The user can also type the value in manually.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Spinbox")
CHAPTER 4. WIDGETS 83
self.var = IntVar ()
self.label = Label(self , text=0, textvariable=self.var)
self.label.pack(side=LEFT)
def onClick(self ):
val = self.sbox.get()
self.var.set(val)
root = Tk()
ex = Example(root)
root.geometry("250 x100 +300+300")
root.mainloop ()
In the example, the selected value from the Spinbox is shown in the label.
self.sbox = Spinbox(self , from_=0, to=100,
command=self.onClick)
A Spinbox widget is created. We set its range with the from_ and to parameters.
def onClick(self ):
val = self.sbox.get()
self.var.set(val)
We retrieve the currently selected Spinbox's value with the get() method and
set it to the label widget with the set() method of its associated value holder.
Figure 4.13 shows the selected value of a spin box in the adjacent label.
CHAPTER 4. WIDGETS 84
4.12 OptionMenu
OptionMenu offers a fixed set of choices to the user in a drop-down menu.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("OptionMenu")
self.pack(fill=BOTH , expand =1)
self.ovar = StringVar ()
options = ["OpenBSD", "NetBSD", "FreeBSD"]
self.lvar = StringVar ()
self.lvar.set("OpenBSD")
lbl = Label(self , textvariable=self.lvar)
lbl.pack(side=LEFT)
self.lvar.set(a)
root = Tk()
ex = Example(root)
root.geometry("250 x150 +300+300")
root.mainloop ()
CHAPTER 4. WIDGETS 85
In the example, we have an OptionMenu widget with three options. The selected
option is displayed in the adjacent label.
self.ovar = StringVar ()
The list groups the values displayed in the drop-down menu of the OptionMenu
widget.
om = OptionMenu(self , self.ovar , options [0],
*options , command=self.onSelect)
The OptionMenu widget is created. The second parameter is the associated con-
trol variable, the third is the default value. Then comes the list of values.
def onSelect(self , a):
self.lvar.set(a)
In the onSelect() method, the currently selected value is set to the control
variable of the label.
4.13 Combobox
Combobox is a combination of an Entry and a drop-down menu. Combobox is
similar to OptionMenu.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Combobox")
self.pack(fill=BOTH , expand =1)
self.cvar = StringVar ()
self.lvar = StringVar ()
self.lvar.set("OpenBSD")
lbl = Label(self , textvariable=self.lvar)
lbl.pack(side=LEFT)
w = e.widget
self.lvar.set(w.get ())
root = Tk()
ex = Example(root)
root.geometry("300 x150 +300+300")
root.mainloop ()
In the example, there are two widgets: a Combobox and a Label. The value
selected from the combo box is shown in the label.
self.cvar = StringVar ()
w = e.widget
self.lvar.set(w.get ())
The onComboSelect() method is fired when the user selects an option from the
drop-down menu. From the event object, we get the reference to the event
source (our combo box), and retrieve its currently selected value with the get()
method. The returned value is set to the label’s control variable.
Figure 4.15 shows a Combobox widget. The selected value is shown in the adjacent
label.
CHAPTER 4. WIDGETS 88
4.14 Scrollbar
Scrollbar provides a scrolling functionality. Sometimes the functional area of a
widget is bigger than the physical area represented by a GUI widget. Scrollbar
gives access to the whole functional area via scrolling. The Scrollbar is used to
implement scrolled listboxes, canvases, and text fields.
"""
ZetCode Tkinter e-book
FONT_SIZE = 12
YOFFSET = 10
XOFFSET = 20
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Scrollbar")
self.pack(fill=BOTH , expand=True)
self.canvas = Canvas(self)
self.canvas.configure(yscrollcommand=sbar.set)
sbar.pack(side=RIGHT , fill=Y)
self.canvas.pack(side=LEFT , fill=BOTH , expand=True)
self.canvas.configure(scrollregion=self.canvas.bbox(ALL))
root = Tk()
ex = Example(root)
root.geometry("300 x150 +300+300")
root.mainloop ()
In the example, a Scrollbar is attached to a Canvas widget. The canvas has one
hundred text items, which cannot be shown at once.
self.canvas = Canvas(self)
In the for loop, we add one hundred text items to the canvas.
self.bind("<Configure >", self.onConfigure)
self.canvas.configure(scrollregion=self.canvas.bbox(ALL))
4.15 Notebook
Notebook is a container widget which consists of tabs. Each time the user clicks
on one of these tabs, the widget will display the child pane associated with that
tab.
def initUI(self ):
self.parent.title("Notebook")
self.pack(fill=BOTH , expand=True)
notebook = Notebook(self)
frame1 = Frame ()
frame2 = Frame ()
frame3 = Frame ()
In the example, we create a Notebook widget with three tabs. Each of these tabs
is associated with a Frame widget. We place one label on each of the frames.
CHAPTER 4. WIDGETS 91
notebook = Notebook(self)
A Label widget is created with the first frame as its parent. The place manager
locates the label on the frame.
notebook.add(frame1 ,text="Tab 1")
With the add() method, we add a new tab to the notebook. The text option
specifies the tab’s description.
4.16 PanedWindow
PanedWindow is a container widget that separates the window into resizable parts.
The child widgets can be resized by the user by moving separator lines using
the mouse. Note that the themed PanedWindow does not support theming of
the separator line, the sash. It is expected that the children provide some
decorations that will tell them apart.
def initUI(self ):
CHAPTER 4. WIDGETS 92
self.parent.title("PanedWindow")
self.pack(fill=BOTH , expand=True)
The example creates a PanedWindow with three panes; the panes are LabelFrames.
The panes have different weights which make them shrink or grow at a different
speed.
pwin = PanedWindow(self , orient=HORIZONTAL)
A LabelFrame widget is created. We put label frames into the paned window.
An initial size is provided with the width and height parameters.
pwin.add(leftFrame , weight =5)
The add() method adds a pane to the paned window. The weight parameter
sets the proportion of the pane’s resizement.
4.17 Progressbar
Progressbar gives the user an indication of the progress of an operation and
reassures them that the application is still running.
"""
ZetCode Tkinter e-book
DELAY1 = 30
DELAY2 = 20
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Progressbar")
self.pack(fill=BOTH , expand=True)
pb1 = self.nametowidget('pb1')
pb2 = self.nametowidget('pb2')
CHAPTER 4. WIDGETS 94
if op == 'start ':
pb1.start(DELAY1)
pb2.start(DELAY2)
else:
pb1.stop ()
pb2.stop ()
root = Tk()
ex = Example(root)
root.geometry("300 x200 +300+300")
root.mainloop ()
The application displays two Progressbar widgets in two different modes: de-
terminate and indeterminate. Two buttons are used to start and stop the pro-
gressbars.
pb1 = Progressbar(self , mode='determinate ', name='pb1')
pb1 = self.nametowidget('pb1')
pb2 = self.nametowidget('pb2')
...
In the updateBars() method, we get the reference to the Progressbars with the
nametowidget() method.
if op == 'start ':
pb1.start(DELAY1)
pb2.start(DELAY2)
else:
pb1.stop ()
pb2.stop ()
The start() method starts the indicator of a Progressbar. Its parameter is the
animation interval in milliseconds; the default is 50 ms. The stop() method
stops the progress.
CHAPTER 4. WIDGETS 95
Figure 4.19 shows two Progressbar widgets controlled with two buttons.
Chapter 5
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Simple menu")
96
CHAPTER 5. MENUS AND TOOLBARS 97
self.pack(fill=BOTH , expand=True)
menubar = Menu(self.parent)
self.parent.config(menu=menubar)
def onExit(self ):
self.quit ()
root = Tk()
root.geometry("300 x150 +300+300")
app = Example(root)
root.mainloop ()
The example shows a menu with one item. By selecting the Exit menu item,
we close the application.
menubar = (self.parent)
self.parent.config(menu=menubar)
A command is added to the File menu with the add_command() method. The
command calls the onExit() method.
menubar.add_cascade(label="File", menu=fileMenu)
The File menu is added to the menubar using the add_cascade() method.
CHAPTER 5. MENUS AND TOOLBARS 98
5.2 Submenu
A submenu is a menu plugged into another menu object. The next example
demonstrates this.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Submenu")
self.pack(fill=BOTH , expand=True)
menubar = Menu(self.parent)
self.parent.config(menu=menubar)
CHAPTER 5. MENUS AND TOOLBARS 99
fileMenu.add_separator ()
def onExit(self ):
self.quit ()
root = Tk()
root.geometry("250 x150 +300+300")
app = Example(root)
root.mainloop ()
By adding the menu to the File menu and not to the menubar, we create a
submenu. The underline parameter creates a keyboard shortcut. It provides
the character position which should be underlined. In our case it is the first;
the positions start from zero. The shortcuts enable to navigate to menu items
with a keyboard. First, we activate the File menu with Alt+F and then we can
activate the submenu with Alt+I. The Exit menu is activated with Alt+E.
fileMenu.add_separator ()
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Popup menu")
self.pack(fill=BOTH , expand=True)
self.menu.add_command(label="Minimize",
command=self.doMinimize)
self.menu.add_command(label="Exit", command=self.onExit)
self.menu.post(e.x_root , e.y_root)
def doMinimize(self ):
self.parent.iconify ()
def onExit(self ):
self.quit ()
root = Tk()
root.geometry("250 x150 +300+300")
app = Example(root)
root.mainloop ()
In the example, we create a popup menu with two commands. One command
minimizes the application, the other one terminates it.
self.menu = Menu(self.parent , tearoff=False)
We bind the <Button-3> event to the showMenu() method. The event is generated
when we right click on the client area of the window.
def showMenu(self , e):
self.menu.post(e.x_root , e.y_root)
The showMenu() method shows the context menu. The popup menu is shown at
the x and y coordinates of the mouse click.
def doMinimize(self ):
self.parent.iconify ()
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
s = Style ()
s.configure("Statusbar.TLabel", borderwidth =2, relief=RIDGE)
CHAPTER 5. MENUS AND TOOLBARS 103
menubar = Menu(self.parent)
self.parent.config(menu=menubar)
self.showStat = BooleanVar ()
self.showStat.set(True)
self.svar = StringVar ()
self.svar.set("Ready")
def onClick(self ):
if (self.showStat.get() == True ):
self.sb.pack(side=BOTTOM , fill=X)
else:
self.sb.pack_forget ()
def onExit(self ):
self.quit ()
root = Tk()
root.geometry("300 x150 +300+300")
app = Example(root)
root.mainloop ()
With the check menu button, we control the visibility of the application’s sta-
tusbar.
s = Style ()
s.configure("Statusbar.TLabel", borderwidth =2, relief=RIDGE)
A check button is added to the View menu using the add_checkbutton() method.
self.sb = Label(self , textvariable=self.svar ,
style="Statusbar.TLabel")
self.sb.pack(side=BOTTOM , fill=X)
The Label widget serves as a statusbar. With the style option, we set the
custom style for the label.
def onClick(self ):
if (self.showStat.get() == True ):
self.sb.pack(side=BOTTOM , fill=X)
else:
self.sb.pack_forget ()
In the onClick() method, we toggle the visibility of the statusbar. The label is
removed from the layout with the pack_forget() method.
Figure 5.4 shows a selected check menu button; the statusbar is visible.
5.5 Toolbar
Menus group commands that we can use in an application. Toolbars provide a
quick access to the most frequently used commands. There is no toolbar widget
in Tkinter; we use a Frame widget to create a toolbar.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Toolbar")
self.pack(fill=BOTH , expand=True)
s = Style ()
s.configure('TFrame ', relief=RAISED)
s.configure('TButton ', relief=FLAT)
menubar = Menu(self.parent)
self.fileMenu = Menu(self.parent , tearoff =0)
self.fileMenu.add_command(label="Exit", command=self.onExit)
menubar.add_cascade(label="File", menu=self.fileMenu)
toolbar = Frame(self)
self.img = Image.open("exit.png")
eimg = ImageTk.PhotoImage(self.img)
toolbar.pack(side=TOP , fill=X)
self.parent.config(menu=menubar)
self.pack ()
def onExit(self ):
self.quit ()
root = Tk()
root.geometry("300 x150 +300+300")
app = Example(root)
root.mainloop ()
CHAPTER 5. MENUS AND TOOLBARS 106
An Exit button is created and placed on the left side of the toolbar.
toolbar.pack(side=TOP , fill=X)
Listbox
Listbox is a widget that displays a list of objects. It allows the user to select
one or more items.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Item selection")
self.pack(fill=BOTH , expand=True)
107
CHAPTER 6. LISTBOX 108
lb = Listbox(self)
for i in acts:
lb.insert(END , i)
self.var = StringVar ()
self.label = Label(self , text=0, textvariable=self.var)
self.label.pack(pady =10)
sender = val.widget
idx = sender.curselection ()
value = sender.get(idx)
self.var.set(value)
root = Tk()
ex = Example(root)
root.geometry("300 x250 +300+300")
root.mainloop ()
sender = val.widget
idx = sender.curselection ()
value = sender.get(idx)
self.var.set(value)
The currently selected item’s index is retrieved with the curselection() method.
The index is passed to the get() method to fetch the item.
Figure 6.1 shows a currently selected item in a label located below the listbox.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Multiple selection")
self.pack(fill=BOTH , expand=True)
lb = Listbox(self , selectmode=EXTENDED)
for i in acts:
lb.insert(END , i)
sender = e.widget
idx = sender.curselection ()
for i in idx:
print(sender.get(i))
print("****************** ")
root = Tk()
ex = Example(root)
root.geometry("300 x250 +300+300")
root.mainloop ()
sender = e.widget
idx = sender.curselection ()
for i in idx:
CHAPTER 6. LISTBOX 111
print(sender.get(i))
The current selection is determined with the curselection() method. In the for
loop, we go through the returned tuple of selected indexes and print the selected
items to the console.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Scrollbar")
self.pack(fill=BOTH , expand=True)
sbar = Scrollbar(self)
sbar.pack(side = RIGHT , fill=Y)
root = Tk()
ex = Example(root)
root.geometry("250 x250 +300+300")
root.mainloop ()
Figure 6.2 shows a listbox with more items that its window can display. There-
fore, we need a scrollbar to reach more items of the listbox.
CHAPTER 6. LISTBOX 113
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.entry = Entry(self)
self.entry.grid(row=0, column =0, padx =10)
self.lbox = Listbox(self)
def addItem(self ):
val = self.entry.get()
CHAPTER 6. LISTBOX 114
def removeItem(self ):
idx = self.lbox.curselection ()
if (len(idx) == 0):
return
self.lbox.delete(idx , idx)
root = Tk()
ex = Example(root)
root.geometry("300 x250 +300+300")
root.mainloop ()
In the example, we have a listbox, an entry, and two buttons. The buttons add
and remove items.
def addItem(self ):
val = self.entry.get()
In the addItem() method, we get the value from the entry widget. We ensure
that it is not empty and does not contain only white characters. We delete the
entry’s value and insert the retrieved value into the listbox using the insert()
method. The value is added at the end of the listbox.
def removeItem(self ):
idx = self.lbox.curselection ()
if (len(idx) == 0):
return
self.lbox.delete(idx , idx)
Figure 6.3 shows four items in a listbox. They were dynamically inserted.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Sorting items")
self.pack(fill=BOTH , expand=True)
CHAPTER 6. LISTBOX 116
items = ['flower ', 'pen', 'cup', 'chair ', 'envelope ', 'valet ',
'coin ', 'book ', 'paper ', 'bottle ']
self.lb = Listbox(self)
for i in items:
self.lb.insert(END , i)
self.var = BooleanVar ()
cb = Checkbutton(self , variable=self.var , text="Ascending")
cb.pack(side=LEFT)
def onSortItems(self ):
if (self.var.get() == True ):
rev = False
else:
rev = True
root = Tk()
ex = Example(root)
root.geometry("300 x250 +300+300")
root.mainloop ()
In the example, we have a listbox, a push button, and a check button. Depend-
ing on the state of the check button, pressing the push button sorts the listbox’s
items in ascending or descending order.
self.var = BooleanVar ()
cb = Checkbutton(self , variable=self.var , text="Ascending")
cb.pack(side=LEFT)
if (self.var.get() == True ):
rev = False
else:
rev = True
CHAPTER 6. LISTBOX 117
...
All the values from the listbox are stored in a temporary list.
sorted_list = sorted(temp_list , reverse = rev)
The temporary list is sorted with the sorted() function. The second parameter
is the sorting order.
self.lb.delete(0, END)
"""
ZetCode Tkinter e-book
CHAPTER 6. LISTBOX 118
class MyList(Listbox ):
Listbox.__init__(self , parent)
self.bind('<Button -1>', self.setCurrent)
self.bind('<B1 -Motion >', self.moveSelection)
self.curIndex = self.nearest(e.y)
i = self.nearest(e.y)
if i < self.curIndex:
x = self.get(i)
self.delete(i)
self.insert(i+1, x)
self.curIndex = i
x = self.get(i)
self.delete(i)
self.insert(i-1, x)
self.curIndex = i
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
lb = MyList(self)
for i in acts:
lb.insert(END , i)
root = Tk()
ex = Example(root)
root.geometry("300 x250 +300+300")
root.mainloop ()
To do the dragging, we have to listen to mouse press and mouse motion events.
class MyList(Listbox ):
Listbox.__init__(self , parent)
self.bind('<Button -1>', self.setCurrent)
self.bind('<B1 -Motion >', self.moveSelection)
...
We create our custom Listbox, where we bind event handlers for <Button-1>
and <B1-Motion> events.
def setCurrent(self , e):
self.curIndex = self.nearest(e.y)
i = self.nearest(e.y)
if i < self.curIndex:
x = self.get(i)
self.delete(i)
self.insert(i+1, x)
self.curIndex = i
x = self.get(i)
self.delete(i)
self.insert(i-1, x)
self.curIndex = i
CHAPTER 6. LISTBOX 120
Text
Text provides a widget that is used to edit and display both plain and formatted
text. The widget also supports embedded images and windows. While the Entry
allows to edit one line of text, Text allows to edit multiple lines of text.
ScrolledText is Text widget with built-in scrolling functionality.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Simple example")
121
CHAPTER 7. TEXT 122
self.pack(fill=BOTH , expand=True)
txt = Text(self)
txt.pack(fill=BOTH , expand=True)
root = Tk()
ex = Example(root)
root.geometry("300 x200 +300+300")
root.mainloop ()
The example displays a text widget and inserts three lines into it.
txt = Text(self)
txt.pack(fill=BOTH , expand=True)
With the focus() method, the text widget receives initial focus.
7.2 Fonts
It is possible to use different fonts in a Text widget. Font support is located in
the Tkinter’s font module.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Fonts")
self.pack(fill=BOTH , expand=True)
self.ovar = StringVar ()
options = ["Arial 10", "Times 12", "Courier 10"]
self.txt = Text(self)
self.txt.pack(fill=BOTH , expand=True)
self.txt.focus_set ()
f = Font(family=fname , size=fsize)
self.txt.configure(font=f)
root = Tk()
ex = Example(root)
root.geometry("300 x250 +300+300")
root.mainloop ()
The example uses an OptionMenu widget with three options. These options are
font names and sizes. The selected font is applied on the text widget.
options = ["Arial 10", "Times 12", "Courier 10"]
We split the font description into a font name and font size.
f = Font(family=fname , size=fsize)
With the configure() method, we apply the chosen font on the text widget.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Text selection")
self.pack(fill=BOTH , expand=True)
self.txt = Text(self)
self.txt.pack(fill=BOTH , expand=True)
def onGetSelection(self ):
try:
selText = self.txt.get(SEL_FIRST , SEL_LAST)
showinfo("You have selected", message=selText)
except TclError:
pass
CHAPTER 7. TEXT 126
root = Tk()
ex = Example(root)
root.geometry("300 x200 +300+300")
root.mainloop ()
The SEL_FIRST index points to the start of the selection; the SEL_LAST index
points to the end of the selection.
showinfo("You have selected", message=selText)
Figure 7.3 shows a message box containing the selected text from the text wid-
get.
7.4 Image
The Text widget is capable of containing an image. It is treated as a single
character whose size is the natural size of the object. To insert an image, we
use the image_create() method.
CHAPTER 7. TEXT 127
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Image")
self.pack(fill=BOTH , expand=True)
self.bard = Image.open("bardejov.jpg")
self.photo = ImageTk.PhotoImage(self.bard)
txt = Text(self)
txt.image_create(END , image=self.photo)
txt.insert(END ,'\n')
txt.insert(END , "Bardejov is a small town in east Slovakia.")
txt.pack(fill=BOTH , expand=True)
root = Tk()
ex = Example(root)
root.geometry("300 x250 +300+300")
root.mainloop ()
"""
ZetCode Tkinter e-book
class Example(Frame ):
Frame.__init__(self , parent)
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Undo , redo")
self.pack(fill=BOTH , expand=True)
self.txt.pack(fill=BOTH , expand=True)
self.cmenu.post(e.x_root , e.y_root)
def doUndo(self ):
try:
self.txt.edit_undo ()
except TclError:
pass
def doRedo(self ):
try:
self.txt.edit_redo ()
except TclError:
pass
root = Tk()
ex = Example(root)
root.geometry("300 x200 +300+300")
root.mainloop ()
The built-in undo and redo operations are tied to the Ctrl+Z and Ctrl+Shift+Z
key combinations. In addition, we create undo and redo context menu options.
self.txt = Text(self , undo=True)
CHAPTER 7. TEXT 130
The undo and redo operations are enabled by setting the text widget’s undo
option to True.
def doUndo(self ):
try:
self.txt.edit_undo ()
except TclError:
pass
try:
self.txt.edit_redo ()
except TclError:
pass
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
CHAPTER 7. TEXT 131
def initUI(self ):
self.txt = Text(self)
self.txt.bind("<Button -3>", self.showContextmenu)
self.txt.focus ()
self.txt.pack(fill=BOTH , expand=True)
self.txt.insert(END , "ZetCode , tutorials for programmers")
def editCut(self ):
self.txt.event_generate("<<Cut >>")
def editCopy(self ):
self.txt.event_generate("<<Copy >>")
def editPaste(self ):
self.txt.event_generate("<<Paste >>")
self.cmenu.post(e.x_root , e.y_root)
root = Tk()
ex = Example(root)
root.geometry("300 x200 +300+300")
root.mainloop ()
The example enables cut, copy, and paste operations for menu items of a context
menu. The operations are performed by triggering existing virtual events.
self.txt = Text(self)
self.txt.bind("<Button -3>", self.showContextmenu )
self.txt.event_generate("<<Cut >>")
As we already mentioned, there are built-in operations for cut, copy, and paste
operations. For the cut operation triggered from the context menu, we generate
the <<Cut>> virtual event.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Searching text")
self.pack(fill=BOTH , expand=True)
self.entry = Entry(self)
self.entry.grid(row=0, column =0, padx=5, sticky=W+E)
self.txt = Text(self)
self.txt.focus ()
self.txt.tag_configure("wordfound", background="gray88")
def onSearch(self ):
idx = "1.0"
myword = self.entry.get()
while True:
if (idx == ""):
return
else:
self.txt.tag_add("wordfound", idx ,
"%s+%dc" % (idx , len(myword )))
idx = "%s+%dc" % (idx , len(myword ))
root = Tk()
ex = Example(root)
root.geometry("350 x250 +300+300")
root.mainloop ()
In the example, we have a search entry, button, and a text widget. Clicking
on the Search button performs a searching operation. All the matching text is
highlighted.
self.txt.tag_configure("wordfound", background="gray88")
A text tag is created for the matching strings; these strings will have a gray
background.
def onSearch(self ):
When we begin searching, we first remove any previous highlights. The "1.0" is
a specific index—the beginning of the text widget’s content. The first digit is a
line number, the second digit is a column number. Lines start from 1, columns
from 0.
if (len(myword.strip ()) == 0):
return
We skip searching if the entry widget is empty or contains just white spaces.
idx = self.txt.search(myword , idx , stopindex=END)
The search() method is used to search for text. The first parameter is the search
string. The second parameter is the index from which the searching progresses.
The stopindex is the limit of the searching operation. The method returns an
empty string if no (other) string was found. The default searching direction is
forward.
self.txt.tag_add("wordfound", idx ,
"%s+%dc" % (idx , len(myword )))
idx = "%s+%dc" % (idx , len(myword ))
We apply the wordfound tag at the matched word and increase the search index
by the length of the word.
Figure 7.5 shows a text widget with all occurrences of the “of” word highlighted.
"""
CHAPTER 7. TEXT 135
import re
from tkinter import Tk , Text , BOTH , END , Y, LEFT
from tkinter.ttk import Frame , Button
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Spell checking")
self.pack(fill=BOTH , expand=True)
f = Frame(self)
f.pack(fill=Y, expand=True)
self.txt = Text(self)
self.txt.pack(fill=BOTH , expand=True)
self.txt.tag_configure("misspell", foreground="red",
underline=True)
def onSpellCheck(self ):
idx = "1.0"
if (len(myword) == 1):
continue
if re.match("[0-9]", myword ):
continue
def onClear(self ):
root = Tk()
ex = Example(root)
root.geometry("450 x300 +300+300")
root.mainloop ()
The program contains two buttons and a text widget. We use a dictionary
available in Linux.
self.txt.tag_configure("misspell", foreground="red",
underline=True)
A misspell tag is created; this tag makes the text underlined and rendered in
red colour.
with open("words", "r") as fwords:
self.words = fwords.read (). split("\n")
At the beginning of the spell checking process, we remove any misspell tags.
CHAPTER 7. TEXT 137
The strings can have many punctuation marks; we will strip them from the
words.
if (len(myword) == 1):
continue
Numeral strings are not checked. A regular expression is used to identify digits
in the string.
if (myword not in self.words and myword.lower ()
not in self.words ):
We check if the word from the text widget is not contained in the dictionary.
We also check for the lowercase version of the word. This is because first words
of a sentence are capitalized but they are available only in lowercase in the
dictionary.
idx = self.txt.search(myword , idx)
If the word is not located in the dictionary, we search for it in the text widget
with the search() method. The second parameter of the method is the index
from which the searching starts. The default search is a forward search.
self.txt.tag_add("misspell", idx ,
"%s+%dc" % (idx , len(myword )))
Finally, the index is increased by the length of the misspelled word. The search-
ing for the next word starts from this place.
CHAPTER 7. TEXT 138
Figure 7.6 shows three misspelled words identified by our spell checking.
"""
ZetCode Tkinter e-book
import sys
from tkinter import Tk , BOTH , END , N, S, E, W, StringVar
from tkinter.ttk import Frame , Label
from tkinter.scrolledtext import ScrolledText
from tkinter.filedialog import askopenfilename , asksaveasfilename
from tkinter.messagebox import askyesnocancel , showerror
class Example(Frame ):
CHAPTER 7. TEXT 139
self.parent = parent
self.initUI ()
def initUI(self ):
self.txt = ScrolledText(self)
self.txt.bind("<Control -o>", self.onOpenFile)
self.txt.bind("<Control -s>", self.onSaveFile)
self.txt.bind("<Control -w>", self.onCloseFile)
self.txt.bind("<Control -Shift -S>", self.onSaveAsFile)
self.txt.bind("<KeyRelease >", self.onKeyReleased)
self.txt.focus ()
self.parent.protocol("WM_DELETE_WINDOW", self.onDeleteEvent)
self.lvar = StringVar ()
sbar = Label(self , textvariable=self.lvar)
sbar.grid(row=1, column =0, sticky=W+E, padx =5)
self.filename = "Untitled"
self.lvar.set(self.filename)
def onDeleteEvent(self ):
if (self.txt.edit_modified ()):
if ret == True:
if (self.filename.startswith("Untitled")):
self.onSaveAsFile(None)
else:
self.doSaveFile ()
self.quit ()
self.quit ()
else:
return
else:
self.quit ()
CHAPTER 7. TEXT 140
self.doDeleteContent ()
return
if ret == True:
if (self.filename.startswith("Untitled")):
ftypes = [('All files ', '*'), ('Python files ', '*.py')]
filename = asksaveasfilename (filetypes=ftypes)
if filename == "":
return
self.filename = filename
self.doSaveFile ()
self.doDeleteContent ()
if self.filename.startswith("Untitled"):
self.onSaveAsFile(e)
else:
self.doSaveFile ()
if filename == "":
return
self.filename = filename
self.doSaveFile ()
def doSaveFile(self ):
self.txt.edit_modified(False)
self.lvar.set(self.filename)
def doDeleteContent(self ):
self.txt.delete("1.0", END)
self.txt.edit_modified(False)
self.filename = "Untitled"
self.lvar.set(self.filename)
if self.txt.edit_modified ():
self.lvar.set(self.filename + " *")
if self.txt.edit_modified ():
showerror("Error", "Save file before opening")
return
if filename == "":
return
else:
self.filename = filename
self.lvar.set(self.filename)
self.txt.delete("1.0", END)
self.txt.insert(END , text)
self.txt.edit_modified(False)
return "break"
root = Tk()
ex = Example(root)
root.geometry("450 x350 +300+300")
root.mainloop ()
There are key combinations for opening, saving, and closing files. Only one file
can be opened at a time. Its name is shown in the statusbar. A document that
has not yet been given a name is called “Untitled”. An asterisk is given to the
CHAPTER 7. TEXT 142
We use the ScrolledText widget. This is a Text widget with some scrolling
functionality enabled.
self.txt.bind("<Control -o>", self.onOpenFile)
self.txt.bind("<Control -s>", self.onSaveFile)
self.txt.bind("<Control -w>", self.onCloseFile)
self.txt.bind("<Control -Shift -S>", self.onSaveAsFile)
We bind key combinations for opening, saving, and closing files. For instance,
the standard Ctrl+O combination is used for opening a new file.
self.parent.protocol(" WM_DELETE_WINDOW ", self.onDeleteEvent)
if (self.txt.edit_modified ()):
Clicking on the window Close button or pressing the Alt+F4 combination gen-
erates a window close event. If there was some modification of the data, we
show a dialog asking for further action. If not, the application is closed. The
edit_modified() method determines if the document was modified.
if ret == True:
if (self.filename.startswith("Untitled")):
self.onSaveAsFile(None)
else:
self.doSaveFile ()
self.quit ()
self.quit ()
else:
return
If we click the dialog’s Yes button, the data is saved. (If the document has not
been named, a Save As dialog is generated.) Clicking on the No button, the
changes are discarded. Nothing is done if we click on the Cancel button.
def onCloseFile(self , e):
sys.exit (0)
...
The Ctrl+W key combination triggers the onCloseFile() method. The appli-
cation is terminated if the document was not modified and it has the default
name.
if (not self.txt.edit_modified () and not
self.filename.startswith("Untitled")):
self.doDeleteContent ()
return
If the document was not modified (it was saved) and the user has given the
document a name, the document’s content is erased in the doDeleteContent()
method.
msg = "Save changes before closing file?"
The other possibility is that the document was modified. For such a case, we
show a dialog asking for further actions.
if ret == True:
if (self.filename.startswith("Untitled")):
ftypes = [('All files ', '*'), ('Python files ', '*.py')]
filename = asksaveasfilename (filetypes=ftypes)
if filename == "":
return
self.filename = filename
self.doSaveFile ()
self.doDeleteContent ()
Choosing Yes from the dialog, a new Save As dialog is generated; it is used
to choose a file name for our untitled document. Then we save the file to the
chosen file name and delete the text widget’s content. Choosing No leads to the
deletion of the data; the data is not saved. The third option does nothing.
def onSaveFile(self , e):
if self.filename.startswith("Untitled"):
self.onSaveAsFile(e)
else:
self.doSaveFile ()
CHAPTER 7. TEXT 144
In the onSaveFile() we first ensure that the document was modified. If the
document has the default name, we first show the Save As dialog for choosing
a file name. Otherwise, we call the doSaveFile() method which saves the file to
the disk.
def doSaveFile(self ):
self.txt.edit_modified(False)
self.lvar.set(self.filename)
In the doSaveFile() method, we get the data from the text widget and save
it into the current file name. The file name is stored in the filename variable.
After the file was saved, the text widget’s state is changed to non-modified.
This is done by passing False to the edit_modified() method. The statusbar is
updated as well—the asterisk indicating document modification is removed.
def doDeleteContent(self ):
self.txt.delete("1.0", END)
self.txt.edit_modified(False)
self.filename = "Untitled"
self.lvar.set(self.filename)
The purpose of the onDeleteContent() method is to delete the data from the
document. The text widget’s delete() method removes all data from the begin-
ning to the end of the widget. The document’s state is changed to non-modified.
Finally, we change the filename variable to the default “Untitled” value.
def onKeyReleased(self , e):
if self.txt.edit_modified ():
self.lvar.set(self.filename + " *")
Each time a key is pressed, we check if the document was modified with the
edit_modified() method. If so, we add an asterisk at the end of its name shown
in the statusbar.
def onOpenFile(self , e):
if self.txt.edit_modified ():
showerror("Error", "Save file before opening")
return
An error message is shown if we try to open a new file while the old one was
not saved yet.
ftypes = [('All files ', '*'), ('Python files ', '*.py')]
filename = askopenfilename(parent=self , filetypes=ftypes)
if filename == "":
return
else:
CHAPTER 7. TEXT 145
self.filename = filename
self.lvar.set(self.filename)
A dialog is shown to select the file name to be opened. There are two categories
of files: all types of files and Python files.
with open(self.filename , "r") as fname:
text = fname.read ()
self.txt.delete("1.0", END)
self.txt.insert(END , text)
self.txt.edit_modified(False)
The file is opened and all the data is read. We delete the existing content from
the document and insert the retrieved data. From the perspective of a text
editor, the document is not modified at this moment.
return "break"
Returning the "break" string stops the propagation of the modification events.
Without this line the modification state would not be updated correctly.
Figure 7.7 shows a file opened in a simple text editor. The files name is in the
statusbar.
Chapter 8
Treeview
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Simple Treeview")
self.pack(fill=BOTH , expand=True)
146
CHAPTER 8. TREEVIEW 147
tree.heading("#0", text="Item")
tree.heading("#1", text="Quantity")
tree.column("#0", width =150)
tree.column("#1", width =150)
tree.column("#1", anchor=E)
tree.pack(fill=BOTH , expand=True)
self.pack ()
root = Tk()
ex = Example(root)
root.geometry("+300+300")
root.mainloop ()
With the heading() method, we set the column titles. The #0 refers to the first
column, the #1 to the second.
tree.column("#0", width =150)
tree.column("#1", width =150)
With the column() method, we set the width of the columns. The default width
of a column is 200 units.
tree.column("#1", anchor=E)
With the anchor option, we align the data inside the second column to the right.
tree.insert("" , index=END , text="coins", values =("10" ,))
The insert() method puts data into the treeview widget. The first parameter
is the parent Id; if we are creating a new top-level item, the parameter is an
empty string. The index parameter sets the position of the item in the treeview.
CHAPTER 8. TREEVIEW 148
Passing END to the parameter places the item at the end. The text option sets
the name of the label—the value of the first column. The values option sets
data for the rest of the columns. Since we have only one additional column,
there is only one value.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
CHAPTER 8. TREEVIEW 149
tree.tag_configure("evenrow", background="gray88")
i = 0
for itm in data.keys ():
if (i % 2 == 0):
tree.insert("", index=END , text=itm ,
values =( data[itm ]))
else:
tree.insert("", index=END , text=itm ,
values =( data[itm]), tags = ('evenrow ' ,))
i += 1
tree.pack(fill=BOTH , expand=True)
self.pack ()
root = Tk()
ex = Example(root)
root.geometry("+300+300")
root.mainloop ()
Each treeview item can have a tag set. The tag_configure() method creates an
evenrow tag, which sets a grey background.
Every row with an even index has the evenrow tag applied.
CHAPTER 8. TREEVIEW 150
8.3 Hierarchy
Treeview items can form a hierarchy. The following example demonstrates this.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Hierarchy")
self.pack(fill=BOTH , expand=True)
tree.heading("#0", text="Country")
tree.heading("#1", text="Capital")
tree.pack(fill=BOTH , expand=True)
self.pack ()
root = Tk()
ex = Example(root)
root.geometry("+300+300")
root.mainloop ()
These two lines add two top-level items. Top-level items contain an empty string
as their first parameter. The insert() method returns an Id of the added item,
which is later used to add a subitem.
for el in asia.keys ():
tree.insert(idAsia , index=END , text=el ,
values =( asia[el],))
CHAPTER 8. TREEVIEW 152
This for loop adds Asian countries to the Asia continent. Note that the first
parameter of the insert() method is the Id of the previously created top-level
item.
8.4 Images
The row label may include an image. The Treeview's insert() has an image
option to specify a photo image.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
CHAPTER 8. TREEVIEW 153
def initUI(self ):
self.parent.title("Images")
self.pack(fill=BOTH , expand=True)
self.img1 = Image.open("battery.png")
self.battery = ImageTk.PhotoImage(self.img1)
self.img2 = Image.open("camera.png")
self.camera = ImageTk.PhotoImage(self.img2)
self.img3 = Image.open("microphone.png")
self.microphone = ImageTk.PhotoImage(self.img3)
tree.pack(fill=BOTH , expand=True)
self.pack ()
root = Tk()
ex = Example(root)
root.geometry("+300+300")
root.mainloop ()
The example displays tree rows. The rows have images in their labels.
Style (). configure('Treeview ', rowheight =30)
We increase the height of the rows to put some additional space between the
images.
self.img1 = Image.open("battery.png")
self.battery = ImageTk.PhotoImage(self.img1)
The image parameter of the insert() method specifies the photo image to be
displayed.
8.5 Selection
There are three selections modes: (a) BROWSE, which allows one row to be selected
at a time; (b) EXTENDED, which allows to select multiple items; (c) NONE, which
disables selections. The <<TreeviewSelect>> virtual event is generated when a
selection of rows has changed.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
CHAPTER 8. TREEVIEW 155
self.parent.title("Selection")
self.pack(fill=BOTH , expand=True)
tree.heading("#0", text="Item")
tree.heading("#1", text="Quantity")
tree.column("#1", anchor=E)
tree.pack(fill=BOTH , expand=True)
self.lvar = StringVar ()
lbl = Label(self , textvariable=self.lvar , text="Ready")
lbl.pack(side=LEFT , pady =2)
self.pack ()
sender = e.widget
itm = sender.selection ()[0]
val1 = sender.item(itm , "text")
val2 = sender.item(itm , "values")[0]
msg = "{0} : {1}".format(val1 , val2)
self.lvar.set(msg)
root = Tk()
ex = Example(root)
root.geometry("+300+300")
root.mainloop ()
The example shows the selected row’s label text and value in the statusbar.
tree = Treeview(self , selectmode=BROWSE , columns =("quant"))
sender = e.widget
itm = sender.selection ()[0]
...
Inside the onSelect() method, we determine the event sender. The selection()
method returns the iid of the selected row. (The iid is an internal id used by
Tkinter.)
val1 = sender.item(itm , "text")
val2 = sender.item(itm , "values")[0]
The item() method uses the iid to determine the row’s label and value.
msg = "{0} : {1}".format(val1 , val2)
self.lvar.set(msg)
Figure 8.5 shows a row selected. The row’s data is mirrored in the statusbar.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Modifying rows")
self.pack(fill=BOTH , expand=True)
item_entry = Entry(self)
item_entry.grid(row=0, column=1, sticky=W+E, padx =5)
quant_entry = Entry(self)
quant_entry.grid(row=0, column=3, sticky=W+E, padx =5)
self.tree.heading("#0", text="Item")
self.tree.heading("#1", text="Quantity")
self.pack ()
def onRemove(self ):
iids = self.tree.selection ()
self.tree.delete(iid)
val1 = item_entry.get()
val2 = quant_entry.get()
item_entry.delete(0, END)
quant_entry.delete(0, END)
self.tree.insert("", index=END , text=val1 , values =(val2 ,))
root = Tk()
ex = Example(root)
root.geometry("+300+300")
root.mainloop ()
In addition to the treeview widget, the example uses buttons and entry widgets,
which are used to modify treeview items.
self.tree = Treeview(self , columns =("quant"),
selectmode=EXTENDED)
iids = self.tree.selection ()
In the EXTENDED selection mode, the selection() method returns multiple iids.
We go through the list of iids and remove the selected rows with the delete()
method.
def onInsert(self , item_entry , quant_entry ):
val1 = item_entry.get()
val2 = quant_entry.get()
...
Two entries are passed to the onInsert() method. We retrieve their contents
with the get() method.
CHAPTER 8. TREEVIEW 159
We ensure that the returned values are not white spaces or empty strings.
item_entry.delete (0, END)
quant_entry.delete(0, END)
self.tree.insert("", index=END , text=val1 , values =(val2 ,))
We delete the contents of the entries and add a new row with the insert()
method.
Figure 8.6 shows three selected rows before they are removed.
"""
ZetCode Tkinter e-book
Website: www.zetcode.com
"""
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Treeview")
self.pack(fill=BOTH , expand=True)
tree.heading("#0", text="Item")
tree.heading("#1", text="Quantity")
tree.column("#1", anchor=E)
tree.pack(fill=BOTH , expand=True)
self.pack ()
sender = e.widget
root = Tk()
ex = Example(root)
root.geometry("+300+300")
root.mainloop ()
The example shows the row’s data on which we click in a message box.
CHAPTER 8. TREEVIEW 161
sender = e.widget
...
With the identify() method, we determine the row’s iid. The method takes
the x and y coordinates of the mouse pointer as parameters to find out the row.
val1 = sender.item(iid , "text")
val2 = sender.item(iid , "values")[0]
A message is built from the row’s data and displayed in a message box.
Figure 8.7 shows a message box displaying the data of the row on which we have
double clicked.
8.8 Sorting
In this section, we will be sorting data. A treeview’s column can react to
mouse clicks. Traditionally, clicking on a column header is bound to a sorting
operation.
"""
ZetCode Tkinter e-book
class Example(Frame ):
sortOrder = True
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("Sorting")
self.pack(fill=BOTH , expand=True)
self.createTreeview ()
self.loadData ()
def createTreeview(self ):
f = Frame(self)
f.pack(fill=BOTH , expand=True)
def loadData(self ):
self.data = [
"Poland", "Argentina", "Slovakia", "Italy", "Canada",
"China", "Germany", "United States", "Brazil",
"Hungary", "Spain", "Japan", "Mexico", "Australia",
"South Africa", "United Kingdom", "France", "Russia"
]
CHAPTER 8. TREEVIEW 163
self.tree.heading("#1", text="Country",
command=lambda c="Country": self.sortColumn(c,
Example.sortOrder ))
data.sort(reverse=sortOrder)
for indx , item in enumerate (data ):
self.tree.move(item [1], '', indx)
root = Tk()
ex = Example(root)
root.geometry("+300+300")
root.mainloop ()
In the example, we have one column with names of countries. Clicking on the
column sorts the data. Clicking again sorts the data in a reverse order. There
is also a vertical scrollbar attached to the treeview.
class Example(Frame ):
sortOrder = True
The sortOrder is a class variable which stores the sorting order. There are two
sorting orders: ascending and descending.
self.tree = Treeview(f, columns =("Country"), show='headings ')
In the sortColumn() method, we first get the data from the treeview. The data
is a list of tuples containing the column values and their iids.
data.sort(reverse=sortOrder)
for indx , item in enumerate (data ):
self.tree.move(item [1], '', indx)
The data is sorted with the sort() method. The treeview’s move() method is
used to reposition the treeview items.
Example.sortOrder = not sortOrder
Figure 8.8 shows a Treeview with its items alphabetically sorted. The vertical
scrollbar allows to reach items that are not visible given the size of the window.
Lazy loading is created with the help of the <<TreeviewOpen>> virtual event.
The event is fired whenever a new node is opened. At this time, the program
determines if a node is a directory and loads its files if it is not empty. It is
not economical to go trough the whole file system because it takes considerable
time and because the user is most likely not interested in seeing all files during
the lifetime of the application.
#! /usr/bin/env python3
import os
import glob
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.parent.title("File browser")
self.pack(fill=BOTH , expand=True)
f = Frame(self)
f.pack(fill=BOTH , expand=True)
self.loadRoot(tree)
tree.bind("<<TreeviewOpen >>", self.updateTree)
tree.bind("<Double -Button -1>", self.changeDir)
tree = e.widget
self.populateTree(tree , tree.focus ())
parent = tree.parent(node)
sdirs = [] if parent else glob.glob('.') + glob.glob('..')
ptype = None
p = os.path.join(path , p). replace('\\', '/')
if os.path.isdir(p):
ptype = "directory"
elif os.path.isfile(p):
ptype = "file"
fname = os.path.split(p)[1]
itemId = tree.insert(node , index=END , text=fname ,
values =[p, ptype ])
tree.insert(itemId , 0, text="dummy")
tree = e.widget
node = tree.focus ()
if tree.parent(node ):
if os.path.isdir(path ):
os.chdir(path)
tree.delete(tree.get_children(''))
self.loadRoot(tree)
root = Tk()
ex = Example(root)
root.geometry("+300+300")
root.mainloop ()
The example uses a treeview widget to display files and directories. The size of
the files is shown in the second column. It is possible to change directories by
clicking on the directory names.
cols = ("path", "type", "size")
There are three column names, but only the last one is displayed. Other two
columns are used for storing values.
tree = Treeview(f, columns=cols , displaycolumns="size",
yscrollcommand=lambda f, l:self.autoScroll(vsb , f, l),
xscrollcommand=lambda f, l:self.autoScroll(hsb , f, l))
Each time we open a file which is a directory (in the Unix philosophy, everything
is a file) a <<TreeviewOpen>> event is triggered. To this event, we bind the
updateTree() method.
The loadRoot() method inserts the root item into the treeview. We start with
the current working directory. The root node shows its full path; other files
show only the last part of their full path. The populateTree() method is called
to load the nodes for the current working directory.
def updateTree(self , e):
tree = e.widget
self.populateTree(tree , tree.focus ())
The method returns if the file is not a directory. It makes sense to load children
for directories only.
path = tree.set(node , "path")
The parent() method returns an empty string for top-level nodes. For other
nodes, it returns the iid of their parent.
sdirs = [] if parent else glob.glob('.') + glob.glob('..')
In the sdirs list, we store two special directories—the current working directory
and the parent directory of the current working directory.
CHAPTER 8. TREEVIEW 169
if os.path.isdir(p):
ptype = "directory"
elif os.path.isfile(p):
ptype = "file"
Except for the root node, all nodes display only the last part of their full path.
Note that we also store information in the two columns that are not visible; the
stored information is a full path name and the file type.
if ptype == 'directory ':
tree.insert(itemId , 0, text="dummy")
If a node is a directory, we add a dummy item. This makes the node expandable.
The dummy item is never visible; it is deleted when the node is opened.
elif ptype == 'file ':
tree = e.widget
node = tree.focus ()
...
if os.path.isdir(path ):
os.chdir(path)
tree.delete(tree.get_children(''))
self.loadRoot(tree)
To change to a new directory, we need its full path name. The full path name is
stored in the path column. We change to the new directory, delete the contents
of the treeview, and load a new root node.
def autoScroll(self , sbar , first , last ):
else:
sbar.grid ()
sbar.set(first , last)
The autoScroll() method shows the scrollbars only when they are needed. Both
horizontal and vertical scrollbars work with two values; these values fall between
0 and 1. If the scrollbar is not needed, it is hidden with the grid_remove()
method. The grid options are remembered and are applied later with the grid()
method.
Figure 8.9 shows the File browser program. The top item is the root node. The
directories have a triangle image next to them.
Chapter 9
Canvas
9.1 Lines
A line is a simple geometric primitive. The create_line() method creates a line
item on the Canvas.
def initUI(self ):
self.parent.title("Lines")
self.pack(fill=BOTH , expand=True)
canvas = Canvas(self)
canvas.create_line (15, 25, 200, 25)
canvas.create_line (300, 35, 300, 200, dash =(4, 2))
canvas.create_line (55, 85, 155, 85, 105, 180, 55, 85)
canvas.pack(fill=BOTH , expand=True)
The parameters of the create_line() method are the x and y coordinates of the
start and end points of the line.
canvas.create_line (300, 35, 300, 200, dash =(4, 2))
A vertical line is drawn. The dash option specifies the dash pattern of the line.
We have a line consisting of alternating segments of 4 px dash and 2 px space.
canvas.create_line (55, 85, 155, 85, 105, 180, 55, 85)
The create_line() method can take multiple points. This line draws a triangle.
171
CHAPTER 9. CANVAS 172
def initUI(self ):
self.parent.title("Joins")
self.pack(fill=BOTH , expand=True)
canvas = Canvas(self)
canvas.create_line (15, 25, 150, 25, 150, 80, 15, 80, 15, 25,
joinstyle=ROUND , width =10)
canvas.create_line (200, 25, 320, 25, 320, 80, 200, 80, 200, 25,
joinstyle=BEVEL , width =10)
canvas.create_line (15, 120, 150, 120, 150, 200, 15, 200, 15, 120,
joinstyle=MITER , width =10)
In the example, we show these three types of line joins. We draw three rectan-
gles.
canvas.create_line (15, 25, 150, 25, 150, 80, 15, 80, 15, 25,
joinstyle=ROUND , width =10)
A rectangle is drawn with the create_line() method. The line join is specified
with the joinstyle parameter. In order to see the joins more clearly, we increase
the width of the line.
CHAPTER 9. CANVAS 173
def initUI(self ):
self.parent.title("Caps")
self.pack(fill=BOTH , expand=True)
canvas = Canvas(self)
canvas.create_line (20, 30, 250, 30, capstyle=BUTT , width =8)
canvas.create_line (20, 80, 250, 80, capstyle=PROJECTING , width =8)
canvas.create_line (20, 130, 250, 130, capstyle=ROUND , width =8)
canvas.pack(fill=BOTH , expand=True)
We draw three vertical lines to explain the differences between the caps. Lines
with ROUND and PROJECTING are bigger than the line with BUTT. Exactly how
much bigger depends on the line size. In our case a line is 8 px thick. Lines are
CHAPTER 9. CANVAS 174
bigger by 8 px—4 px on the left and 4 px on the right. It should be clear from
the picture.
def initUI(self ):
self.parent.title("Cubic line")
self.pack(fill=BOTH , expand=True)
canvas = Canvas(self)
canvas.create_line (25, 35, 250, 350, 380, 35, smooth=True)
canvas.pack(fill=BOTH , expand=True)
9.5 Colours
A colour is an object representing a combination of Red, Green, and Blue (RGB)
intensity values.
def initUI(self ):
self.parent.title("Colours")
self.pack(fill=BOTH , expand =1)
canvas = Canvas(self)
canvas.create_rectangle (30, 10, 120, 80,
outline="#fb0", fill="#fb0")
canvas.create_rectangle (150, 10, 240, 80,
outline="#f50", fill="#f50")
canvas.create_rectangle (270, 10, 370, 80,
outline="#05f", fill="#05f")
canvas.pack(fill=BOTH , expand =1)
In the code example, we draw three rectangles and fill them with different colour
values.
canvas = Canvas(self)
The create_rectangle() creates a rectangle item on the canvas. The first four
parameters are the x and y coordinates of the two bounding points: the top-left
and bottom-right points. With the outline parameter we control the colour of
CHAPTER 9. CANVAS 176
the outline of the rectangle. Likewise, the fill parameter provides a colour for
the inside of the rectangle.
Figure 9.5 shows a three rectangles with three different colour fills.
9.6 Shapes
It is possible to draw various shapes on the canvas.
def initUI(self ):
self.parent.title("Shapes")
self.pack(fill=BOTH , expand=True)
canvas = Canvas(self)
canvas.create_oval (10, 10, 80, 80, outline="gray",
fill="gray", width =2)
canvas.create_oval (110, 10, 210, 80, outline="gray",
fill="gray", width =2)
canvas.create_rectangle (230, 10, 290, 60,
outline="gray", fill="gray", width =2)
canvas.create_arc (30, 200, 90, 100, start=0,
extent =210, outline="gray", fill="gray", width =2)
canvas.pack(fill=BOTH , expand=True)
Here the create_oval() method is used to create a circle item. The first four
parameters are the bounding box coordinates of the circle. In other words, they
are x and y coordinates of the top-left and bottom-right points of the box, in
which the circle is drawn.
CHAPTER 9. CANVAS 177
We create a rectangle item. The coordinates are again the bounding box of the
rectangle to be drawn.
canvas.create_arc (30, 200, 90, 100, start=0,
extent =210, outline="gray", fill="gray", width =2)
This code line creates an arc. An arc is a part of the circumference of the circle.
We provide its bounding box. The start parameter is the start angle of the arc.
The extent is the angle size.
points = [150, 100, 200, 120, 240, 180, 210,
200, 150, 150, 100, 200]
canvas.create_polygon(points , outline='gray ',
fill='gray ', width =2)
9.7 Image
In the following example we draw an image item on the canvas.
def initUI(self ):
self.parent.title("High Tatras")
self.pack(fill=BOTH , expand=True)
self.img = Image.open("tatras.jpg")
self.tatras = ImageTk.PhotoImage(self.img)
height=self.img.size [1]+20)
canvas.create_image (10, 10, anchor=NW , image=self.tatras)
canvas.pack(fill=BOTH , expand=True)
Tkinter does not support JPG images internally. As a workaround, we use the
Image and ImageTk modules.
We create the Canvas widget. It takes the size of the image into account. It is
20 px wider and 20 px higher than the actual image size.
canvas.create_image (10, 10, anchor=NW , image=self.tatras)
We use the create_image() method to create an image item on the canvas. The
image is anchored to the north-west. The image parameter provides the photo
image to display.
9.8 Text
Text is drawn on canvas with the create_text() method.
def initUI(self ):
self.parent.title("Lyrics")
CHAPTER 9. CANVAS 179
self.pack(fill=BOTH , expand=True)
canvas = Canvas(self)
canvas.create_text (20, 30, anchor=W, font="Purisa",
text="Most relationships seem so transitory")
canvas.create_text (20, 60, anchor=W, font="Purisa",
text="They 're good but not the permanent one")
canvas.create_text (20, 130, anchor=W, font="Purisa",
text="Who doesn 't long for someone to hold")
canvas.create_text (20, 160, anchor=W, font="Purisa",
text="Who knows how to love without being told")
canvas.create_text (20, 190, anchor=W, font="Purisa",
text="Somebody tell me why I'm on my own")
canvas.create_text (20, 220, anchor=W, font="Purisa",
text="If there 's a soulmate for everyone")
canvas.pack(fill=BOTH , expand=True)
The first two parameters are the x and y coordinates of the centre point of the
text. If we anchor the text item to the west, the text starts from this position.
The font parameter provides the font of the text and the text parameter is the
text to be displayed.
"""
ZetCode Tkinter e-book
class Example(Frame ):
self.parent = parent
self.initUI ()
def initUI(self ):
self.canvas = Canvas(self)
self.canvas.pack(fill=BOTH , expand=True)
self.drag_data["x"] = e.x
self.drag_data["y"] = e.y
CHAPTER 9. CANVAS 181
root = Tk()
ex = Example(root)
root.geometry("400 x250 +300+300")
root.mainloop ()
Two rectangle objects are created. It is possible to relocate them with a mouse.
self.drag_data = {"x": 0, "y": 0, "item": None}
Two rectangles are created on the canvas. The items have a draggable tag
defined.
self.canvas.tag_bind("draggable", "<ButtonPress -1>",
self.OnItemButtonPress)
self.canvas.tag_bind("draggable", "<B1 -Motion >",
self.OnItemMotion)
We add bindings for clicking and moving any draggable item on the canvas.
def OnItemButtonPress(self , e):
The first two lines of the OnItemMotion() method compute how much the current
object has moved.
self.canvas.move(self.drag_data["item"], delta_x , delta_y)
9.10 Arkanoid
Arkanoid is an arcade game developed by Atari Inc. In this game, the player
moves a bar and bounces a ball. The objective is to destroy bricks in the top of
the window.
"""
ZetCode Tkinter e-book
BAR_WIDTH = 60
BOTTOM_EDGE = 270
RIGHT_EDGE = 400
NEAR_BAR_Y = 248
BALL_INIT_X = 200
BALL_INIT_Y = 150
INIT_DELAY = 800
DELAY = 30
class Example(Frame ):
self.parent = parent
self.initVariables ()
self.initBoard ()
self.after(INIT_DELAY , self.onTimer)
def initVariables(self ):
self.bricks = []
self.ballvx = self.ballvy = 3
self.ball_x = BALL_INIT_X
self.ball_y = BALL_INIT_Y
self.inGame = True
self.bar_x = 170
self.bar_y = 250
CHAPTER 9. CANVAS 183
self.lives = 3
def initBoard(self ):
self.parent.title("Arkanoid")
self.pack(fill=BOTH , expand=True)
self.parent.config(cursor="none")
k = 0.0
for j in range (10):
for i in range (10):
self.canvas.pack(fill=BOTH , expand=True)
self.bar_x = e.x
def onTimer(self ):
if self.inGame:
self.doCycle ()
self.checkCollisions ()
self.after(DELAY , self.onTimer)
else:
self.gameOver ()
def doCycle(self ):
self.ball_x += self.ballvx
self.ball_y += self.ballvy
CHAPTER 9. CANVAS 184
if (len(self.bricks) == 0):
self.msg = "Game won"
self.inGame = False
def checkCollisions(self ):
hit = 0
co = self.canvas.coords(brick)
hit = 1
self.ballvy *= -1
hit = 1
self.ballvx *= -1
if (hit == 1):
self.bricks.remove(brick)
self.canvas.delete(brick)
self.ballvy *= -1
self.ballvx *= -1
self.ballvx *= -1
self.lives -= 1
self.canvas.delete(self.lives_item)
self.lives_item = self.canvas.create_text (15, 270,
text=self.lives , fill="white")
if self.lives == 0:
self.inGame = False
self.msg = "Game lost"
else:
self.ball_x = BALL_INIT_X
self.ball_y = BALL_INIT_Y
def gameOver(self ):
self.canvas.delete(ALL)
self.canvas.create_text(self.winfo_width ()/2,
self.winfo_height ()/2, text=self.msg , fill="white")
root = Tk()
ex = Example(root)
root.geometry("+300+300")
root.mainloop ()
In our game, we have one bar, one ball, and one hundred bricks. A timer is
used to create a game cycle. The player controls the bar with a mouse.
BAR_WIDTH = 60
BOTTOM_EDGE = 270
RIGHT_EDGE = 400
NEAR_BAR_Y = 248
BALL_INIT_X = 200
BALL_INIT_Y = 150
INIT_DELAY = 800
DELAY = 30
In the beginning, we define some constants. The BAR_WIDTH sets the size of the
bar object. The BOTTOM_EDGE and the RIGHT_EDGE determine the boundaries of
the game. The ball will bounce from this edges. The NEAR_BAR_Y is a place where
the ball hits the bar. The BALL_INIT_X and BALL_INIT_Y are initial coordinates of
the ball object. Finally, the INIT_DELAY and DELAY are initial delay and normal
delay of the game’s timer.
self.initVariables ()
self.initBoard ()
CHAPTER 9. CANVAS 186
self.after(INIT_DELAY , self.onTimer)
In the Example's constructor, we initiate game variables and the board and start
the timer. The timer is started with an initial delay using the after() method.
def initVariables(self ):
self.bricks = []
self.ballvx = self.ballvy = 3
self.ball_x = BALL_INIT_X
self.ball_y = BALL_INIT_Y
self.inGame = True
self.bar_x = 170
self.bar_y = 250
self.lives = 3
The initVariables() method initiates important variables. The bricks list holds
the brick objects. The ballvx and bavllvy are the horizontal and vertical speeds
of the ball. The ball_x and ball_y are the x and y coordinates of the ball’s top-
left point. The inGame controls whether we are playing the game. The bar_x
and bar_y are the x and y coordinates of the bar’s top-left point. Finally, the
lives determines how many lives we have; i.e. how may times we can miss the
ball.
def initBoard(self ):
self.parent.title("Arkanoid")
self.pack(fill=BOTH , expand=True)
self.parent.config(cursor="none")
...
Inside the initBoard() method, we initiate the game board. With the config()
method, we hide the cursor. The bar is controlled with the mouse and the icon
of the cursor would be misleading.
self.canvas = Canvas(self , width =400, height =300,
background="#000000")
self.canvas.bind("<Motion >", self.onMotion)
A Canvas is created; its background is set to black colour. We listen for mouse
motion events on the canvas.
self.bar = self.canvas.create_line(self.bar_x , self.bar_y ,
self.bar_x+BAR_WIDTH , self.bar_y ,
fill="#ffffff")
A bar object is created. It is a simple line in white colour. The line is created
with the create_line() method.
self.lives_item = self.canvas.create_text (15, 270,
text=self.lives , fill="white")
A text item is created at the bottom-left corner of the window. The item shows
the lives. It is created with the create_text() method.
CHAPTER 9. CANVAS 187
k = 0.0
for j in range (10):
for i in range (10):
These lines create one hundred bricks in different colours. They are stored in
the bricks list. A brick is a rectangle item created with the create_rectangle()
method. The colour values are generated with the HSV (Hue, Saturation, Value)
colour model.
self.ball = self.canvas.create_oval(self.ball_x -3,
self.ball_y -3, self.ball_x +3, self.ball_y +3,
fill="#cccccc")
self.bar_x = e.x
The onMotion() method is created in reaction to the mouse move event. Inside
the method, we update the x coordinate of the bar.
def onTimer(self ):
if self.inGame:
self.doCycle ()
self.checkCollisions ()
self.after(DELAY , self.onTimer)
else:
self.gameOver ()
Each DELAY ms, the onTimer() method is called; it controls the cycles of the
game, checks for collisions, or ends the game.
def doCycle(self ):
self.ball_x += self.ballvx
self.ball_y += self.ballvy
if (len(self.bricks) == 0):
self.msg = "Game won"
self.inGame = False
CHAPTER 9. CANVAS 188
Inside the doCycle() method, we update the ball’s coordinates and move the
ball and the bar with the coords() method. If there are no more bricks left, we
set the inGame variable to false.
def checkCollisions(self ):
In the checkCollisions() method we check for collisions in the game. The ball
changes its direction when it collides with the top, left, and right edges.
for brick in self.bricks:
hit = 0
co = self.canvas.coords(brick)
...
In this for loop, we check for collisions between the bricks and the ball. The
coords() method can be used to move the canvas item or to determine its
coordinates; in this case, we get the coordinates of the currently examined brick.
if (self.ball_x > co[0] and self.ball_x < co[2]
and self.ball_y + self.ballvy > co[1]
and self.ball_y + self.ballvy < co [3]):
hit = 1
self.ballvy *= -1
If the ball hits the top or the bottom edge of the brick, the hit variable is set
and the vertical direction of the ball changes.
if (self.ball_x + self.ballvx > co[0]
and self.ball_x + self.ballvx < co[2]
and self.ball_y > co[1]
and self.ball_y < co [3]):
hit = 1
self.ballvx *= -1
Likewise, if the ball hits the right or left edge of the brick, the hit variable is
set and the horizontal direction of the ball changes.
if (hit == 1):
self.bricks.remove(brick)
self.canvas.delete(brick)
self.ballvy *= -1
CHAPTER 9. CANVAS 189
If the ball hits the bar, its vertical direction changes; it bounces off the bar.
if (self.ball_y > NEAR_BAR_Y
and self.ball_x < self.bar_x + BAR_WIDTH /2
and self.ballvx > 0):
self.ballvx *= -1
self.ballvx *= -1
The horizontal changes of the direction of the ball depend on what part of the
bar the ball lands.
if (self.ball_y > BOTTOM_EDGE ):
self.lives -= 1
self.canvas.delete(self.lives_item)
self.lives_item = self.canvas.create_text (15, 270,
text=self.lives , fill="white")
if self.lives == 0:
self.inGame = False
self.msg = "Game lost"
else:
self.ball_x = BALL_INIT_X
self.ball_y = BALL_INIT_Y
If the ball passes the bottom edge, we loose a life. The lives text item is updated.
If there are no more lives left, the game is over. Otherwise, the ball reappears
in its initial position and the game continues.
def gameOver(self ):
self.canvas.delete(ALL)
self.canvas.create_text(self.winfo_width ()/2,
self.winfo_height ()/2, text=self.msg , fill="white")
In the gameOver() method, we delete all canvas items and create a final text
message. The message is ”Game won” or ”Game lost”, depending on our per-
formance in the game.
CHAPTER 9. CANVAS 190
191
Index
192
INDEX 193