Tkinter GUI Application Development Blueprints - Sample Chapter
Tkinter GUI Application Development Blueprints - Sample Chapter
Tkinter GUI Application Development Blueprints - Sample Chapter
$ 44.99 US
28.99 UK
P U B L I S H I N G
Bhaskar Chaudhary
ee
Sa
m
pl
C o m m u n i t y
E x p e r i e n c e
D i s t i l l e d
Bhaskar Chaudhary
Preface
Preface
Tkinter GUI Application Development Blueprints will walk you through the process
of developing real-world graphical applications using Python and Tkinter, the
built-in GUI module of Python.
This book attempts to highlight the features and capabilities of Tkinter while
demonstrating best practices involved in writing GUI programs, irrespective of the
library that you choose to build your application with. Here, you will learn how
to use Tkinter to develop exciting, fun, and useful GUI applications with Tkinter
and Python.
We hope to take you on a fun journey through more than 10 projects from
different problem domains. As we develop new applications in each project,
the book also builds up a catalog of some commonly used strategies to develop
real-world applications.
Preface
Meet Tkinter
Welcome to the exciting world of GUI programming with Tkinter. This chapter aims
at getting you acquainted with Tkinter, the built-in graphical user interface (GUI)
library for all standard Python distributions.
Tkinter (pronounced tea-kay-inter) is the Python interface to Tk, the GUI toolkit
for Tcl/Tk.
Tcl (short for Tool Command Language and pronounced as tickle) is a popular
scripting language in the domains of embedded applications, testing, prototyping,
and GUI development. On the other hand, Tk is an open source, multi-platform
widget toolkit that is used by many different languages to build GUI programs.
The Tkinter interface is implemented as a Python moduleTkinter.py in
Python 2.x versions and tkinter/__init__.py in Python 3.x versions. If you
look at the source code, Tkinter is just a wrapper around a C extension that uses
the Tcl/Tk libraries.
Tkinter is suitable for application to a wide variety of areas, ranging from small
desktop applications to use in scientific modeling and research endeavors across
various disciplines.
When a person learning Python needs to graduate to GUI programming, Tkinter
seems to be the easiest and fastest way to get the work done.
Tkinter is a great tool for the programming of GUI applications in Python.
The features that make Tkinter a great choice for GUI programming include
the following:
It is simple to learn (simpler than any other GUI package for Python)
Meet Tkinter
None of the other Python GUI toolkits have all of these features at the same time.
We believe that the concepts that you will develop here will enable you to apply and
develop GUI applications in your area of interest.
The key aspects that we want you to learn from this chapter include the following:
Chapter 1
Styling widgets by using styling options and configuring the root widget
[3]
Meet Tkinter
This should make a window pop up. The first line in the window reads This is
Tcl/Tk version 8.6. Make sure that it is not 8.5 or any earlier version, as Version 8.6
is a vast improvement over its previous versions.
You are ready to code Tkinter GUI applications if your version test confirms it as
Tcl/Tk version 8.6.
Let's get started!
Importing Tkinter
This section describes the different styles of importing Tkinter modules. In the
preceding example, we imported Tkinter by using the following command:
from tkinter import *
This method of importing eases the handling of methods defined in the module.
That is to say, you can simply access the methods directly. Generally, it is considered
bad practice to import all (*) the methods of a module like we did here. This is so
because this style of importing leads to memory flooding, namespace confusion,
and difficulty in bug tracking and/or reviewing code. Importing into the global
namespace can also lead to an accidental overwriting of methods from other
libraries in the global namespace.
There are several ways to import Tkinter in which this overlapping can be avoided,
with a common way being the following one:
import tkinter
This style of importing does not pollute the namespace with a list of all the methods
defined within Tkinter. However, every method within Tkinter will now have
to be called by using the tkinter.some_method format instead of directly calling
the method.
Another commonly used import style is as follows:
import tkinter as tk
Here too, you do not pollute the current namespace with all the Tkinter methods.
Now, you can access methods such as tk.some_method. The tk alias is convenient
and easy to type. It is commonly used by many developers to import Tkinter.
[4]
Chapter 1
Which components should appear on the screen? This involves choosing the
components that make the user interface. Typical components include things
such as buttons, entry fields, checkboxes, radio buttons, scroll bars, and the
like. In Tkinter, the components that you add to your GUI are called widgets.
Widgets (short for window gadgets) are the graphical components that make
up your application's frontend.
Where should the components go? This includes deciding the position and
the structural layout of various components. In Tkinter, this is referred to as
geometry management.
Let's delve deeper into each of these three components in the context of Tkinter.
[5]
Meet Tkinter
Drawing the root window is easy. You just need the following three lines of code:
from tkinter import *
root = Tk()
root.mainloop()
Save this with the .py file extension or check out the 1.01.py code. Open it in the
IDLE window and run the program from the Run menu (F5 in IDLE). Running
this program should generate a blank root window, as shown in the preceding
screenshot. This window is equipped with the functional minimize, maximize,
and close buttons, and a blank frame.
Downloading the example code
You can download the example code files for all Packt books you have
purchased from your account at http://www.packtpub.com. If you
purchased this book elsewhere, you can visit http://www.packtpub.
com/support and register to have the files e-mailed directly to you.
[6]
Chapter 1
The first line imported all (*) the classes, attributes, and methods of Tkinter
into the current workspace.
The second line created an instance of the tkinter.Tk class. This created
what is called the "root" window, which is shown in the preceding
screenshot. According to the conventions, the root window in Tkinter is
usually called "root", but you are free to call it by any other name.
The third line executed the mainloop (that is, the event loop) method of the
root object. The mainloop method is what keeps the root window visible.
If you remove the third line, the window created in line 2 will disappear
immediately as soon as the script stops running. This will happen so fast that
you will not even see the window appearing on your screen. Keeping the
mainloop method running also lets you keep the program running until you
press the close button, which exits the main loop.
Congratulations! You have completed your first objective, which was to draw the
root window. You have now prepared your drawing board (root window). Now, get
ready to paint it with your imagination!
Commit the three lines of code (shown in code 1.01.py) to memory.
These three lines generate your root window, which will accommodate
all the other graphical components. These lines form the skeleton of any
GUI application that you will develop in Tkinter. The entire code that will
make your GUI application functional will go between line 2 (new object
creation) and line 3 (mainloop) of this code.
[7]
Meet Tkinter
In the following example (1.02.py), we will add two widgets, a label, and a button
to the root frame. Note how all the widgets are added between the skeleton code that
we defined in the first example:
from tkinter import *
root = Tk()
label = Label(root, text="I am a label widget")
button = Button(root, text="I am a button")
label.pack()
button.pack()
root.mainloop()
This code added a new instance named label for the Label widget. The first
parameter defined root as its parent or container. The second parameter
configured its text option as I am a label widget.
We used the pack() method, which is essentially required to position the label
and button widgets within the window. We will discuss the pack() method
and several other related concepts when exploring the geometry management
task. However, you must note that some sort of geometry specification is
essential for the widgets to be displayed within the top-level window.
Running the preceding code will generate a window with a label and a button
widget, as shown in the following screenshot:
[8]
Chapter 1
All widgets are actually objects derived from their respective widget classes.
So, a statement such as button = Button(its_parent) actually creates a
button instance from the Button class.
Each widget has a set of options that decides its behavior and appearance.
This includes attributes such as text labels, colors, and font size. For example,
the Button widget has attributes to manage its label, control its size, change
its foreground and background colors, change the size of the border, and
so on.
To set these attributes, you can set the values directly at the time of creating
the widget, as demonstrated in the preceding example. Alternatively, you
can later set or change the options of the widget by using the .config()
or .configure() method. Note that the .config() or .configure()
methods are interchangeable and provide the same functionality. In fact,
the .config() method is simply an alias of the .configure() method.
You can either save a reference to the widget created (my_label, as in the first
example), or create a widget without keeping any reference to it (as demonstrated
in the second example).
You should ideally keep a reference to the widget in case the widget has to be accessed
later on in the program. For instance, this is useful in case you need to call one of its
internal methods or for its content modification. If the widget state is supposed to
remain static after its creation, you need not keep a reference to the widget.
[9]
Meet Tkinter
Note that calls to pack() (or other geometry managers) always return
None. So, consider a situation where you create a widget, save a reference
to it, and add a geometry manager (say, pack()) on the same line, as
follows:
my_label = Label(...).pack()
In this case, you are actually not creating a reference to the widget.
Instead, you are creating a None type object for the my_label variable.
So, when you later try to modify the widget through the reference, you
get an error because you are actually trying to work on a None type
object.
If you need a reference to a widget, you must create it on one line and
then specify its geometry (like pack()) on the second line, as follows:
my_label = Label(...)
my_label.pack()
Label widget
Button widget
Canvas widget
Checkbutton widget
Entry widget
Frame widget
LabelFrame widget
Listbox widget
Menu widget
Menubutton widget
Message widget
OptionMenu widget
PanedWindow widget
Radiobutton widget
Scale widget
Scrollbar widget
Spinbox widget
Text widget
Let's write a program to display all of these widgets in the root window.
[ 10 ]
Chapter 1
Can you spot the pattern that is common to each widget? Can you spot the
differences?
As a reminder, the syntax for adding a widget is as follows:
Widget-name(its_parent, **its_configuration_options)
Using the same pattern, let's add all the 21 core Tkinter widgets into a dummy
application (the 1.03.py code). We do not produce the entire code here. A
summarized code description for 1.03.py is as follows:
We add a Frame widget and name it menu_bar. Note that Frame widgets
are just holder widgets that hold other widgets. Frame widgets are great for
grouping widgets together. The syntax for adding a frame is the same as that
of all the other widgets:
frame = Frame(root)
frame.pack()
Keeping the menu_bar frame as the container, we add two widgets to it:
Menubutton
Menu
[ 11 ]
Meet Tkinter
We create another Frame widget and name it frame. Keeping frame as the
container/parent widget, we add the following seven widgets to it:
Label
Entry
Button
Checkbutton
Radiobutton
OptionMenu
Bitmap Class
We then proceed to create another Frame widget. We add six more widgets
to the frame:
Image Class
Listbox
Spinbox
Scale
LabelFrame
Message
Text
Scrollbar
We create another Frame widget and add two more widgets to it:
Canvas
PanedWindow
[ 12 ]
Chapter 1
pack: This is the one that we have used so far. It is simple to use for simpler
layouts, but it may get very complex for slightly complex layouts.
grid: This is the most commonly used geometry manager that provides a
Now, let's have a look at some examples of all the three geometry managers
in action.
[ 13 ]
Meet Tkinter
side: LEFT, TOP, RIGHT, and BOTTOM (these decide the alignment of
fill: X, Y, BOTH, and NONE (these decide whether the widget can grow in size)
Internal padding (ipadx and ipady) for the padding inside widgets and
external padding (padx and pady), which all default to a value of zero
the widget)
cardinal directions)
Let's take a look at a demo code that illustrates some of the pack features.
Two of the most commonly used pack options are fill and expand.
Here's the code snippet (code 1.04.py) that will generate a GUI like the one shown
in following screenshot:
The following is the code (1.04.py) that generates the preceding GUI:
from tkinter import *
root = Tk()
frame = Frame(root)
# demo of side and fill options
Label(frame, text="Pack Demo of side and fill").pack()
Button(frame, text="A").pack(side=LEFT, fill=Y)
Button(frame, text="B").pack(side=TOP, fill=X)
Button(frame, text="C").pack(side=RIGHT, fill=NONE)
Button(frame, text="D").pack(side=TOP, fill=BOTH)
[ 14 ]
Chapter 1
frame.pack()
# note the top frame does not expand nor does it fill in
# X or Y directions
# demo of expand options - best understood by expanding the root
widget and seeing the effect on all the three buttons below.
Label (root, text="Pack Demo of expand").pack()
Button(root, text="I do not expand").pack()
Button(root, text="I do not fill x but I expand").pack(expand = 1)
Button(root, text="I fill x and expand").pack(fill=X, expand=1)
root.mainloop()
When you insert the A button in the root frame, it captures the leftmost area
of the frame, expands, and fills the Y dimension. Because the fill option
is specified as fill=Y, it claims all the area that it wants and fills the Y
dimension of its frame container frame.
Because frame is itself packed with a plain pack() method with no mention
of a pack option, it takes the minimum space required to accommodate all of
its child widgets.
If you increase the size of the root window by pulling it down or sideways,
you will see that the all the buttons within frame do not fill or expand with
the root window.
The positioning of the B, C, and D buttons occurs on the basis of the side
and fill options specified for each of them.
The next three buttons (after B, C, and D) demonstrate the use of the expand
option. A value of expand=1 means that the button moves its place on
resizing the window. Buttons with no explicit expand options stay at their
place and do not respond to changes in the size of their parent container
(the root window in this case).
The best way to study this piece of code would be to resize the root window
to see the effect that it has on various buttons.
The anchor attribute (not used in the preceding code) provides a means to
position a widget relative to a reference point. If the anchor attribute is not
specified, the pack manager places the widget at the center of the available
space or the packing box. The other options that are allowed include the four
cardinal directions (N, S, E, and W) and a combination of any two directions.
Therefore, valid values for the anchor attribute are CENTER (the default
value), N, S, E, W, NW, NE, SW, and SE.
[ 15 ]
Meet Tkinter
The value for most of the Tkinter geometry manager attributes can
either be specified in capital letters without quotes (such as side=TOP,
anchor=SE) or in small letters within quotes (such as side='top',
anchor='se').
We will use the pack geometry manager in some of our projects. Therefore, it will be
worthwhile to get acquainted with pack and its options.
The pack manager is ideally suited for the following two kinds of situation:
For a complete pack reference, type the following command in the Python shell:
>>> import tkinter
>>> help(tkinter.Pack)
[ 16 ]
Chapter 1
[ 17 ]
Meet Tkinter
The following is the code (1.06.py) that generates the preceding GUI:
from tkinter import *
root = Tk()
Label(root, text="Username").grid(row=0, sticky=W)
Label(root, text="Password").grid(row=1, sticky=W)
Entry(root).grid(row=0, column=1, sticky=E)
Entry(root).grid(row=1, column=1, sticky=E)
Button(root, text="Login").grid(row=2, column=1, sticky=E)
root.mainloop()
Take a look at the grid position defined in terms of the row and column
positions for an imaginary grid table spanning the entire frame. See how the
use of sticky=W on both the labels makes them stick on the left-hand side,
thus resulting in a clean layout.
The width of each column (or the height of each row) is automatically
decided by the height or width of the widgets in the cell. Therefore, you
need not worry about specifying the row or column width as equal. You can
specify the width for widgets if you need that extra bit of control.
You can use the sticky=NSEW argument to make the widget expandable and
fill the entire cell of the grid.
In a more complex scenario, your widgets may span across multiple cells in the grid.
To make a grid to span multiple cells, the grid method offers handy options such as
rowspan and columnspan.
Furthermore, you may often need to provide some padding between cells in the
grid. The grid manager provides the padx and pady options to provide padding
that needs to be placed around a widget.
Similarly, the ipadx and ipady options are used for internal padding. These options
add padding within the widget itself. The default value of an external and internal
padding is 0.
Let's have a look at an example of the grid manager, where we use most of the
common arguments to the grid method, such as row, column, padx, pady, rowspan,
and columnspan.
[ 18 ]
Chapter 1
The following is the code (1.07.py) that generates the preceding GUI:
from tkinter import *
parent = Tk()
parent.title('Find & Replace')
Label(parent, text="Find:").grid(row=0, column=0, sticky='e')
Entry(parent, width=60).grid(row=0, column=1,
padx=2, pady=2, sticky='we', columnspan=9)
Label(parent, text="Replace:").grid(row=1, column=0, sticky='e')
Entry(parent).grid(row=1, column=1, padx=2, pady=2, sticky='we',
columnspan=9)
Button(parent, text="Find").grid(
row=0, column=10, sticky='e' + 'w', padx=2, pady=2)
Button(parent, text="Find All").grid(
row=1, column=10, sticky='e' + 'w', padx=2)
Button(parent, text="Replace").grid(row=2, column=10, sticky='e' +
'w', padx=2)
Button(parent, text="Replace All").grid(
row=3, column=10, sticky='e' + 'w', padx=2)
Checkbutton(parent, text='Match whole word only').grid(
row=2, column=1, columnspan=4, sticky='w')
Checkbutton(parent, text='Match Case').grid(
row=3, column=1, columnspan=4, sticky='w')
Checkbutton(parent, text='Wrap around').grid(
row=4, column=1, columnspan=4, sticky='w')
Label(parent, text="Direction:").grid(row=2, column=6, sticky='w')
Radiobutton(parent, text='Up', value=1).grid(
row=3, column=6, columnspan=6, sticky='w')
Radiobutton(parent, text='Down', value=2).grid(
row=3, column=7, columnspan=2, sticky='e')
parent.mainloop()
[ 19 ]
Meet Tkinter
Note how just 14 lines of the core grid manager code generate a complex layout
such as the one shown in the preceding screenshot. On the contrary, developing
this with the pack manager would have been much more tedious.
Another grid option that you can sometimes use is the widget.grid_forget()
method. This method can be used to hide a widget from the screen. When you use
this option, the widget still exists at its former location, but it becomes invisible.
The hidden widget may be made visible again, but the grid options that you had
originally assigned to the widget will be lost.
Similarly, there is a widget.grid_remove() method that removes the widget, except
that in this case, when you make the widget visible again, all of its grid options will
be restored.
For a complete grid reference, type the following command in the Python shell:
>>> import tkinter
>>> help(tkinter.Grid)
Now, we will delve into configuring a grid's column and row sizes.
Different widgets have different heights and widths. So, when you specify the
position of a widget in terms of rows and columns, the cell automatically expands
to accommodate the widget.
Normally, the height of all the grid rows is automatically adjusted to be the height of
its tallest cell. Similarly, the width of all the grid columns is adjusted to be equal to
the width of the widest widget cell.
If you then want a smaller widget to fill a larger cell or to stay at any one side of the
cell, you can use the sticky attribute on the widget to control this aspect.
However, you can override this automatic sizing of columns and rows by using the
following code:
w.columnconfigure(n, option=value, ...)
w.rowconfigure(N, option=value, ...)
[ 20 ]
AND
Chapter 1
Use these to configure the options for a given widget, w, in either the nth column or
the nth row, specifying values for the options, minsize, pad, and weight. Note that
the numbering of rows begins from 0 and not 1.
The options available are as follows:
Options
minsize
Description
This is the minimum size of a column or row in pixels. If there is no
widget in a given column or row, the cell does not appear in spite of this
minsize specification.
pad
This is the external padding in pixels that will be added to the specified
column or row over the size of the largest cell.
weight
[ 21 ]
Meet Tkinter
The other options that are commonly used with place include width and anchor
(the default is NW). Refer to the code in 1.08.py for a demonstration of the common
place options:
from tkinter import *
root = Tk()
# Absolute positioning
Button(root, text="Absolute Placement").place(x=20, y=10)
# Relative positioning
Button(root, text="Relative").place(
relx=0.8, rely=0.2, relwidth=0.5, width=10, anchor=NE)
root.mainloop()
You may not see much of a difference between the absolute and relative positions
simply by looking at the code or the window frame. However, if you try resizing the
window, you will observe that the button placed does not change its coordinates,
while the relative button changes its coordinates and size to accommodate the new
size of the root window.
For a complete place reference, type the following command in the Python shell:
>>> import tkinter
>>> help(tkinter.Place)
Chapter 1
The place manager is rarely used because, if you use it, you have to worry about
the exact coordinates. If you make a minor change to a widget, it is very likely that
you will have to change the X-Y values for other widgets as well, which can be
very cumbersome.
We will not use the place manager in our projects. However, knowing that options
for coordinate-based placement exist can be helpful in certain situations.
This concludes our discussion on geometry management in Tkinter.
In this section, you had a look at how to implement the pack, grid, and place
geometry managers. You also understood the strengths and weaknesses of each
geometry manager.
You learned that pack is suitable for a simple side-wise or top-down widget
placement. You also learned that the grid manager is best suited for the handling of
complex layouts. You saw examples of the place geometry manager and explored
the reasons behind why it is rarely used.
You should now be able to plan and execute different layouts for your programs
using these Tkinter geometry managers.
[ 23 ]
Meet Tkinter
Command binding
The simplest way to add functionality to a button is called command binding,
whereby a callback function is mentioned in the form of command = some_callback
in the widget option. Note that the command option is available only for a few
selected widgets.
Take a look at the following sample code:
def my_callback ():
# do something when button is clicked
After defining the above callback we can connect it to, say, a button with the command
option referring to the callback, as follows:
Button(root, text="Click me", command=my_callback)
Then, somewhere else in the code, we define a button with a command callback
that takes some arguments, as follows:
Button(root,text="Click", command=lambda: my_callback
('some argument'))
[ 24 ]
Chapter 1
Python borrows syntax from functional programming called the lambda function.
The lambda function lets you define a single-line, nameless function on the fly.
The format for using lambda is as follows:
lambda arg: #do something with arg in a single line
Here's an example:
square = lambda x: x**2
Event binding
Fortunately, Tkinter provides an alternative form of an event binding mechanism
called bind() to let you deal with different events. The standard syntax used to
bind an event is as follows:
widget.bind(event, handler, add=None)
When an event corresponding to the event description occurs in the widget, it calls
not only the associated handler that passes an instance of the event object as the
argument, but also the details of the event. If there already exists a binding for that
event for this widget, the old callback is usually replaced with the new handler, but
you can trigger both the callbacks by passing add='+' as the last argument.
[ 25 ]
Meet Tkinter
Let's look at an example of the bind() method (refer to the 1.09.py code file):
from tkinter import *
root = Tk()
Label(root, text='Click at different\n locations in the frame
below').pack()
def callback(event):
print dir(event)
print "you clicked at", event.x, event.y
frame = Frame(root, bg='khaki', width=130, height=80)
frame.bind("<Button-1>", callback)
frame.pack()
root.mainloop()
We define the callback(event) function. Note that it takes the event object
generated by the event as an argument.
From the attributes list generated by the object, we use two attributes,
When you run the preceding code (code 1.09.py), it produces a window, as shown
in following screenshot:
[ 26 ]
Chapter 1
When you left-click anywhere in the yellow-colored frame within the root window,
it outputs messages to the console. A sample message passed to the console is
as follows:
['__doc__', '__module__', 'char', 'delta', 'height', 'keycode',
'keysym', 'keysym_num', 'num', 'send_event', 'serial', 'state',
'time', 'type', 'widget', 'width', 'x', 'x_root', 'y', 'y_root']
You clicked at 63 36.
Event patterns
In the previous example, you learned how to use the <Button-1> event to denote a
left-click. This is a built-in pattern in Tkinter that maps it to a left-click event. Tkinter
has an exhaustive mapping scheme that perfectly identifies events such as this one.
Here are some examples to give you an idea of event patterns:
The event pattern
<Button-1>
<KeyPress-B>
<Alt-Control-KeyPress- KP_Delete>
[ 27 ]
Meet Tkinter
The event detail (optional): The mouse event detail is captured by the
number 1 for a left-click and the number 2 for a right-click. Similarly,
each key press on the keyboard is either represented by the key letter itself
(say, B in <KeyPress-B>) or by using a key symbol abbreviated as keysym.
For example, the up arrow key on the keyboard is represented by the keysym
value of KP_Up. For a complete keysym mapping, refer to https://www.tcl.
tk/man/tcl8.6/TkCmd/bind.htm.
Let's take a look at a practical example of the event binding on widgets (refer to code
The following is a modified snippet of code; it will give you an idea of the commonly
used event bindings:
widget.bind("<Button-1>", callback) #bind widget to left mouse
click
widget.bind("<Button-2>", callback) # bind to right mouse click
widget.bind("<Return>", callback)# bind to Return(Enter) Key
widget.bind("<FocusIn>", callback) #bind to Focus in Event
widget.bind("<KeyPress-A>", callback)# bind to keypress A
widget.bind("<KeyPress-Caps_Lock>", callback)# bind to CapsLock
keysym
widget.bind("<KeyPress-F1>", callback)# bind widget to F1 keysym
widget.bind("<KeyPress-KP_5>", callback)# bind to keypad number 5
widget.bind("<Motion>", callback) # bind to motion over widget
widget.bind("<Any-KeyPress>", callback) # bind to any keypress
[ 28 ]
Chapter 1
Rather than binding an event to a particular widget, you can also bind it to the top,
level window. The syntax remains the same except that now you call it on the root
instance of the root window like root.bind().
Class-level binding: You can also bind events at a particular class level.
This is normally used to set the same behavior for all instances of a particular
widget class.
The syntax for class-level binding is as follows:
w.bind_class(class_name, event, callback, add=None)
In the preceding example, all the entry widgets will be bound to the <Control-V>
event, which will call a method named paste (event).
[ 29 ]
Meet Tkinter
Event propagation
Most keyboard and mouse events occur at the operating system level.
It propagates hierarchically upwards from the source of the event
until it finds a window that has the corresponding binding. The event
propagation does not stop there. It propagates itself upwards, looking for
other bindings from other widgets, until it reaches the root window. If
it does reach the root window and no bindings are discovered by it, the
event is disregarded.
Once the variable is created, you can use it as a widget option, as follows:
Entry(root, textvariable=my_string)
Checkbutton(root, text="Remember Me", variable=ticked_yes)
Radiobutton(root, text="Option1", variable=group_choice,
value="option1") #radiobutton
Scale(root, label="Volume Control", variable=volume, from =0,
to=10) # slider
[ 30 ]
Chapter 1
Additionally, Tkinter provides access to the values of variables via the set() and
get() methods, as follows:
my_var.set("FooBar") # setting value of variable
my_var.get() # Assessing the value of variable from say a callback
A demonstration of the Tkinter variable class is available in the 1.11.py code file.
The code generates a window, as shown in the following screenshot:
This concludes our brief discussion on events and callbacks. Here's a brief summary
of the things that we discussed:
The use of the lambda function in case you need to process arguments
Using the Tkinter variable class to set and get the values of widget-specific
variables
In short, you now know how to make your GUI program responsive to
end-user requests!
[ 31 ]
Meet Tkinter
unbind: Tkinter provides the unbind option to undo the effect of an earlier
binding. The syntax is as follows:
widget.unbind(event)
Virtual events: Tkinter also lets you create your own events. You can give
these virtual events any name that you want.
For example, let's suppose that you want to create a new event called
widget.event_add('<<commit>>', '<KeyRelease-F9>')
Other event-related methods can be accessed by typing the following line in the
Python terminal:
>>> import tkinter
>>> help(tkinter.Event)
Now that you are ready to dive into real application development with Tkinter, let's
spend some time exploring a few custom styling options that Tkinter offers. We will
also have a look at some of the configuration options that are commonly used with
the root window.
Doing it in style
So far, we have relied on Tkinter to provide specific platform-based styling for our
widgets. However, you can specify your own styling of widgets, such as their color,
font size, border width, and relief. A brief introduction of styling features that are
available in Tkinter is covered in the following section.
[ 32 ]
Chapter 1
You may recall that we can specify the widget options at the time of its instantiation,
as follows:
my_button = Button(parent, **configuration options)
Alternatively, you can specify the widget options by using configure () in the
following way:
my_button.configure(**options)
The styling options are also specified as options to the widgets either at the time of
creating the widgets, or later by using the configure option.
Specifying styles
Under the purview of styling, we will cover how to apply different colors, fonts,
border widths, reliefs, cursors, and bitmap icons to widgets.
First, let's see how to specify the color options for a widget. You can specify the
following two types of color for most widgets:
You can specify the color by using hexadecimal color codes for the proportion of red,
green, and blue. The commonly used representations are #rgb (4 bits), #rrggbb (8
bits), and #rrrgggbbb (12 bits).
For example, #fff is white, #000000 is black, #f00 is red (R=0xf, G=0x0, B=0x0),
#00ff00 is green (R=0x00, G=0xff, B=0x00), and #000000fff is blue (R=0x000,
G=0x000, B=0xfff).
Alternatively, Tkinter provides mapping for standard color names. For a list of
predefined named colors, visit http://wiki.tcl.tk/37701 or http://wiki.tcl.
tk/16166.
Next, let's have a look at how to specify fonts for our widgets. A font can be
represented as a string by using the following string signature:
{font family} fontsize fontstyle
[ 33 ]
Meet Tkinter
font family: This is the complete font family long name. It should
preferably be in lowercase, such as font="{nimbus roman} 36 bold
italic".
The following are the examples that illustrate the method of specifying fonts:
widget.configure (font='Times 8')
widget.configure(font='Helvetica 24 bold italic')
If you set a Tkinter dimension in a plain integer, the measurements take place in
pixel units. Alternatively, Tkinter accepts four other measurement units, which are
m (millimeters), c (centimeters), i (inches), and p (printer's points, which are about
1/72").
For instance, if you want to specify the wrap length of a button in terms of a printer's
point, you can specify it as follows:
button.configure(wraplength="36p")
The default border width for most Tkinter widgets is 2 px. You can change the border
width of the widgets by specifying it explicitly, as shown in the following line:
button.configure(borderwidth=5)
The relief style of a widget refers to the difference between the highest and lowest
elevations in a widget. Tkinter offers six possible relief stylesflat, raised, sunken,
groove, solid, and ridge:
button.configure(relief='raised')
Tkinter lets you change the style of the mouse cursor when you hover over a
particular widget. This is done by using the option cursor, as follows:
button.configure(cursor='cross')
TkCmd/cursors.htm.
[ 34 ]
Chapter 1
Though you can specify the styling options at each widget level, sometimes it may
be cumbersome to do so individually for each widget. Widget-specific styling has
the following disadvantages:
It mixes logic and presentation into one file, making the code bulky and
difficult to manage
Fortunately, Tkinter now offers a way to separate presentation from logic and specify
styles in what is called the external option database. This is nothing but a text file
where you can specify the common styling options.
A typical option database text file looks like this:
*background: AntiqueWhite1
*Text*background: #454545
*Button*foreground: gray55
*Button*relief: raised
*Button*width: 3
In its simplest use, the asterisk (*) symbol here means that the particular style is
applied to all the instances of the given widget. For a more complex usage of the
asterisk in styling, refer to http://infohost.nmt.edu/tcc/help/pubs/tkinter/
web/resource-lines.html.
These entries are placed in an external text (.txt) file. To apply this styling to a
particular piece of code, you can simply call it by using the option_readfile()
call early in your code, as shown here:
root.option_readfile('optionDB.txt')
Let's have a look at an example (see code 1.12.py) of using this external styling text
file in a program:
from tkinter import *
root = Tk()
root.configure(background='#4D4D4D')#top level styling
# connecting to the external styling optionDB.txt
root.option_readfile('optionDB.txt')
#widget specific styling
mytext = Text(root, background='#101010', foreground="#D6D6D6",
borderwidth=18, relief='sunken',width=17, height=5)
[ 35 ]
Meet Tkinter
mytext.insert(END, "Style is knowing who you are, what you want to
say, and not giving a damn.")
mytext.grid(row=0, column=0, columnspan=6, padx=5, pady=5)
# all the below widgets derive their styling from optionDB.txt
file
Button(root, text='*').grid(row=1, column=1)
Button(root, text='^').grid(row=1, column=2)
Button(root, text='#').grid(row=1, column=3)
Button(root, text='<').grid(row=2, column=1)
Button(root, text='OK', cursor='target').grid(row=2,
column=2)#changing cursor style
Button(root, text='>').grid(row=2, column=3)
Button(root, text='+').grid(row=3, column=1)
Button(root, text='v').grid(row=3, column=2)
Button(root, text='-').grid(row=3, column=3)
for i in range(9):
Button(root, text=str(i+1)).grid(row=4+i//3, column=1+i%3)
root.mainloop()
The next segment of code creates a Text widget and specifies styling on the
widget level.
The next segment of code has several buttons, all of which derive their
styling from the centralized optionDB.txt file. One of the buttons also
defines a custom cursor.
Specifying attributes such as font sizes, the border width, the widget width, the
widget height, and padding in absolute numbers, as we have done in the preceding
example, can cause some display variations between different operating systems such
as Ubuntu, Windows, and Mac respectively, as shown in the following screenshot.
This is due to differences in the rendering engines of different operating systems.
[ 36 ]
Chapter 1
Description
You can specify the size and location of
a root window by using a string of the
widthxheight + xoffset + yoffset form.
This changes the title bar icon to something
that is different from the default Tk icon.
or
self.root.
iconbitmap('mynewicon.ico ')
[ 37 ]
Meet Tkinter
Method
root.overrideredirect(1)
Description
This removes the root border frame. It
hides the frame that contains the minimize,
maximize, and close buttons.
self.root.wm_iconbitmap('my_icon.ico') or self.root.
iconbitmap('my_icon.ico '): This option is only applicable to Windows.
window limits the launch size of the root window. If the widgets do not fit in
the specified size, the widgets get clipped from the window. It is often better
not to specify this and let Tkinter decide this for you.
This provides a detailed help documentation of all the methods defined under the
Pack class in Tkinter.
You can similarly receive help for all the other individual widgets. For instance, you
can check the comprehensive and authoritative help documentation for the Label
widget in the interactive shell by typing the following command:
>>>help(tkinter.Label)
All the standard and widget-specific options for the Label widget
[ 38 ]
Chapter 1
Finally, when in doubt regarding a method, look into the source code of Tkinter,
which is located at <location-of-python-installation>\lib\ directory. For
instance, the Tkinter source code is located in the /usr/lib/python3.4/tkinter
directory on my Ubuntu 14.04 operating system.
You might also find it useful to look at the source code implementation of
various other modules, such as the color chooser, file dialogs, ttk module,
and the other modules located in the aforementioned directory.
Summary
This brings us to end of Chapter 1, Meet Tkinter. This chapter is aimed to provide
a high-level overview of Tkinter. We worked our way through all the important
concepts that drive a Tkinter program.
You now know what a root window is and how to set it up. You also know the
21 core Tkinter widgets and how to set them up. We also had a look at how to lay
out our programs by using the pack, grid, and place geometry managers, and make
our programs functional by using events and callbacks. Finally, you saw how to
apply custom styles to GUI programs.
To summarize, we can now start thinking of making interesting, functional, and
stylish GUI programs with Tkinter!
[ 39 ]
www.PacktPub.com
Stay Connected: