This is the third installment of our multi-part series on developing GUIs in Python using Tkinter. Check out the links below for the other parts to this series:
- Python GUI Development with Tkinter
- Python GUI Development with Tkinter: Part 2
- Python GUI Development with Tkinter: Part 3
Tkinter is the de facto standard package for building GUIs in Python. In StackAbuse's first and second part of the Tkinter tutorial, we learned how to use the basic GUI building blocks to create simple interfaces.
In the last part of our tutorial, we'll take a look at a couple of shortcuts that Tkinter offers to let us effortlessly offer complex and very useful features. We'll also learn about Python Mega Widgets - a toolkit, based on Tkinter, that speeds up building complicated interfaces even faster.
Letting a user select a file on their machine is obviously a very common feature of graphical interfaces. The file dialogs are usually pretty complex - they combine at least multiple buttons (like Open, Cancel, or Create New Folder) and a frame that displays the structure of our environment's directories. Based on our previous tutorials, you may assume that using Tkinter it is very difficult to create such a complicated feature. However, actually, it isn't. Take a look at the following example:
import tkinter import tkinter.filedialog root = tkinter.Tk() def print_path(): f = tkinter.filedialog.askopenfilename( parent=root, initialdir='C:/Tutorial', title='Choose file', filetypes=[('png images', '.png'), ('gif images', '.gif')] ) print(f) b1 = tkinter.Button(root, text='Print path', command=print_path) b1.pack(fill='x') root.mainloop()
The code above is all you need to display a nice, useful File Dialog. In line 2 we import the contents of the
filedialog class. Then, after creating our
root window in line 4, we define a new function in line 6 (which is supposed to be executed by the button created in line 17 and packed in line 18).
Let's take a look at the
print_path() function definition. In line 7, we execute the
askopenfilename function, which takes a couple of arguments. The first argument, of course, is the dialog's parent widget (which in this case is our root window). Then, in the
initialdir argument, we provide a location that'll be displayed in our file dialog right after it's opened.
title controls the content of the dialog's title bar.
And then we have the
filetypes argument, thanks to which we can specify what kind of files will be visible for the user in the file dialog. Narrowing down the file types can make the search for the desired file much faster, as well as letting the user know which types of files are accepted.
The argument to
filetypes is a list of 2-element tuples. In each tuple, the first element is a string which is any description we want to set for each of the file types. The second element is where we state or list the file extensions associated with each file type (if there's only one extension, it's a string - otherwise, it's a tuple). As you can see on the output screenshot above, the user can select the displayed file type from the dropdown list in the bottom right corner of the dialog.
askopenfilename() method returns a string which is the path of the file selected by the user. If the user decides to hit Cancel, an empty string is returned. In line 7 we return the path to variable
f, and then, in line 15 (which is only executed after the File Dialog is closed), the path is printed in the console.
Displaying Images Using Tkinter
One more interesting thing that many people might find useful to apply to their GUIs is displaying images. Let's modify the previous example a little bit.
import tkinter import tkinter.filedialog root = tkinter.Tk() def display_image(): f = tkinter.filedialog.askopenfilename( parent=root, initialdir='C:/Tutorial', title='Choose file', filetypes=[('png images', '.png'), ('gif images', '.gif')] ) new_window = tkinter.Toplevel(root) image = tkinter.PhotoImage(file=f) l1 = tkinter.Label(new_window, image=image) l1.image = image l1.pack() b1 = tkinter.Button(root, text='Display image', command=display_image) b1.pack(fill='x') root.mainloop()
Let's see what changed inside the function executed by our button, now renamed to
display_image. We display the File Dialog, we use the same criteria for file selection as before, and again we store the returned path in variable
f. However, after getting the file path, we don't print it in the console. What we do is we create a top-level window in line 14. Then, in line 16, we instantiate an object of the
PhotoImage class, by making it read the
.png file that was selected by the user. The object is then stored in the
image variable, which we can pass as an argument for the construction of
Label widget in line 17. In line 18, we make sure to keep a reference to the
image object in order to keep it from getting cleared by Python's garbage collector. Then, in line 19, we pack our label (this time displaying an image, not a text) inside the
Another common feature, especially in software focused on graphics, is allowing the user to select a color from a palette. In this case, Tkinter also offers a nice, ready-to-use solution that should satisfy most of our needs concerning the color choosing feature.
import tkinter import tkinter.colorchooser root = tkinter.Tk() def color_button(): color = tkinter.colorchooser.askcolor(parent=root) print(color) b1.configure(bg=color) b1 = tkinter.Button(root, text='Select Color', command=color_button) b1.pack(fill='x') root.mainloop()
In line 2 of the example shown above, we import a class called
colorchooser. We use its
askcolor() method in line 7. This method, similarly to
askopenfilename(), is responsible for opening a nice, complex dialog (a color chooser in this case) and returns the data dependent on the user's choice. In this case, after the user picks a color from the palette and accepts their choice, the object returned to variable
color is a tuple containing two elements. The first element is a tuple that stores values for the selected color's red, green and blue channels. The second element of the tuple is the same color specified in hexadecimal format. We can see the contents of the tuples in our console, thanks to the
print() in line 8.
After we store the tuple returned by
askcolor in variable
color, we then use that variable in line 9 to configure the
b1 button. As you already know, the
bg argument is responsible for controlling the button's background color. We pass the first element of the
color tuple to it (the color representation in hexadecimal format). As a result, after pressing the
b1 button, the user can change its background color using a nice color chooser.
Before move on from Tkinter to Python Mega Widgets, it's good to mention one more feature of the Tkinter module that makes programming GUIs a bit quicker. Tkinter offers so-called Message Boxes, which are a set of simple, but widely used standard dialogs. These message boxes can be used to display a quick message, a warning, or when we need our user to make a simple yes/no decision. The following example demonstrates all message boxes offered by Tkinter:
import tkinter import tkinter.messagebox root = tkinter.Tk() def display_and_print(): tkinter.messagebox.showinfo("Info","Just so you know") tkinter.messagebox.showwarning("Warning","Better be careful") tkinter.messagebox.showerror("Error","Something went wrong") okcancel = tkinter.messagebox.askokcancel("What do you think?","Should we go ahead?") print(okcancel) yesno = tkinter.messagebox.askyesno("What do you think?","Please decide") print(yesno) retrycancel = tkinter.messagebox.askretrycancel("What do you think?","Should we try again?") print(retrycancel) answer = tkinter.messagebox.askquestion("What do you think?","What's your answer?") print(answer) b1 = tkinter.Button(root, text='Display dialogs', command=display_and_print) b1.pack(fill='x') top.mainloop()
This time, our
b1 button executes function
display_and_print(). The function lets 7 message boxes pop up in sequence - each is displayed after the user interacts with the previous one. dialogs defined in lines 11 - 21 are dialogs that require the user to choose one of two available options - therefore, they return values based on the decisions and store them in their respective variables. In each case, we can pass two arguments while defining the dialogs - first is always the dialog's title, and the second one contains contents of its main message.
So, to start from the top. In line 7, we define a simple
showinfo dialog, which is only meant to display a neutral icon, a message, and an OK button that closes it. In lines 8 and 9, we have similar, simple types of message boxes, but their icons indicate that caution from the user is required (
showwarning) or that an error has occurred (
showerror). Note that in each of the three cases, a different sound is played upon the dialog's appearance.
As I stated before, lines 11 - 21 contain code responsible for displaying dialogs to get the user's decision.
askokcancel (line 11) returns
True if the user clicks OK and
False if they click Cancel.
askyesno (line 14) returns
True if the user clicks Yes and
False if the user clicks No.
askretrycancel (line 17) returns
True if the user clicks Retry and
False if the user clicks Cancel.
askquestion is very similar to
askyesno, but returns
'yes' if the user clicks Yes and
'no' if the user clicks No.
Keep in mind, that the exact appearance of the File Dialog, Color Chooser, and all Message Boxes depends on the operating system that the code is executed on, as well as on the system language.
Another useful element of advanced GUIs is a Progress Bar. The following example shows a simple implementation of this feature using Tkinter:
import tkinter import time from tkinter import ttk root = tkinter.Tk() def start(): for k in range(1, 11): progress_var.set(k) print("STEP", k) k += 1 time.sleep(1) root.update_idletasks() b1 = tkinter.Button(root, text="START", command=start) b1.pack(side="left") progress_var = tkinter.IntVar() pb = ttk.Progressbar(root, orient="horizontal", length=200, maximum=10, mode="determinate", var=progress_var) pb.pack(side="left") pb["value"] = 0 root.mainloop()
The example above shows the implementation of
Progressbar. It is a part of the tkinter.ttk module, which provides access to the Tk themed widget set, introduced in Tk 8.5. This is why we need to additionally import the
ttk module in line 3.
Our progress bar's state will be controlled by time - the bar will progress in ten steps, executed in one-second intervals. For that purpose, we import the
time module in line 2.
We define our
Progressbar in line 20. We define its parent widget (
root), we give it a "horizontal" orientation and a
length of 200 pixels. Then, we define the
maximum value - which is the value of the variable assigned to the progress bar using the
var argument (in our case, the
progress_var variable), that means the progress bar is filled completely. We set the
mode to "determinate", which means that our code will be moving the indicator's length to precisely defined points based on the
progress_var integer variable that will control the bar's progress is defined in line 18. In line 26, using a dictionary-like assignment, we set the initial value of the progress bar to 0.
In line 15, we create a
Button that's supposed to start the clock controlling our bar's progress by executing the
start() function, defined between lines 7 and 13. There, we have a simple
for loop, that will iterate through values between 1 and 10. With each iteration, the
progress_var value is updated and increased by 1. In order to be able to observe the progress clearly, we wait for one second during each iteration (line 12). We then use the root window's
update_idletasks() method in line 13, in order to let the program update the appearance of the progress bar even though we're still executing the
for loop (so, we're technically still in a single
Python Mega Widgets
If you use Tkinter extensively in your projects, I think it's a good idea to consider incorporating Python Mega Widgets in your code. Python Mega Widgets is a toolkit based on Tkinter that offers a set of megawidgets: complex, functional and relatively aesthetically pleasing widgets made out of simpler Tkinter widgets. What's great about this package, which you can download here is that the general philosophy of defining and orienting widgets is the same as in case of Tkinter, and you can mix both libraries in your code. Let's finish our tutorial by scratching the surface of this powerful toolkit.
One of the most useful widgets of the Pmw package is
EntryField. Let's analyze the following example to see what it's capable of:
import tkinter import Pmw root = tkinter.Tk() def color_entry_label(): color = entry_color.get() entry_number.configure(label_bg=color) entry_color = Pmw.EntryField(root, labelpos="w", label_text="First name:", entry_bg="white", entry_width=15, validate="alphabetic") entry_number = Pmw.EntryField(root, labelpos="w", label_text="Integer:", entry_bg="white", entry_width=15, validate="integer") ok_button = tkinter.Button(root, text="OK", command=color_entry_label) entry_color.pack(anchor="e") entry_number.pack(anchor="e") ok_button.pack(fill="x") root.mainloop()
This time, we have to not only import
tkinter, but also our freshly installed
Pmw package (line 2). As always, we use the
Tk class to initiate our root window.
In lines 10-14 and 16-20 we define two
Pmw.EntryField widgets. An
EntryField is a functional mix of Tkinter's
Entry, with some addition of useful functionalities. The first argument for the widget's initialization is, of course, the parent widget. The
entry_width control some self-explanatory aspects of the widget's appearance. The most interesting argument in our example is probably the
validate argument. Here, we can decide what kind of data the user can put inside the field.
entry_color field, we expect a string of letters, so we set
validate to "alphabetic". In the
entry_number widget, we expect an integer, and that's what we set the
validate argument value to. This way, if we try to put a number inside the former and a letter inside the latter, the symbols simply won't appear in the widgets and a system sound will be played, informing us that we're trying to do something wrong. Also, if the widget expects a certain type of data and its contents are in conflict with this condition the moment it's initialized, the
EntryField will be highlighted red.
As you can see in our example, right after we display our window, the first entry field is white, and the second one is red. This is because an empty string (default content of the entries) falls into the category of "alphabetic" entities, but it's definitely not an integer.
The button defined in line 26 executes the
color_entry_label() command defined between lines 6 and 8. The function's goal is to paint the
entry_number widget label's background according to the contents of the
entry_color widget. In line 7, the
get() method is used to extract the contents of the
EntryField. Then, naturally, the
configure() method is used in order to change the appearance of the
entry_number widget. Note that in order to change characteristics of Pmw widgets that are composed of several simpler widgets, we have to specify which sub-widget we want to configure (in our case, it's the label - this is why we configure the
label_bg and not, let's say, the
EntryField widget might not be visually very impressive, but even this simple example illustrates the potential of mega-widgets - building this kind of self-verifying piece of the interface of higher complexity would require much more code if we tried to achieve the same effect using plain Tkinter. I encourage you to explore other powerful mega-widgets described in the toolkit's documentation.
Tkinter is one of many available GUI libraries for Python, but its great advantage is that it's considered a Python standard and still distributed, by default, with all Python distributions. I hope you enjoyed this little tutorial and now have a good grasp on building interfaces for users that might get scared off by command-line operated software.