visit
There are several options to build GUI applications with Python, including and . This guide, however, will introduce you to .
Tkinter, which stands for "Tk interface", is Python's standard for building GUIs and is included with the . It is a binding to the Tk GUI toolkit, a free, open-source library of GUI widgets that can be used to build graphical interfaces in a variety of programming languages.
This tutorial walks through the design of a basic GUI application that displays the local time in a timezone selected by the user. The steps build the application progressively and describe some of the key concepts when working with Tkinter, including the layout of GUI elements, capturing user input, and binding GUI elements to callback methods.
pip
.pytz
timezone library. The library can be installed using pip
.
pip install pytz
This guide will use the terminology root window and main window interchangeably.
Create a new Python application named timezone.py
and add the following import
statements to import the Tkinter
, datetime
, and pytz
modules.
import tkinter as tk
import datetime
import pytz
This guide incorporates the set of United States timezones which are a small fraction of the timezones supported by pytz
. The functionality of the application can be extended by adding additional pytz
timezone names. A complete list of timezones available with pytz
can be output by running the following command:
print(pytz.all_timezones)
The set of United States timezones available via pytz
is specified as a list.
import tkinter as tk
import datetime
import pytz
timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"]
Instantiating the Tk
class creates a root window which will serve as the main window of the timezone application's GUI.
import tkinter as tk
import datetime
import pytz
timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"]
root = tk.Tk()
The window title is set using the title
method.
import tkinter as tk
import datetime
import pytz
timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"]
root = tk.Tk()
root.title("Simple Timezone Application")
import tkinter as tk
import datetime
import pytz
timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"]
root = tk.Tk()
root.title("Simple Timezone Application")
window_width = 450
window_height = 175
The main window can be placed anywhere on the screen, e.g. in the center, in the upper-left corner, etc. A centered window provides a nice "look" and the center position for the main window can be determined using the root window's winfo_screenwidth()
and winfo_screenheight()
methods along with some simple math. These two methods return the screen width and screen height which are used to calculate the appropriate (x,y) coordinates to center the main window. As with the window width and height, the center position values can also be assigned to variables to make the code more readable.
import tkinter as tk
import datetime
import pytz
timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"]
root = tk.Tk()
root.title("Simple Timezone Application")
window_width = 450
window_height = 175
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
center_x = int((screen_width - window_width)/2)
center_y = int((screen_height - window_height)/2)
Note that the center_x
and center_y
values are also expressed in pixels and int
is used to ensure both calculated values are integers.
The root
window geometry, which specifies the size and position of the main window, is set using the root window's geometry
method. To make things simple, you can use a formatted sting literal, or f-string, that will interpolate the geometry variable expressions at runtime. As per the following code block, the f-string literal is passed to the geometry
method as a parameter.
import tkinter as tk
import datetime
import pytz
timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"]
root = tk.Tk()
root.title("Simple Timezone Application")
window_width = 450
window_height = 175
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
center_x = int((screen_width - window_width)/2)
center_y = int((screen_height - window_height)/2)
root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}")
The resizability of the root window along its x and y axes can be set via the root window's resizable
method. The resizable
method accepts height
and width
parameters, in that order. A True
or non-zero value for either parameter specifies that the main window is resizable along the associated axis. A False
or 0
value for either parameter specifies that the main window is not resizable along the given axis. The timer application will use a value of False
for both height and width to prevent the main window from being resized.
import tkinter as tk
import datetime
import pytz
timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"]
root = tk.Tk()
root.title("Simple Timezone Application")
window_width = 450
window_height = 175
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
center_x = int((screen_width - window_width)/2)
center_y = int((screen_height - window_height)/2)
root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}")
root.resizable(False, False)
Widgets can be arranged on top of the main window in different ways using geometry managers. Generally, widgets can be arranged in 3 ways:
Packed using the pack()
method.
The pack()
geometry manager organizes widgets into blocks before they are added to a root window or parent widget. An imperfect but perhaps useful way to think about packing is to think about adding grocery items to a grocery bag. The items aren't necessarily added to pre-defined locations in the bag. Rather, they are packed one after another, using up available space until all items have been put in the bag. The pack()
geometry manager works in a similar way.
Placed using the place()
method.
The place()
geometry manager places widgets into specific, pre-defined positions in the root window or parent widget. This geometry manager is obviously useful when building precise GUI layouts.
Arranged as a grid using the grid()
method.
Grids are 2-dimensional row/column tables. Widgets are added to a grid by specifying the particular row and column where the widget should be placed. Grids are setup using the root window's columnconfigure
and rowconfigure
methods. Each of these methods has an index
and a weight
attribute. The index
attribute specifies the position of the column or row using a starting basis of 0
. The weight
attribute specifies the size of a particular column or row relative to other columns. For example, if column 0 has weight 1
and column 1 has weight 3
, then column 1 will be 3 times as large as column 0.
import tkinter as tk
import datetime
import pytz
timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"]
root = tk.Tk()
root.title("Simple Timezone Application")
window_width = 450
window_height = 175
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
center_x = int((screen_width - window_width)/2)
center_y = int((screen_height - window_height)/2)
root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}")
root.resizable(False, False)
root.columnconfigure(0, weight=1)
root.columnconfigure(1, weight=1)
root.columnconfigure(2, weight=1)
root.columnconfigure(3, weight=1)
In this step, widgets are added to the main window grid defined in Step 4. The timezone application will use 3 TKinter widgets:
Label
Listbox
Button
Tkinter's Label
and Button
widgets are exactly as they are named. The Label
widget allows the programmer to display text (i.e. a label), and the Button
widget is used to display buttons. The Listbox
widget is used to select a value (or values) from a list of options.
Of course, there are other widgets beyond the 3 that are listed above, such as the SpinBox
, Entry
, and Message
widgets. The formal is a useful resource to learn more about the widgets available through the Tk toolkit.
An instance of each widget class is created for a given widget used with the GUI design. The timezone application uses 4 widget instances: 2 Label
widgets, 1 Listbox
widget, and 1 Button
widget.
import tkinter as tk
import datetime
import pytz
timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"]
root = tk.Tk()
root.title("Simple Timezone Application")
window_width = 450
window_height = 175
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
center_x = int((screen_width - window_width)/2)
center_y = int((screen_height - window_height)/2)
root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}")
root.resizable(False, False)
root.columnconfigure(0, weight=1)
root.columnconfigure(1, weight=1)
root.columnconfigure(2, weight=1)
root.columnconfigure(3, weight=1)
# Instance of Label widget class for selection prompt.
select_timezone_label = tk.Label(root, text="Please select a timezone.")
# Instance of Listbox class for selection of timezone from list.
list_var = tk.Variable(value=timezones)
select_timezone_listbox = tk.Listbox(root, listvariable=list_var, height=1)
# Instance of Button class to get the local time in the selected timezone.
select_timezone_button = tk.Button(root, text="Get Time")
# Second instance of the Label class to display the local time in the selected timezone.
time_label = tk.Label(root, text="")
As seen in the code block above, each widget instance is "attached" to the root
window. The Listbox
widget also requires instantiation of a special Tkinter Variable()
class which is used to supply the widget with the list of timezone options that the user can select from via the Listbox
listvariable
attribute. The text displayed by each Label
widget can be configured using the text
attribute. The text value for time_label
is left blank since it will be set dynamically each time the user "gets" the time for a selected timezone.
The widget instances created in Step 5a can be placed on the grid defined in Step 4 using the grid()
method. The timezone application will use the following grid()
method attributes to define the layout:
column
: Specifies the particular column where the widget will be placed using a 0
basis.
row
: Specifies the particular row where the widget will be placed using a 0
basis.
columnspan
: Specifies that widget should span the specified number of columns. For example, a widget with a columnspan=3
value will span 3 columns even if the widget itself is smaller than 3 columns.
sticky
: Tkinter's default behavior is to place a widget in the horizontal and vertical center of the cell (i.e. particular row/column position) where it is placed. This default behavior can be overridden using the sticky
attribute which uses compass-like values, including NW
, N
, NE
, W
, E
, SW
, S
, and SE
, to align the widget at a particular location within the widget's cell. For example, sticky=tK.W
specifies that the widget should be aligned to the west corner of its grid cell. Analogously, sticky=tK.E
specifies that the widget should be aligned to the east corner of its grid cell.
padx
, pady
: The padx
and pady
attributes are used to add x-axis and y-axis padding respectively with values specified in pixels. Naturally, padding can provide a more professional looking GUI ensuring that widgets do not "bump up" directly against the edges of the root window or other widgets.
import tkinter as tk
import datetime
import pytz
timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"]
root = tk.Tk()
root.title("Simple Timezone Application")
window_width = 450
window_height = 175
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
center_x = int((screen_width - window_width)/2)
center_y = int((screen_height - window_height)/2)
root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}")
root.resizable(False, False)
root.columnconfigure(0, weight=1)
root.columnconfigure(1, weight=1)
root.columnconfigure(2, weight=1)
root.columnconfigure(3, weight=1)
# Instance of Label widget class for selection prompt.
select_timezone_label = tk.Label(root, text="Please select a timezone.")
# Instance of Listbox class for selection of timezone from list.
list_var = tk.Variable(value=timezones)
select_timezone_listbox = tk.Listbox(root, listvariable=list_var, height=1)
# Instance of Button class to get the local time in the selected timezone.
select_timezone_button = tk.Button(root, text="Get Time")
# Second instance of the Label class to display the local time in the selected timezone.
time_label = tk.Label(root, text="")
# Place widgets on grid.
select_timezone_label.grid(column=0, row=0, columnspan=4, sticky=tk.W, padx=10, pady=10)
select_timezone_listbox.grid(column=0, row=1, columnspan=3, sticky=tk.EW, padx=10, pady=10)
select_timezone_button.grid(column=4, row=1, sticky=tk.E, padx=10, pady=10)
time_label.grid(column=0, row=4, columnspan=4, sticky=tk.W, padx=10, pady=10)
A callback method needs to be defined to handle events when the user click on the select_timezone_button
button created in Step 5.
Before defining the callback logic, it is helpful to first bind the button to the method name that will eventually encompass the callback code. The bind()
method can be used in conjunction with a lambda
function to bind the select_timezone_button
to the specified callback method. Also, note that the lambda
function is used to pass references to the select_timezone_listbox
and time_label
widgets as callback parameters. These callback parameters are actually not necessary since select_timezone_listbox
and time_label
are within the global scope. However, it is arguably useful to demonstrate how arguments can be passed to the callback function.
import tkinter as tk
import datetime
import pytz
timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"]
root = tk.Tk()
root.title("Simple Timezone Application")
window_width = 450
window_height = 175
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
center_x = int((screen_width - window_width)/2)
center_y = int((screen_height - window_height)/2)
root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}")
root.resizable(False, False)
root.columnconfigure(0, weight=1)
root.columnconfigure(1, weight=1)
root.columnconfigure(2, weight=1)
root.columnconfigure(3, weight=1)
# Instance of Label widget class for selection prompt.
select_timezone_label = tk.Label(root, text="Please select a timezone.")
# Instance of Listbox class for selection of timezone from list.
list_var = tk.Variable(value=timezones)
select_timezone_listbox = tk.Listbox(root, listvariable=list_var, height=1)
# Instance of Button class to get the local time in the selected timezone.
select_timezone_button = tk.Button(root, text="Get Time")
# Second instance of the Label class to display the local time in the selected timezone.
time_label = tk.Label(root, text="")
# Place widgets on grid.
select_timezone_label.grid(column=0, row=0, columnspan=4, sticky=tk.W, padx=10, pady=10)
select_timezone_listbox.grid(column=0, row=1, columnspan=3, sticky=tk.EW, padx=10, pady=10)
select_timezone_button.grid(column=4, row=1, sticky=tk.E, padx=10, pady=10)
time_label.grid(column=0, row=4, columnspan=4, sticky=tk.W, padx=10, pady=10)
select_timezone_button.bind("<Button>", lambda e, args=[select_timezone_listbox, time_label]: get_timezone_time(e, args))
def get_timezone_time(e, args):
select_timezone_listbox = args[0]
time_label = args[1]
selection_index = select_timezone_listbox.curselection()
selected_timezone = select_timezone_listbox.get(selection_index)
now_time = datetime.datetime.now()
tz_time = now_time.astimezone(pytz.timezone(selected_timezone))
tz_formatted = tz_time.strftime("%H:%M:%S")
time_label.configure({"text": f"The time in {selected_timezone} is {tz_formatted}."})
time_label.update()
The curselection()
and get()
methods are used to retrieve the user's selected timezone from the reference to the select_timezone_listbox
widget. The user's current time is then converted into the selected timezone time. Finally, the configure
method is used to change the text
attribute of the reference to the time_label
with the local time in the selected timezone. Note that the update()
method is used to "force" the time_label
widget to update itself with the new text value.
The root window's mainloop()
method is applied as the last line of code. The mainloop()
method will run the GUI in an infinite loop until the user exits.
import tkinter as tk
import datetime
import pytz
timezones = ["US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Samoa"]
def get_timezone_time(e, args):
select_timezone_listbox = args[0]
time_label = args[1]
selection_index = select_timezone_listbox.curselection()
selected_timezone = select_timezone_listbox.get(selection_index)
now_time = datetime.datetime.now()
tz_time = now_time.astimezone(pytz.timezone(selected_timezone))
tz_formatted = tz_time.strftime("%H:%M:%S")
time_label.configure({"text": f"The time in {selected_timezone} is {tz_formatted}."})
time_label.update()
root = tk.Tk()
root.title("Simple Timezone Application")
window_width = 450
window_height = 175
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
center_x = int((screen_width - window_width)/2)
center_y = int((screen_height - window_height)/2)
root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}")
root.resizable(False, False)
root.columnconfigure(0, weight=1)
root.columnconfigure(1, weight=1)
root.columnconfigure(2, weight=1)
root.columnconfigure(3, weight=1)
# Instance of Label widget class for selection prompt.
select_timezone_label = tk.Label(root, text="Please select a timezone.")
# Instance of Listbox class for selection of timezone from list.
list_var = tk.Variable(value=timezones)
select_timezone_listbox = tk.Listbox(root, listvariable=list_var, height=1)
# Instance of Button class to get the local time in the selected timezone.
select_timezone_button = tk.Button(root, text="Get Time")
# Second instance of the Label class to display the local time in the selected timezone.
time_label = tk.Label(root, text="")
# Place widgets on grid.
select_timezone_label.grid(column=0, row=0, columnspan=4, sticky=tk.W, padx=10, pady=10)
select_timezone_listbox.grid(column=0, row=1, columnspan=3, sticky=tk.EW, padx=10, pady=10)
select_timezone_button.grid(column=4, row=1, sticky=tk.E, padx=10, pady=10)
time_label.grid(column=0, row=4, columnspan=4, sticky=tk.W, padx=10, pady=10)
# Bind button to callback.
select_timezone_button.bind("<Button>", lambda e, args=[select_timezone_listbox, time_label]: get_timezone_time(e, args))
root.mainloop()
To use the application, click the list box and use your keyboard’s up/down cursor keys to scroll through the timezone options. Click on the Get Time button to display the current time in your selected timezone.
Congratulations on building your first Python GUI application with Tkinter! As mentioned in the Introduction, this guide was designed to introduce you to a few basic concepts. The and the references mentioned earlier are two fantastic resources to help you learn about more advanced Tkinter features and functionality.