Welcome to the exciting world of GUI programming with Tkinter. This project aims at getting you acquainted with Tkinter, the built-in graphical user interface (GUI) interface for all standard Python distributions.
Tcl (pronounced "tickle" and is an acronym for Tool Command Language) is a popular scripting language in the domains of embedded applications, testing, prototyping, and GUI development. Tk on the other hand is an open source, multiplatform widget toolkit that is used by many different languages for building GUI programs.
The Tkinter interface is implemented as a Python module,
Tkinter.py, which is just a wrapper around a C-extension that uses Tcl/Tk libraries.
Tkinter is suited for application to a wide variety of areas ranging from small desktop applications, to use in scientific modeling and research endeavors across various disciplines.
We believe that the concepts you will develop here will enable you to apply and develop GUI applications in your area of interest. Let's get started!
By the end of this project, you will have developed several partly functional dummy applications such as the one shown as follows:
The applications developed in this project are "dummy applications" because they are not fully functional. In fact, the purpose of each small dummy application is to introduce you to some specific aspects of programming with Tkinter. This will set up the context for developing some fun and fully functional project ideas from Project 2, Making a Text Editor, onwards.
The ability to program a GUI application (as opposed to a simple console application) opens a whole world of possibilities for a programmer. It shifts the focus of the program from the programmer to the end user, enabling the programmer to reach out to a wider audience.
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 programming GUI applications in Python.
The features that make Tkinter a great choice for GUI programming include:
It is simple to learn (simpler than any other GUI package for Python)
Relatively little code can produce powerful GUI applications
Layered design ensures that it is easy to grasp
It is portable across all operating systems
It is easily accessible as it comes pre-installed with standard Python distribution
Understanding the concept of root window and main loop
Understanding widgetsâthe building blocks for your programs
Acquainting yourself with a list of available widgets
Developing layouts using three geometry managers: pack, grid, and place
Learning to apply events and callbacks to make your program functional
Styling your widgets with styling options and configuring the root widget
The Python download package and instructions for downloading for different platforms are available at http://www.Python.org/getit/releases/2.7.3/.
We will develop our application on the Windows 7 platform. However, since Tkinter is truly cross-platform, you can follow along on Mac or Linux distributions without any modifications to our code.
After the installation, open the IDLE window and type:
>>>from Tkinter import *
If you have installed Python 2.7, this shell command should execute without any errors.
If there are no error messages the Tkinter module is installed in your Python distribution. When working with examples from this book, we do not support any other Python version except for Python 2.7, which comes bundled with Tkinter Tcl/Tk Version 8.5.
To test if you have the correct Tkinter version on your Python installation, type the following commands in your IDLE or interactive shell:
>>> import Tkinter >>>Tkinter._test()
This should pop up a window where the first line in the window reads This is Tcl/Tk version 8.5. Make sure it is not 8.4 or any earlier version, as Version 8.5 is a vast improvement over its previous versions.
You are ready to code your Tkinter GUI applications if your version test confirms it as Tcl/Tk version 8.5. Let's get started!
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 code
1.01.py. 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 furnished with 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.
The description of the code is as follows:
The first line imports all (
*) classes, attributes, and methods of Tkinter into the current workspace.
The second line creates an instance of the class
Tkinter.Tk. This creates what is called the "root" window that you see in the screenshot provided. By convention, the root window in Tkinter is usually called "root", but you are free to call it by any other name.
The third line executes the
mainloop(that is, the event loop) method of the
mainloopmethod is what keeps the root window visible. If you remove the third line, the window created in line 2 will disappear immediately 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 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 canvas (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 other graphical components. These lines constitute the skeleton of any GUI application that you will develop in Tkinter. All code that will make your GUI application functional will go between line 2 (new object creation) and line 3 (
mainloop) of this code.
This section describes the different styles of importing Tkinter modules.
In the preceding example, we imported Tkinter using the following command:
from Tkinter import *
This method of import eases the handling of methods defined in the module. That is to say, you can simply access the methods directly. Generally, it is considered a bad practice to import all (
*) methods of a module like we did here. This is because if you import all methods from some other module with a common method name, it would lead to the overwriting of methods.
There are several ways to import Tkinter in which this overlapping can be avoided, a common one being:
This style of importing does not pollute the namespace with a list of all methods defined within Tkinter. However, every method within Tkinter will now have to be called using the format
Tkinter.methodA 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 Tkinter methods and now you can access methods such as
Tk.methodA. "Tk" is a convenient, easy-to-type alias commonly used by many developers for importing Tkinter.
As a GUI programmer, you will generally be responsible for deciding three aspects of your program:
What components should appear on 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.
Where should the components go?: This involves deciding the positioning or placement of each component in the overall design structure. This includes decisions to be made on issues of positioning and the structural layout of various components. In Tkinter, this is referred to as geometry management.
How do components interact and behave?: This involves adding functionality to each component. Each component or widget does some work. For example, a button, when clicked on, does something in response; a scrollbar handles scrolling; and checkboxes and radio buttons enable the user to make some choices. In Tkinter, the functionality of various widgets is managed by
eventbinding using callback functions.
Let us delve deeper into each of these three components in the context of Tkinter.
mywidget = Widget-name (its container window,**configuration options)
In the following example (refer to the code
01.02.py), we add two widgets, a label and a button, to the root frame. Notice how all widgets are added in between the skeleton code we defined in the first example.
from Tkinter import * root = Tk() mylabel = Label(root,text="I am a label widget") mybutton = Button(root,text="I am a button") mylabel.pack() mybutton.pack() root.mainloop()
The description of the code is listed as follows:
We use 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 under the Geometry management task. However, you must note that some sort of geometry specification is essential for the widgets to display within the Toplevel window.
Running this code will generate a window as shown in the following screenshot. It will have a custom label and a custom button:
In this iteration, we have learned the following:
What widgets are.
How widgets are instantiated and displayed within a container window frame.
How to set options for the widgets at the time of instantiation.
The importance of specifying a geometry option such as
pack()to display a widget. We will discuss more about this in a subsequent task.
Each widget has a set of options that decides its behavior and appearance. This includes attributes such as text labels, colors, font size, and many more. 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 creation of the widget as we have done in the preceding example. Alternatively, you can later set or change the options of the widget by using the
.configure()method. Note that the
.configure()method are interchangeable and provide the same functionality.
mylabel = Label(root,text="I am a label widget") mylabel.pack()
If you are instantiating the widget directly, you can write both the lines together as follows:
Label(root,text="I am a label widget").pack()
You may keep a reference to the widget created (as in the first example,
mylabel) or you can create a widget without keeping any reference to it (as in the second example).
You should ideally keep the reference if the widget content is likely to be modified by some action at a later stage in the program. If the widget state is to remain static after its creation, you need not keep a reference for the widget.
Also, note that calls to
pack() (or other geometry managers) always returns
None. So, consider you create a widget keeping a reference to it and add the geometry manager (say
pack()) on the same line as shown:
mylabel = Label(â¦).pack()
In this case, you are actually not creating a reference to the widget but instead creating a
None type object for the variable
So, when you later try to modify the widget through the reference, you get an error as you are actually trying to work on a
None type object.
This is one of the most common errors committed by beginners.
Tkinter includes 21 core widgets. These are as follows:
Let's write a program to include these widgets on our root window.
Label(parent, text=" Enter your Password:") Button(parent, text="Search") Checkbutton(parent, text='RememberMe', variable=v, value=True) Entry(parent, width=30) Radiobutton(parent, text=Male, variable=v, value=1) Radiobutton(parent, text=Female, variable=v, value=2) OptionMenu(parent, var, "Select Country", "USA", "UK", "India", Others") Scrollbar(parent, orient=VERTICAL, command=mytext.yview)
Can you spot the pattern common to each widget? Can you spot the differences?
As a reminder, the syntax for adding a widget is:
Widget-name (its container window, *configuration options)
The method for creating all the previously mentioned widgets is the same. Most of the configuration options will also be similar. However, a few configuration options vary from widget to widget.
For example, the Button and Label widgets will have an option to configure their text, but scrollbars do not have a text-configuration option.
Do not be intimidated by the size of the program. Instead look for a common pattern that is used to initialize and display all the widgets. To reiterate, the syntax for adding a widget is:
mywidget = Widget-name (container, all widget-options)
Notice how the configuration options for each widget differ slightly from each other depending on the type of widget being initialized.
Refer to the code
1.03.py for a demo of all Tkinter widgets. A summarized code description for
1.03.py is as follows:
We create a Toplevel window and create a main loop as seen in the earlier examples.
We add a Frame widget that we named
menubar. 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 other widgets:
myframe = Frame(root) myframe.pack()
menubarframe as the container, we add two widgets to it, the Menubutton and Menu widgets.
We create another frame and name it
myframe1as the container/parent widget, we add seven widgets to it:
The Label, Entry, Button, Checkbutton, Radiobutton, OptionMenu, and Bitmap Class widgets.
The Image Class, Listbox, Spinbox, Scale, LabelFrame, and Message widgets.
We then create
myframe3, another Frame widget. We add two more widgets to it, the Text and Scrollbar widgets.
Finally we create the last frame,
myframe4, another Frame widget. We add two more widgets to it, the Canvas and PanedWindow widgets.
All these widgets constitute the 21 core widgets of Tkinter.
Read through the code explanation, and find the corresponding piece of code in the example code
01.03.py. Look at how each widget is created. Try to identify each widget's class name as used in Tkinter. Look what remains the same in all widgets, and what changes between one widget and another?
A few minutes spent reading and understanding the code in
1.03.py will really help you appreciate the simplicity and overall structure of a Tkinter program.
Finally, note that we have used
.pack() on each widget to display it inside its container frame. We discuss
.pack() in the next task. However, for now just note that we have used something called
pack(), without which the widgets would not have displayed at all.
You have reached a major milestone in your GUI programming effort.
You now know all the 21 core widgets of Tkinter. You can identify them by their class names, and you can create them on a root frame or on a subframe within the root. You now know how to configure options of widgets.
With this you have now seen the first and the most important building block of a Tkinter program. You have mastered Tkinter widgets.
Widget options can be set at instantiation time as we have done in the examples so far. Alternatively, the options can be configured after instantiation using the following syntax:
This is a very handy tool that lets you change widget options dynamically after the widget has been created. We will be using this very often in all our projects.
For common widget configuration options, refer to the Options common to widgets section in Appendix B, Quick Reference Sheets.
Having seen all the core Tkinter widgets, let us now turn our attention to the second component of GUI programmingâthe question of where to place those widgets.
This is taken care of by the geometry manager options of Tkinter. This component of GUI programming involves deciding the position of the widget, overall layout, and relative placement of various widgets on the screen.
Recall that we used the
pack() method for adding widgets to the dummy application we developed in the previous section.
pack() is an example of geometry management in Tkinter.
pack() is not the only way you can manage the geometry in your interface. In fact, there are three geometry managers in Tkinter that let you specify the position of widgets inside a Toplevel or parent window.
The geometry managers are as follows:
Let us now see examples of all three geometry managers in action.
The unclaimed space
The claimed but unused space
The claimed and used space
BOTTOM(these decide the alignment of the widget)
NONE(these decide whether the widget can grow in size)
No(corresponding to values respectively)
CENTER(corresponding to the cardinal directions)
Internal padding (
ipady) and external padding (
pady), which all defaulted to a value of zero
from Tkinter import * root = Tk() Button(root, text="A").pack(side=LEFT, expand=YES, fill=Y) Button(root, text="B").pack(side=TOP, expand=YES, fill=BOTH) Button(root, text="C").pack(side=RIGHT, expand=YES, fill=NONE, anchor=NE) Button(root, text="D").pack(side=LEFT, expand=NO, fill=Y) Button(root, text="E").pack(side=TOP, expand=NO, fill=BOTH) Button(root, text="F").pack(side=RIGHT, expand=NO, fill=NONE) Button(root, text="G").pack(side=BOTTOM, expand=YES, fill=Y) Button(root, text="H").pack(side=TOP, expand=NO, fill=BOTH) Button(root, text="I").pack(side=RIGHT, expand=NO) Button(root, text="J").pack(anchor=SE) root.mainloop()
When you insert button A in the
rootframe, it captures the left-most area of the frame, it expands, and fills the Y dimension. Because expand and fill options are specified in affirmative, it claims all the area it wants and fills the Y dimension. If you increase the size of the root window pulling it down, you will notice that the button A expands in the downward direction (along the Y coordinate) but a side-wise increase in the window does not result in a horizontal increase in the size of button A.
When you insert the next button, B, into the root window, it picks up space from the remaining area but aligns itself to
TOP, expand-fills the available area, and fills both X and Y coordinates of the available space.
The third button, C, adjusts to the right-hand side of the remaining space. But because fill is specified as
NONE, it takes up only that much space as is required to accommodate the text inside the button. If you expand the root window, the button C will not change its size.
anchorattribute used in some lines provides a means to position a widget relative to a reference point. If the
anchorattribute is not specified, the
packmanager places the widget in the center of the available space or the packing box . Other allowed options include the four cardinal directions (
W) and a combination of any two directions. Therefore, valid values for the
The description for the rest of the lines is left as an exercise for you to explore. The best way to study this piece of code would be to comment out all lines of code and introduce each successive button one after another. At each step, try to resize the window to see the effect it has on various buttons.
Note that the value for most of the Tkinter geometry manager attributes can either be specified in capital letters without quotes (like
anchor=SE) or in small letters but within quotes (like
For a complete
pack manager reference refer to the The pack manager section in Appendix B, Quick Reference Sheets.
Where should you use the pack() geometry manager?
pack manager is somewhat complicated compared to the
grid method that we will discuss next, but it is a great choice in situations such as:
Having a widget fill the complete container frame
Placing several widgets on top of each other or in a side by side position (as in the previous screenshot). See code
While you can create complicated layouts by nesting widgets in multiple frames, you can find the
grid geometry manager more suitable for most of the complex layouts.
grid geometry manager is most easy to understand and, perhaps, the most useful geometry manager in Tkinter. The central idea of the
grid geometry manager is to divide the container frame into a two-dimensional table divided into a number of rows and columns. Each cell in the table can then be targeted to hold a widget. In this context, a cell
is an intersection of imaginary rows and columns. Note that in the
grid method, each cell can hold only one widget. However, widgets can be made to span multiple cells.
Within each cell you can further align the position of the widget using the
STICKY option. The
sticky option decides how the widget is expanded, if its container cell is larger than the size of the widget it contains. The
sticky option can be specified using one or more of the
Not specifying stickiness defaults to stickiness to the center of the widget in the cell.
Let us now see a demo code that illustrates some of the features of the
grid geometry manager. The code in
1.06.py generates a GUI-like figure as shown:
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()
The description of the code is listed as follows:
Take a look at the grid position defined in terms of rows and column positions for an imaginary grid table spanning the entire frame. See how the use of
sticky=Won both labels makes them stick to the west or left-hand side, resulting in a clean layout.
The width of each column (or height of each row) is automatically decided by the height or width of the widgets contained in the cell. Therefore, you need not worry about specifying the row or column width as equal. You may specify the width for widgets, if you need that extra bit of control.
In a more complex scenario, your widgets may span across multiple cells in the grid. To enable a grid to span multiple cells, the
grid method offers very handy options such as
Furthermore, you may often need to provide some padding between cells in the grid. The
grid manager provides
pady options to provide padding to place around the widget in a cell.
Similarly, there are
ipady options for internal padding. The default value of external and internal padding is
Let us see an example of the
grid manager, where we use most of the common arguments to the
grid method such as
columnspan in action.
from Tkinter import * top = Tk() top.title('Find & Replace') Label(top,text="Find:").grid(row=0, column=0, sticky='e') Entry(top).grid(row=0,column=1,padx=2,pady=2,sticky='we',columnspan=9) Label(top, text="Replace:").grid(row=1, column=0, sticky='e') Entry(top).grid(row=1,column=1,padx=2,pady=2,sticky='we',columnspan=9) Button(top, text="Find").grid(row=0, column=10, sticky='ew', padx=2, pady=2) Button(top, text="Find All").grid(row=1, column=10, sticky='ew', padx=2) Button(top, text="Replace").grid(row=2, column=10, sticky='ew', padx=2) Button(top, text="Replace All").grid(row=3, column=10, sticky='ew', padx=2) Checkbutton(top, text='Match whole word only').grid(row =2, column=1, columnspan=4, sticky='w') Checkbutton(top, text='Match Case').grid(row =3, column=1, columnspan=4, sticky='w') Checkbutton(top, text='Wrap around').grid(row =4, column=1, columnspan=4, sticky='w') Label(top, text="Direction:").grid(row=2, column=6, sticky='w') Radiobutton(top, text='Up', value=1).grid(row=3, column=6, columnspan=6, sticky='w') Radiobutton(top, text='Down', value=2).grid(row=3, column=7, columnspan=2, sticky='e') top.mainloop()
Notice how just 14 lines of core
grid manager code generates a complex layout such as the one shown in the following screenshot. In contrast, developing this with the
pack manager would have been much more tedious:
grid option that you can sometimes use is the
widget.grid_forget() method. This method can be used to hide the widget from the screen. When you use this option, the widget exists in its place but becomes invisible. The hidden widget may be made visible again but any
grid options that you had originally assigned to the widget will be lost.
For a complete
grid() reference, refer to the the The grid manager section in Appendix B, Quick Reference Sheets.
Where should you use the grid() geometry manager?
grid manager is a great tool for developing complex layouts. Complex structures can be easily achieved by breaking the container widget into grids of rows and columns and then placing the widgets in grids where they are wanted.
It is also commonly used in developing different kinds of dialog boxes.
Now we will delve into configuring grid 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 grid rows is automatically adjusted to be the height of its tallest cell. Similarly, the width of all grid columns is adjusted to be equal to the width of the widest widget cell.
You can, however, override this automatic sizing of columns and rows using the following code:
w.columnconfigure(n, option=value, ...) AND w.rowconfigure(N, option=value, ...)
Use these to configure the options for a given widget,
w, in the column,
n, specifying values for the options,
The options available here are as mentioned in the following table:
The minimum size of column or row in pixels. If there is no widget in the given column or row, the cell does not appear despite this
External padding in pixels that will be added to the specified column or row over the size of largest cell.
For example, the following code distributes two-fifths of the extra space to the first column and three-fifths to the second column:
w.columnconfigure(0, weight=2) w.columnconfigure(1, weight=3)
rowconfigure() methods are often used to implement dynamic resizing of widgets, especially on resizing the root window.
place geometry manager is the most rarely used geometry manager in Tkinter. Nevertheless, it has its uses in that it lets you precisely position widgets within its parent frame using the X-Y coordinate system.
place manager can be assessed using the
place() method on all standard widgets.
The important options for
place geometry include:
Absolute positioning (specified in terms of
Relative positioning (key options include
Other options commonly used with
anchor (the default is
NW). Refer to the code in
1.09.py for a demonstration of the common
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 absolute and relative positions simply by looking at the code or the window frame. If, however, you try resizing the window, you will notice that the button placed absolutely does not change its coordinates, while the relative button changes its coordinates and size to fit the new size of the root window.
For a complete
place() reference, check out the The place manager section in Appendix B, Quick Reference Sheets.
When should you use the place manager?
grid() managers cannot be used together in the same frame, the
place() manager can be used with any other geometry manager within the same container frame.
place manager is rarely used. This is because if you use it you have to worry about the exact coordinates. If say you make a minor change for one 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 implemented examples of
place geometry managers. You also understood the strength and weaknesses of each geometry manager.
You learned that
pack is best for a simple side-wise or top-down widget placement. You also saw that the
grid manager is best suited for handling complex layouts. You saw examples of the
place geometry manager and the reasons why it is rarely used.
Now that we have learned how to add widgets to our screen and how to position them where we want, let's turn our attention to the third component of GUI programming. This addresses the question of how to make the widgets functional.
Making the widgets functional involves making them responsive to events such as the pressing of buttons, the pressing keys on keyboards, mouse clicks, and the like. This requires associating callbacks to specific events.
Take a look at the following sample code:
def my_callback (): # do something Button(root,text="Click",command= my_callback)
my_callback is called without parentheses
() from within the widget
command option. This is because when the callback functions are set, it is necessary to pass a reference to a function rather than actually calling it.
If the callback does not take any argument, it can be handled with a simple function like the one we just used. However, if the callback needs to take some arguments, we can use the
lambda function as shown in the following code snippet:
def my_callback (somearg): #do something with argument Button(root,text="Click",command=lambda: my_callback ('some argument'))
Python borrows syntax from a functional program called the
lambda function. The
lambda function lets you define a single-line, nameless function on the fly.
The format for using
lambda arg: #do something with arg in a single line, for instance:
lambda x: return x^2
Please note that the
command option available with the Button widget is really an alternative function to ease programming the Button event. Many other widgets do not provide any equivalent
command binding option.
The command button binds by default to the left mouse click and the Space bar. It does not bind to the Return key. Therefore, if you bind a button using the
command function, it will react to the Space bar and not the Return key. This is counter-intuitive to many Windows users. What's worse is you cannot change this binding of the
command function. The moral is that
command binding, though a very handy tool, does not provide you the the independence to decide your own bindings.
When an event corresponding to the event description occurs in the widget, it calls the associated handle passing an instance of the event object as the argument, with the event details.
Let us look at an example of the
bind() method (refer to the code file
from Tkinter import * root = Tk() Label(root, text='Click at different\n locations in the frame below').pack() def mycallback(event): print dir(event) print "you clicked at", event.x, event.y myframe = Frame(root, bg='khaki', width=130, height=80) myframe.bind("<Button-1>", mycallback) myframe.pack() root.mainloop()
We bind the Frame widget to the event,
<Button-1>, which corresponds to left-click of the mouse. On the occurrence of this event, it calls the function
mycallback, passing along an object instance as its argument.
We define the function
mycallback(event). Notice that it takes the event object generated by the event as the argument.
We inspect the event object using
dir(event), which returns a sorted list of attribute names for the event object passed to it. This prints the list:
['__doc__', '__module__', 'char', 'delta', 'height', 'keycode', 'keysym', 'keysym_num', 'num', 'send_event', 'serial', 'state', 'time', 'type', 'widget', 'width', 'x', 'x_root', 'y', 'y_root'].
Out of the attributes list generated by the object, we use two attributes,
event.y, to print the coordinates of the point of click.
When you run this code, it produces a window like the one shown. When you left-click anywhere in the frame, 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.
In the previous example, you saw how we used the event
<Button-1> to denote the left-click of a mouse. This is a built-in pattern in Tkinter that maps it to the mouse's left-click event. Tkinter has an exhaustive mapping scheme that exactly identifies events such as this one.
Here are some examples to give you an idea of event patterns:
Left-click of the mouse button
Keyboard press of the key B
Keyboard press of Alt + Ctrl + Delete
In general, the mapping pattern takes the following form:
<[event modifier-]...event type [-event detail]>
Typically an event pattern will comprise of:
An event type (required): Some common event types include
Leave(mouse leaves the widget), and
MouseWheel. For a complete list of event types, refer to the The event types section in Appendix B, Quick Reference Sheets.
An event modifier (optional): Some common event modifiers include
Any(used like in
Double(used like in
<Double-Button-1>to denote a double-click of the left mouse button),
Shift. For a complete list of event modifiers, refer to the The event modifiers section in Appendix B, Quick Reference Sheets.
The event detail (optional): The mouse event detail is captured by number
1for a left-click and number
2for a right-click. Similarly, each keyboard keypress is either represented by the key letter itself (say B in
<KeyPress-B>) or using a key symbol abbreviated as keysym. For example, the up arrow key on the keyboard is represented by the
KP_Up. For a complete
keysymmapping, refer to the The event details section in Appendix B, Quick Reference Sheets.
Let's take a look at a practical example of the
event binding on widgets. (See the code in
1.11.py for the complete working example). The following is a modified snippet of code to give you a flavor of the commonly used the
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 CapsLockkeysym 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
Rather than binding an event to a particular widget, you can also bind it to the Toplevel window. The syntax remains the same except that now you call it on the root instance of the root window like
However, there might be times when you need to bind events to the entire application. At other times you may want to bind the event to a particular class of widget. Tkinter provides different levels of binding options for this:
The syntax for application-level bindings is:
The typical usage pattern is as follows:
An application-level binding here means that no matter what widget is under the current focus, a press of the F1 key will always trigger the
show_helpcallback as long as the application is under active focus.
This syntax for class level binding is as follows:
w.bind_class(className, event, callback)
myentry.bind_class('Entry', '<Control-V>', paste)
In the preceding example, all entry widgets will be bound to the
<Control-V>event that would call a method called
Most of the keyboard events and mouse events occur at the operating system level. It propagates from the source of the event, hierarchically up, until it finds a window that has a 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.
You need variables with a wide variety of widgets. You likely need a string variable to track what the user enters into the entry widget or text widget. You most probably need Boolean variables to track whether the user has checked the Checkbox widget. You need integer variables to track the value entered in a Spinbox or Slider widget.
In order to respond to changes in widget-specific variables, Tkinter offers its own variable class. The variable that you use to track widget-specific values must be subclassed from this Tkinter variable class. Tkinter offers some commonly used predefined variables. They are
You can use these variables to capture and play with changes in the value of variables from within your callback functions. You can also define your own variable type, if required.
Creating a Tkinter variable is simple. You simply call the required constructor:
mystring = StringVar() ticked_yes = BooleanVar() option1 = IntVar() volume = DoubleVar()
Once the variable is created, you can use it as a widget option, as follows:
Entry(root, textvariable = mystring) Checkbutton(root, text="Remember Me", variable=ticked_yes) Radiobutton(root, text="Option1", variable=option1, value="option1") #radiobutton Scale(root, label="Volume Control", variable=volume, from =0, to=10) # slider
myvar.set("Wassup Dude") # setting value of variable myvar.get() # Assessing the value of variable from say a callback
In this lesson, you learned:
commandbinding to bind simple widgets to certain functions
Use of the
lambdafunction, if you need to process arguments
eventbinding using the
widget.bind(event, callback)method to bind keyboard and mouse events to your widgets and to invoke callbacks on the occurrence of some events
How to pass extra arguments to a callback
How to bind events to an entire application or to a particular class of widget using
How to use the Tkinter variable class to set and get values of widget specific variables
In short you now know how to make your GUI program functional!
unbind: Tkinter provides the
unbindoptions to undo the effect of an earlier binding. The syntax is as follows:
The following are some examples of its usage:
entry.unbind('<Alt-Shift-5>') root.unbind_all('<F1>') root.unbind_class('Entry', '<KeyPress-Del>')
For example, imagine you want to create a new event called
<<commit>>, which is triggered by the F9 key. To create this virtual event on a given widget, use the syntax:
You can then bind
<<commit>>to any callback using a normal
Other event-related methods are listed in the Other event-related methods section in Appendix B, Quick Reference Sheets.
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 see some of the configuration options commonly used with the root window.
So far, we have have relied on Tkinter to provide specific platform-based styling for our widgets. However, you can specify your own styling of widgets in terms of their color, font size, border width, and relief. A brief introduction of styling features available in Tkinter is covered in the following task.
mybutton = Button(parent, **configuration options)
Alternatively, you could specify widget options using
Styling options are also specified as options to the widgets, either at the time of instantiation or later using the configure option.
Under the purview of styling, we will cover how to apply different colors, fonts, border width, relief, cursor, and bitmap icons to our widgets. We will also look at some of the root configurations later in the section.
Let's first see how to specify color options for a widget. You can specify two types of color for most of the widgets:
You can specify the color using hexadecimal color codes using the proportion of red, green, and blue. Commonly used representations are
#rgb (4 bits),
#rrggbb (8 bits), and
#rrrgggbbb (12 bits).
#fff is white,
#000000 is black, and
#fff000000 is red.
Alternatively, Tkinter provides mapping for standard color names. For a list of predefined colors, open the program titled
pynche in the
Tools folder within your Python installation directory (in my case,
C:\Python27\Tools\pynche). Within the program click on View | Color list Window.
Next, the easiest and the most common way to specify a font is to represent it as a tuple. The standard representation is as follows:
widget.configure( font= 'font family, fontsize, optional style modifiers like bold, italic, underline and overstrike')
Here are some examples to illustrate the method for 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 takes place in units of pixel. Alternatively, Tkinter accepts four other measurement units which are: m (millimeters), c (centimeters), i (inches), and p (printer's points, which is about 1/72").
The relief style of a widget refers to the difference between the highest and lowest elevations in a widget. Tkinter offers five possible relief styles:
Tkinter lets you change the style of mouse cursor when you hover over a particular widget. This is done using the option cursor as in the following example:
For a complete list of available cursors, refer to the List of available cursors section in Appendix B, Quick Reference Sheets.
While you can specify styling options at each widget level, sometimes it may be cumbersome to do so individually for each widget. Widget-specific styling has several disadvantages:
Fortunately, Tkinter now offers a way to separate presentation from the logic and to 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 may look like the following:
*font: Arial 10 *Label*font: Times 12 bold *background: AntiqueWhite1 *Text*background: #454545 *Button*foreground:gray55 *Button*relief: raised *Button*width: 3
The asterisk (
*) symbol here means that the particular style applies to all instances of the given widget.
These entries are placed in an external text
(.txt) file. To apply this styling to a particular piece of code, you simply call it using the
option_readfile() call early in your code, as shown here:
Now that we are done discussing styling options, let us wrap up with a discussion on some commonly used options for the root window:
Specifying the title for the Title bar
You can specify the size and location of a root window using a string of the form
Changing the Title bar icon to something different from the default Tk icon
Removing the root border frame
from Tkinter import * root = Tk() #demo of some important root methods root.geometry('142x280+150+200') #specify root window size and position root.title("Style Demo") #specifying title of the program self.root.wm_iconbitmap('brush1.ico')#changing the default icon #root.overrideredirect(1) # remove the root border - uncomment #this line to see the difference 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=16, height=5 ) mytext.insert(END, "Style is knowing \nwho you are, what \nyou want to say, \nand not giving a \ndamn.") 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) Button(root, text='>').grid(row=2, column=3) Button(root, text='+' ).grid(row=3, column=1) Button(root, text='v', font='Verdana 8').grid(row=3, column=2) Button(root, text='-' ).grid(row=3, column=3) fori in range(0,10,1): Button(root, text=str(i) ).grid( column=3 if i%3==0 else (1 if i%3==1 else 2), row= 4 if i<=3 else (5 if i<=6 else 6)) #styling with built-in bitmap images mybitmaps = ['info', 'error', 'hourglass', 'questhead', 'question', 'warning'] for i in mybitmaps: Button(root, bitmap=i, width=20,height=20).grid(row=(mybitmaps.index(i)+1), column=4,sticky='nw') root.mainloop()
The first segment of code uses some important root methods to define the geometry, title of the program, icon for the program, and method to remove the border of the root window.
The code then connects to an external styling file called
optionDB.txtthat defines common styling for the widgets.
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.txtfile. One of the buttons also defines a custom cursor.
The last segment of code styles some buttons using built-in bitmap images.
Running this program would produce a window like the following screenshot:
In this task, we explored how to use styling options to modify the default styling of Tkinter. We saw how to specify custom colors, fonts, reliefs, and cursors for our GUI programs. We also saw how to separate styling from the logic using the option database. Finally, we explored some of the common options for configuring our root window.
This brings us to end of Project 1, Meet Tkinter. This project aimed to provide a high-level overview of Tkinter. We have worked our way through all the important concepts that drive a Tkinter program. We now know:
What a root window is and how to set it up
What the 21 core Tkinter widgets are and how to set them up
How to layout our programs using
How to make our programs functional using events and callbacks
How to apply custom styles to our GUI programs
Time for your first Hotshot challenge! Your task is to build a simple calculator (or if you are ambitious, a scientific calculator). It should be fully functional and should have custom-styled buttons and a screen. Try to make it look as close to real physical calculators as you can.
When you are done, we invite you to search in your computer for complex GUI programs. These can range from your operating system programs such as the search bar, to some simple dialog-based widgets. Try to replicate any chosen GUIs using Tkinter.