In this Tkinter tutorial, we will explore how to generate Dynamic Content (widgets) in Tkinter. Most Tkinter applications are “static”. This means that when a Tkinter window is generated, the widgets that get generated along with it remain constant. Widgets do not get created or destroyed during the runtime of your application.
We have previously covered how to destroy widgets dynamically in Tkinter. This tutorial is about dynamically created widgets as the user interacts with the tkinter application.
How to create Dynamic Content (widgets) in Tkinter
Let’s describe our Tkinter application first. We have a basic “form” application, with a few compulsory fields, and some optional fields. The optional fields are hidden however (not added to the layout). Our goal is to have these widgets added to our application after we click on the “Optional details” check button.
This is a fairly common occurrence in online forms, where the input fields can change, based on the options that you have selected.
Here is our current application. Nothing fancy, just a few basic widgets. The only thing to important to keep in mind is the additional_section
frame, which has the optional widgets. This frame has not been added to the layout with either pack, grid, or place.
import tkinter as tk
root = tk.Tk()
root.title("Registration Form")
main_frame = tk.Frame(root)
main_frame.pack(pady=10)
name_label = tk.Label(main_frame, text="Name:")
name_label.grid(row=0, column=0, sticky="W", padx = (10, 5))
name_entry = tk.Entry(main_frame)
name_entry.grid(row=0, column=1, padx = (5, 10))
email_label = tk.Label(main_frame, text="Email:")
email_label.grid(row=1, column=0, sticky="W", padx = (10, 5))
email_entry = tk.Entry(main_frame)
email_entry.grid(row=1, column=1, padx = (5, 10))
checkbutton_var = tk.IntVar()
checkbutton = tk.Checkbutton(main_frame, text="Optional Details",
variable=checkbutton_var)
checkbutton.grid(row=2, column=0, sticky="W", padx=(10,5), pady=(10,5))
### Additional Fields
additional_section = tk.Frame(root)
age_label = tk.Label(additional_section, text="Email")
age_label.grid(row=0, column=0, sticky="W", padx = (10, 10))
age_entry = tk.Entry(additional_section)
age_entry.grid(row=0, column=1)
gender_label = tk.Label(additional_section, text="Address")
gender_label.grid(row=1, column=0, sticky="W", padx = (10, 10))
gender_entry = tk.Entry(additional_section)
gender_entry.grid(row=1, column=1)
root.mainloop()
And here is the output.
Currently the function which packs the new widgets in, has not been coded. Let’s do that.
We will create a new function called on_click
, which we connect to the CheckButton from earlier using the command
parameter. This will cause the function to be executed whenever we click the check button.
def on_click():
if checkbutton_var.get() == 1:
additional_section.pack(pady = 10, fill = tk.X)
elif checkbutton_var.get() == 0:
additional_section.pack_forget()
.
.
.
checkbutton= tk.Checkbutton(main_frame, text="Optional Details",
variable=checkbutton_var, command=on_click)
This function does one of two things. If the check button is being turned on, it calls the pack()
method on the additional frame, adding to the layout. If the check button is being turned off, it calls the pack_forget()
method which removes the effect of the packing.
Clicking the check button now gives the following output.
And that’s it. We are actually done! As you the toggle the CheckButton, the optional fields will dynamically be created and removed.
Generating Dynamic Content in the middle of your Application
There are however, some cases where additional precautions need to be made. In the previous code, the dynamic content was being generated at very end. If we try creating dynamic content in the middle, there is an issue we will have to resolve.
Shown below is the same code from before, with one difference. We added a new button at the very end (second last line).
import tkinter as tk
def on_click():
if checkbutton_var.get() == 1:
additional_section.pack(pady = 10, fill = tk.X)
elif checkbutton_var.get() == 0:
additional_section.pack_forget()
root = tk.Tk()
root.title("Registration Form")
main_frame = tk.Frame(root)
main_frame.pack(pady=10)
name_label = tk.Label(main_frame, text="Name:")
name_label.grid(row=0, column=0, sticky="W", padx = (10, 5))
name_entry = tk.Entry(main_frame)
name_entry.grid(row=0, column=1, padx = (5, 10))
email_label = tk.Label(main_frame, text="Email:")
email_label.grid(row=1, column=0, sticky="W", padx = (10, 5))
email_entry = tk.Entry(main_frame)
email_entry.grid(row=1, column=1, padx = (5, 10))
checkbutton_var = tk.IntVar()
checkbutton= tk.Checkbutton(main_frame, text="Optional Details",
variable=checkbutton_var, command=on_click)
checkbutton.grid(row=2, column=0, sticky="W", padx=(10,5), pady=(10,5))
additional_section = tk.Frame(root)
age_label = tk.Label(additional_section, text="Email")
age_label.grid(row=0, column=0, sticky="W", padx = (10, 10))
age_entry = tk.Entry(additional_section)
age_entry.grid(row=0, column=1)
gender_label = tk.Label(additional_section, text="Address")
gender_label.grid(row=1, column=0, sticky="W", padx = (10, 10))
gender_entry = tk.Entry(additional_section)
gender_entry.grid(row=1, column=1)
tk.Button(root, text = "Submit").pack(anchor = "e", padx = 10, pady= 10)
root.mainloop()
So what’s the problem here? Well, when we click the check button, the optional fields will show up at the end of the application, not the middle (where we wanted them). It doesn’t matter in which order you “define” or “declare” your tkinter widgets. That’s not what decides what order they appear in the window.
What effects the way they appear is the “packing order” (or grid). The order of packing in our application is something like this.
- Main frame gets packed
- Button gets packed
- additional section gets packed after check button is clicked.
In tkinter, the packing order determines the order in which the widgets show up. To resolve this, we can play a little trick. Take an extra frame, and pack it where you want your dynamic content (widgets) to go. So the packing order becomes like this.
- Main frame gets packed
- Wrapper frame gets packed
- Button gets packed
- additional section gets packed into the wrapper frame after check button is clicked.
Hopefully that made sense. Here is the code.
import tkinter as tk
def on_click():
if checkbutton_var.get() == 1:
additional_section.pack(pady = 10, fill = tk.X)
wrapper.config(height = 0)
elif checkbutton_var.get() == 0:
additional_section.pack_forget()
wrapper.config(height = 1)
root = tk.Tk()
root.title("Registration Form")
main_frame = tk.Frame(root)
main_frame.pack(pady=10)
name_label = tk.Label(main_frame, text="Name:")
name_label.grid(row=0, column=0, sticky="W", padx = (10, 5))
name_entry = tk.Entry(main_frame)
name_entry.grid(row=0, column=1, padx = (5, 10))
email_label = tk.Label(main_frame, text="Email:")
email_label.grid(row=1, column=0, sticky="W", padx = (10, 5))
email_entry = tk.Entry(main_frame)
email_entry.grid(row=1, column=1, padx = (5, 10))
checkbutton_var = tk.IntVar()
checkbutton= tk.Checkbutton(main_frame, text="Optional Details",
variable=checkbutton_var, command=on_click)
checkbutton.grid(row=2, column=0, sticky="W", padx=(10,5), pady=(10,5))
wrapper = tk.Frame(root)
wrapper.pack(fill = tk.X)
additional_section = tk.Frame(wrapper)
age_label = tk.Label(additional_section, text="Email")
age_label.grid(row=0, column=0, sticky="W", padx = (10, 10))
age_entry = tk.Entry(additional_section)
age_entry.grid(row=0, column=1)
gender_label = tk.Label(additional_section, text="Address")
gender_label.grid(row=1, column=0, sticky="W", padx = (10, 10))
gender_entry = tk.Entry(additional_section)
gender_entry.grid(row=1, column=1)
tk.Button(root, text = "Submit").pack(anchor = "e", padx = 10, pady= 10)
root.mainloop()
This code will now work without any issues.
Creating widgets dynamically which stretch Tkinter Window
There is one extra modification we made in the earlier code, which may or may not be necessary depending on your application and layout.
def on_click():
if checkbutton_var.get() == 1:
additional_section.pack(pady = 10, fill = tk.X)
wrapper.config(height = 0)
elif checkbutton_var.get() == 0:
additional_section.pack_forget()
wrapper.config(height = 1)
In this function, we set the height of wrapper frame manually depending on the state of the check button. The problem here is that if you click on the check button, and the new fields get generated in the middle, causing the window to expand, when you toggle the check button off, the window will remain stretched.
Here is an image illustrating this issue.
To resolve this, we manually set the height of the wrapper frame (the frame causing the window to stretch) to 1. Setting it 0 actually is a default setting, so don’t do that if you want to hide it. It will have the opposite effect.
This marks the end of the Generating Dynamic Content (widgets) in Tkinter Tutorial. Any suggestions or contributions for CodersLegacy are more than welcome. Questions regarding the tutorial content can be asked in the comments section below.