Hello, and welcome to Tkinter GUI Programming by Example. In this book, we will be building three real-world desktop applications using Python and Tkinter. You will gain the knowledge to fully utilize Tkinter's vast array of widgets to create and lay out any application you choose.
So why use Tkinter? Tkinter comes bundled with Python most of the time, meaning there's no arduous installation process. It's also licensed under a free software license, meaning, unlike some other GUI frameworks, there's no complicated licensing model to battle against when you want to release your software to the outside world.
Tkinter is also very quick and easy to learn. Code can be written both procedurally or using object-oriented practices (which is the preferred style for anything non-experimental), and runs perfectly on any operating system supporting Python development, including Windows, macOS, and Linux.
In this first chapter, we will cover the following topics:
- Ensuring Tkinter is installed and available
- Creating a main window in which to display your application
- Laying out widgets inside the window via geometry managers
- Creating widgets and displaying them inside your main window
- Displaying static information via a
label
widget - Creating interactivity with the
Button
widget - Tying widgets to Python functions
- Using Tkinter's special variables
- Displaying pop-up messages easily
- Getting information from the user
Most of the time, you will not need to install Tkinter as long as you have Python installed. To check, open an instance of the interactive interpreter and type import tkinter
(Python 3) or import Tkinter
(Python 2). If you don't see an error, then Tkinter is already installed and you are ready to go! Some flavors of Linux will not come with Tkinter by default, and if you receive an error message while performing the previous step, search your distribution's package manager. On Debian-based distributions such as Ubuntu, the package should be called python3-tk
. On RPM-based distributions, including Fedora, you may instead find a package called python3-tkinter
.
Examples in this book will be written using Python 3.6.1 and Tkinter 8.6. I recommend you also use these versions, or as close to them as possible, when following along. To check your Tkinter version, open an interactive Python prompt and type the following:
>>> import tkinter >>> tkinter.TkVersion
Once you've got Tkinter installed and ready, we can move on to a brief overview of how we will be structuring a Tkinter application and then dive in and write our first program.
Tkinter exposes many classes. These are known as widgets. A widget is typically any part of the application that needs to be drawn onto the screen, including the main window.
A Tkinter application always needs to have a main window. This is what will be drawn on the screen for the user to see. This is crucial for any GUI application, so much so that if you do not define one, Tkinter will try to create one for you (though you should never rely on this!). The widget that performs this job is called Tk
.
The Tk
widget exposes various window properties, such as the text within the top bar of the application, the size of the application, its position on screen, whether it can be resized, and even the icon which appears in the top right-hand corner (on Windows only).
Because of this feature exposure, it is very common for the main class of an application to inherit from the Tk
widget, though any Tkinter widget can be subclassed to add program-specific functionality.
There is no set convention for what the subclass should be called. Some like to call it Root
, some choose App
, and others (such as myself) prefer to name it after the program itself. For example, a shopping list program would have a class called ShoppingList
that inherits from Tk
. Bear this in mind when looking through other sources of information on Tkinter.
Once you have a main window defined, you can begin adding other widgets into it. All other widgets must belong to a parent which has the ability to display them, such as a Tk
or Frame
. Each widget is only visible if its parent is. This allows us to group widgets into different screens and show or hide groups of them as need be.
Widgets are placed into their parents using special functions called geometry managers. There are three geometry managers available in Tkinter – pack
, grid
, and place
. Let's take a look at each of them in detail.
Geometry managers serve the purpose of deciding where in the parent widget to render its children. Each of the three geometry managers uses a different strategy and therefore takes different arguments. Let's go over each one in detail, looking at how it decides the positions of new widgets and what sort of arguments need to be provided.
The pack
geometry manager acts based on the concept of using up free space within the parent widget. When packing, you can specify at which end of the free space to put the widget, and how it will grow along with said free space (as the window itself grows and shrinks). The geometry manager than assigns widgets into said free space, leaving as little empty space as possible.
The pack
geometry manager is primarily controlled by three keyword arguments:
side
: On which end of the available space do you want to place the widget? The options are defined as constants within Tkinter, asLEFT
,RIGHT
,TOP
, andBOTTOM
.fill
: Do you want the widget to fill any available space around it? The options are also constants:X
orY
. These are Cartesian, meaningX
is horizontal andY
is vertical. If you want the widget to expand in both directions, use theBOTH
constant.expand
: Should the widget resize when the window does? This argument is a Boolean, so you can passTrue
or1
to make the widget grow with the window.
These are not the only arguments that can be provided to pack
; there are others which handle things such as spacing, but these are the main ones you will use. The pack
geometry manager is somewhat difficult to explain, but tends to create very readable code thanks to its use of words to describe positions.
The order in which widgets are packed matters greatly. Suppose you have two buttons which you wish to stack vertically, with one underneath the other. The first button, which you call pack(side=tk.BOTTOM)
on, will be at the very bottom of the main window. The next widget, which is packed with side=tk.BOTTOM
, will then appear above it. Bear this in mind if your widgets appear to be out of order when using pack
as your geometry manager.
The grid
—as the name suggests—treats the parent widget as a grid
containing rows and columns of cells. If you are familiar with spreadsheet software, grid
will work in the same way. The grid
lines will not be visible, they are just conceptual.
To specify the position within the grid
, the row
and column
keywords are used. These accept integer values and begin at 0
, not 1
. A widget placed with grid(row=0, column=0)
will be to the left of a widget at grid(row=0, column=1)
. Underneath these would sit a widget placed at grid(row=1, column=0)
.
To make a widget span more than one cell, use columnspan
for a horizontal size increase and rowspan
for a vertical increase. So, to make our hypothetical bottom widget sit below both, the full argument set would be grid(row=1, column=0, columnspan=2)
.
By default, a widget will sit in the center of its assigned cell(s). In order to make the widget touch the very edge of its cell, we can use the sticky
argument. This argument takes any number of four constants: N
, S
, E
, and W
. These are abbreviations for North, South, East, and West. Passing in W
or E
will align the widget to the left or right, respectively. S
and N
will align to the bottom and top.
These constants can be combined as desired, so NE
will align top right and SW
will sit the widget bottom left.
If you wish for the widget to span the entire vertical space, use NS
. Similarly, use EW
to stretch to the full size in the horizontal direction.
If you instead want the widget to fill the whole cell edge to edge, NSEW
will let you do this.
Note
The pack
and grid
are both intended to lay out the entire content of a parent widget and apply different logic to decide where each new widget added should go. For this reason, they cannot be combined inside the same parent. Once one widget is inserted using pack
or grid
, all other widgets must use the same geometry manager. You can, however, pack
widgets into one Frame
, grid
widgets into another, then pack
/grid
both of those Frame
widgets into the same parent.
Unlike pack
and grid
, which automatically calculate where each new widget is added, place
can be used in order to specify an exact location for a particular widget. place
takes either x and y coordinates (in pixels) to specify an exact spot, which will not change as the window is resized, or relative arguments to its parent, allowing the widget to move with the size of the window.
To place a widget at (5, 10) within the window, you would write widget.place(x=5, y=10)
.
To keep a widget in the direct center, you would use widget.place(relx=0.5, rely=0.5)
.
place
also takes sizing options, so to keep a widget at 50 percent width and 25 percent height of the window, add (relwidth=0.5, relheight=0.25)
.
place
is rarely used in bigger applications due to its lack of flexibility. It can be tiresome keeping track of exact coordinates for a widget, and as things change with the application, widgets may resize, causing unintended overlapping.
For a smaller window with only one or two widgets – say a custom pop-up message – place
could be a viable choice of geometry manager, since it allows for very easy centering of said widgets.
One thing to note is that place
can be used alongside pack
or grid
within the same parent widget. This means that if you have just one widget which you need to put in a certain location, you can do so quickly without having to restructure your already packed or gridded widgets.
Using pack
versus grid
in your application is mostly down to personal preference. There doesn't seem to be a particularly dominant reason to use one over the other.
The main advantage of pack
is the code tends to be very readable. pack
uses words such as left and top to make it clear where the programmer wants the widget to go.
When using pack
, sections of the window are also split using frames to allow for much greater control. When variables are named sensibly, this allows anyone changing the code to know exactly which part of a window the widget will end up in (by its parent Frame
) and prevents them from having unexpected consequences when changing widgets, such as resizing a widget in the top-left corner of an application, knocking a widget at the bottom out of alignment.
Note
The grid
can also take advantage of Frame
widgets too, but this can sometimes cause alignment issues.
Finally, pack
works out widget positions based mainly on the argument and the order in which they are added. This means that when a new widget is added among existing ones, it is usually quite easy to get it into the correct spot. Simply adjust the order in which your widget.pack()
calls occur. When using grid
, you may need to change quite a few row and column arguments in order to slot the widget where you need it and keep everything else in their correct positions.
The great advantage of grid
is its code simplicity to layout complexity ratio. Without the need to split your application into frames, you can save many lines of code and lay out a complicated window design with essentially just one line of code per widget.
You also don't need to worry about the order in which you add your widgets to their parent as the numerical grid
system will apply regardless.
In the end, both prove to be good tools for the job and there is no need to use one if you prefer the other.
My personal preference is to use pack
for main windows which may change quite a bit during development, and sometimes grid
for smaller windows or layouts which are written in one go. Any additional windows for an application which would require more than two Frame
widget are often better off being managed by grid
for simplicity's sake.
Examples in this book will cover both grid
and pack
, so you will be able to practice both and decide which you prefer.
Now that we have the basic understanding of the concept of widgets and how to add them into a window, it's time to put this into practice and make ourselves an application!
As with almost all programming examples, we will start with a Hello World
application. Don't feel cheated though, it will have interactive aspects too! Let's begin with the most important step for any GUI application—showing a window. Start by opening your choice of text editor or IDE and putting in the following code:
import tkinter as tk class Window(tk.Tk): def __init__(self): super().__init__() self.title("Hello Tkinter") label = tk.Label(self, text="Hello World!") label.pack(fill=tk.BOTH, expand=1, padx=100, pady=50) if __name__ == "__main__": window = Window() window.mainloop()
Let's break this first step down. We begin by importing Tkinter and giving it the alias of tk
for brevity. Every example in this book should include this line so that we have access to all of Tkinter's widgets, including the main window.
Speaking of widgets, we begin this example by subclassing Tkinter's main window widget—Tk
. Doing so allows us to change various aspects of it, including the title which will display inside the window's top bar. We set the title to Hello Tkinter
in this example.
Next, we want to display some text within our window. To do this, we use a Label
widget. A Label
widget is typically non-interactive and is used to display either text or an image.
When defining Tkinter widgets, the first argument is always the parent (sometimes called master) which will hold the widget. We use self
to refer to our main window in this case. Afterward, we have a vast array of keyword arguments to use in order to change their properties. The text argument here will take a string which tells the label what to display. Our label will say Hello World!
Now that we have two widgets, we need to place the label inside of our main window (Tk
) so that they both display. To do this with Tkinter, we utilize one of the geometry managers we covered earlier. For our first example, we will be using pack
. Pack has been given the following arguments:
fill
: This tells the widget to take all the space in both directionsexpand
: This tells the widget to expand when the window is resizedpadx
: Padding (empty space) of 100 pixels in the x direction (left, right)pady
: Padding of 50 pixels in the y direction (above, below)
With that, our Hello World
is ready to run. In order to tell the main window to show itself, we call the mainloop
method. This is all enclosed within the (hopefully familiar) if __name__ == "__main__"
block. Utilizing this block allows widgets from one file to be imported into another file for reuse without creating multiple main windows.
Execute the code via your preferred method and you should see a little window appear. Congratulations! You have now written your first GUI application with Tkinter!:

Our Hello World application
Of course, without any interactivity, this is just a message box. Let's add something for the user to do with our application. Bring the source code back up and change the __init__
method to look like this:
class Window(tk.Tk): def __init__(self): super().__init__() self.title("Hello Tkinter") self.label = tk.Label(self, text="Choose One") self.label.pack(fill=tk.BOTH, expand=1, padx=100, pady=30) hello_button = tk.Button(self, text="Say Hello", command=self.say_hello) hello_button.pack(side=tk.LEFT, padx=(20, 0), pady=(0, 20)) goodbye_button = tk.Button(self, text="Say Goodbye", command=self.say_goodbye) goodbye_button.pack(side=tk.RIGHT, padx=(0, 20), pady=(0, 20))
Our label has changed to say Choose one
to indicate that the user can now interact with the application by selecting one of the two buttons to click. A button in Tkinter is created by adding an instance of the Button
widget.
The Button
widget is exactly what you would imagine; something the user can click on to execute a certain piece of code. The text displayed on a Button
is set via the text
attribute, much like with a Label
, and the code to run when clicked is passed via the command
argument.
Note
Be sure to remember that the argument passed to command must be a function, and should not be called (by adding parentheses). This means your code will not behave as intended if you use command=func()
instead of command=func
.
Our two buttons are placed within our main window using pack
. This time, we use the side
keyword argument. This tells the geometry manager where to place the item inside the window. Our hello_button
will go on the left, and our goodbye_button
will go on the right.
We also use padx
and pady
to give some spacing around the buttons. When a single value is given to these arguments, that amount of space will go on both sides. When a tuple is passed instead, the format is (above, below) for pady
and (left, right) for padx
. You will see in our example that both buttons have 20 pixels of padding below them; our leftmost button has 20 pixels of padding to its left, and our rightmost has 20 pixels to its right. This serves to keep the buttons from touching the edge of the window.
We now need to define the functions which will run when each button is pressed. Our Say Hello
button calls say_hello
and our Say Goodbye
button calls say_goodbye
. These are both methods of our Window
class and so are prefixed with self
. Let's write the code for these two methods now:
def say_hello(self): self.label.configure(text="Hello World!") def say_goodbye(self): self.label.configure(text="Goodbye! \n (Closing in 2 seconds)") self.after(2000, self.destroy)
In say_hello
, we will update the text of our label widget to Hello World!
as it was before. We can change attributes of Tkinter widgets using the configure
method. This then takes a keyword argument and value, just like when we created them initially.
Our say_goodbye
method will also update the label's text and then close the window after two seconds. We achieve this using the after
method from our Tk
widget (which we have subclassed into Window
). This method will call a piece of code after a certain amount of time has elapsed (in milliseconds).
The destroy
method can be called on any Tkinter widget, and will remove it from the application. Destroying the main window will cause your application to exit, so use it carefully.
Leave the if __name__ == "__main__"
block as it was before and give this application a try. You should see now that both buttons will do something. It may not look like many lines of code, but we have now covered quite a lot of the things a GUI application will need to do. You may be getting the following output:

Our application now with two buttons
We have provided user interactivity with Button
widgets and seen how to link a button press to a piece of code. We've also covered updating elements of the user interface by changing the text displayed in our Label
widget. Performing actions outside of the main loop has also happened when we used the after
method to close the window. This is an important aspect of GUI development, so we will revisit this later.
Instead of using configure
to repeatedly change the text within our label, wouldn't it be better if we could assign a variable to it and just change this variable? The good news is you can! The bad news? Regular Python variables aren't perfectly compatible with Tkinter widgets. Shall we take a look?
Let's give it a try the regular way. Open up your previous code and change it to look like this:
class Window(tk.Tk): def __init__(self): super().__init__() self.title("Hello Tkinter") self.label_text = "Choose One" self.label = tk.Label(self, text=self.label_text) self.label.pack(fill=tk.BOTH, expand=1, padx=100, pady=30) hello_button = tk.Button(self, text="Say Hello", command=self.say_hello) hello_button.pack(side=tk.LEFT, padx=(20, 0), pady=(0, 20)) goodbye_button = tk.Button(self, text="Say Goodbye", command=self.say_goodbye) goodbye_button.pack(side=tk.RIGHT, padx=(0, 20), pady=(0, 20)) def say_hello(self): self.label_text = "Hello World" def say_goodbye(self): self.label_text="Goodbye! \n (Closing in 2 seconds)" self.after(2000, self.destroy) if __name__ == "__main__": window = Window() window.mainloop()
Give this code a whirl and click on your Say Hello
button. Nothing happens. Now try your Say Goodbye
button. The label will not update, but the window will still close after 2 seconds. This goes to show that the code written is not invalid, but will not behave as we may expect it to.
So, how would we go about using a variable to update this label? Tkinter comes with four built-in variable objects for us to handle different data types:
StringVar
: This holds characters like a Python string.IntVar
: This holds an integer value.DoubleVar
: This holds a double value (a number with a decimal place).BooleanVar
: This holds a Boolean to act like a flag.
To create a variable, just instantiate it like any other class. These do not require any arguments. For example:
label_text = tk.StringVar()
Since these variables are objects, we cannot assign to them a statement like label_text = "Hello World!"
. Instead, each variable exposes a get
and set
method. Let's have a play with these in the interactive shell:
>>> from tkinter import * >>> win = Tk() >>> sv = StringVar() >>> sv <tkinter.StringVar object at 0x05F82D50> >>> sv.get() '' >>> sv.set("Hello World!") >>> sv.get() 'Hello World!' >>> sv.set(sv.get() + " How's it going?") >>> sv.get() "Hello World! How's it going?"
These variables are passed to widgets inside their keyword arguments upon creation (or at a later stage, using configure
). The keyword arguments expecting these special variables will usually end in var
. In the case of a label, the argument is textvar
.
Let's get our Hello World
application working as intended again using our new knowledge of Tkinter variables. After setting the title, change the label_text
property as follows:
self.label_text = tk.StringVar() self.label_text.set("Choose One")
Now, alter our other two methods like so:
def say_hello(self): self.label_text.set("Hello World") def say_goodbye(self): self.label_text.set("Goodbye! \n (Closing in 2 seconds)") self.after(2000, self.destroy)
Once again, run the application and click both buttons. Everything should now be all working as before.
Great! We now know how to take advantage of Tkinter's special variables, and it's super easy.
Often, a GUI application will need to tell the user something. Using what we have learned at the moment, we could make several Label
widgets which update depending on the results of some other functions. This would get tedious and take up a lot of space within the application's window.
A much better way to achieve this is to use a pop-up window. These can be created manually, but Tkinter also comes with a few pre-built pop-ups which are already laid out and ready to display any message the programmer passes to them.
Let's adjust our Hello World
application to utilize these windows to display the chosen message to the user.
Import the messagebox
module with the following statement:
import tkinter.messagebox as msgbox
Now update the non-init methods to utilize this module:
def say_hello(self): msgbox.showinfo("Hello", "Hello World!") def say_goodbye(self): self.label_text.set("Window will close in 2 seconds") msgbox.showinfo("Goodbye!", "Goodbye, it's been fun!") self.after(2000, self.destroy)
Run this version of our application and try out both buttons.
You should be able to see what the two arguments to showinfo
do.
The first argument is the window's title bar text. If you didn't notice, click the Say Hello
button again – you should see the word Hello
inside the title bar of the pop-up window.
Clicking the Say Goodbye
button will yield a pop-up message with Goodbye!
in the title bar.
The second argument is a string containing the information which will be written inside the box.
The showinfo
box contains just one button—an OK
button. Clicking this button dismisses the window:

A showinfo box
While a messagebox
window is displayed, the main window is effectively paused. The Say Goodbye
button demonstrates this well. The line which tells the main window to close after 2 seconds does not get executed until the messagebox
is dismissed.
Try clicking the Say Goodbye
button and waiting for more than 2 seconds. You will see that the main window stays open until 2 seconds after you click OK to close the messagebox
window. This is important to remember.
If, for example, you are processing a large list of items and you wish to alert the user to their status, it's best to wait until all of the items are processed before using showinfo
. If you put a showinfo
box after each item, the user will have to continually close them in order to allow the main window to continue processing.
If the information to convey is more serious, you can let the user know with showwarning
.
If something goes wrong, tell the user with showerror
instead.
Both of these function the same as the showinfo
box that we have practiced but display a different image inside the box.
Try changing the showinfo
in say_hello
to a showwarning
and the showinfo
in say_goodbye
to a showerror
to see what these boxes will look like.
Should you require something back from the user, Tkinter has four more message boxes for you:
askquestion
askyesno
askokcancel
askretrycancel
askquestion
will allow any question to be passed in and provides Yes
and No
answers. These are returned to the program as the string literals "yes"
and "no"
.
askyesno
does the same, but will return 1 on Yes
and nothing on No
.
askokcancel
provides OK
and Cancel
buttons to the user. OK
returns 1
and Cancel
nothing.
askretrycancel
provides Retry
and Cancel
buttons. Retry
returns 1
and Cancel
nothing.
Despite the seemingly large number of choices, these all do pretty much the same thing. There doesn't seem to be much of a use case for askquestion
over askyesno
since they provide the same button choices, but askquestion
will produce cleaner code thanks to the return values.
Let's see askyesno
in action within our Hello World
application.
Change the say_goodbye
method to the following:
def say_goodbye(self): if msgbox.askyesno("Close Window?", "Would you like to close this window?"): self.label_text.set("Window will close in 2 seconds") self.after(2000, self.destroy) else: msgbox.showinfo("Not Closing", "Great! This window will stay open.")
Run this application and try clicking the Say Goodbye
button. You will now be asked whether you want to close the window. Give both No
and Yes
a try:

Our askyesno box
From the code for this function, you should see that the askyesno
method can be treated like a Boolean statement. If you don't like doing this in one go, you could always use a variable such as the following:
close = msgbox.askyesno("Close Window?", "Would you like to close this window?") if close: self.close()
We now know how to get Boolean information from our user, but what if we want to get something more detailed, such as text?
Tkinter provides us with the perfect widget to do just this – Entry
.
An Entry
widget is a one-line text entry box which is put into a parent widget just like a Label
or Button.
The special Tkinter variables can be attached to an Entry
to make getting the value out a breeze.
Why don't we add some personalization to our Hello World
application? Grab your code and adjust it to the following:
class Window(tk.Tk): def __init__(self): super().__init__() self.title("Hello Tkinter") self.label_text = tk.StringVar() self.label_text.set("My Name Is: ") self.name_text = tk.StringVar() self.label = tk.Label(self, textvar=self.label_text) self.label.pack(fill=tk.BOTH, expand=1, padx=100, pady=10) self.name_entry = tk.Entry(self, textvar=self.name_text) self.name_entry.pack(fill=tk.BOTH, expand=1, padx=20, pady=20) hello_button = tk.Button(self, text="Say Hello", command=self.say_hello) hello_button.pack(side=tk.LEFT, padx=(20, 0), pady=(0, 20)) goodbye_button = tk.Button(self, text="Say Goodbye", command=self.say_goodbye) goodbye_button.pack(side=tk.RIGHT, padx=(0, 20), pady=(0, 20))
If you run this version of the code, you will now see a text box in which to enter your name. As we enter our name in the Entry
widget, its value is automatically assigned to the name_text StringVar
thanks to the textvar
keyword argument:

Our application now with an Entry widget
The buttons will still function the same, however, so let's do something about that:
def say_hello(self): message = "Hello there " + self.name_entry.get() msgbox.showinfo("Hello", message) def say_goodbye(self): if msgbox.askyesno("Close Window?", "Would you like to close this window?"): message = "Window will close in 2 seconds - goodybye " + self.name_text.get() self.label_text.set(message) self.after(2000, self.destroy) else: msgbox.showinfo("Not Closing", "Great! This window will stay open.")
These functions demonstrate both of the ways we can now grab the value back out of our Entry
widget. We can either call the get
method of the Entry
itself, or grab the value out of our StringVar
(also with the get
method).
If the Entry
box itself is the only part of your application which will need to use its value, I would recommend just grabbing it directly via .get()
and foregoing the use of a StringVar
. If, however, its value will be needed by other parts of your application, using a StringVar
is probably the best way. This allows you to use the set
method to adjust its value programmatically.
With this, our Hello World
application has taught us all we should need to know with regard to basic GUI functionality. We have learned how to spawn a window containing various GUI elements by utilizing Tkinter's built-in widgets. We can place these widgets into the window using special functions called geometry managers, of which we have three to choose from.
The messagebox
module allows us to easily convey information to the user without having to use any widgets within our main window, and can also be used to get feedback from a user and control how our window will behave.
We've added three simple, but effective widgets to our arsenal (not including the main window): the Label
, for displaying static information; the Button
, which allows a user to execute functions by clicking on it; and the Entry
, which gathers textual information and allows for its use by our applications.
Next on our agenda is something a little different—a game of blackjack! By writing this game, we will also cover a very common starting point among programmers interested in GUI development: having a command-line application which could be improved by becoming a graphical one. In order to do this, we will briefly step back to the world of the CLI.