In the previous tutorial, we learned about the window widget in Tkinter and designed a blank GUI main window (which is not a child to any other window in the GUI application). Child windows serve as a container for other widgets
There is no point of blank windows so there must be other widgets included in the window widget to facilitate interaction with the user. The widgets contained in a window widget are children of that window.
Tk widgets
Tk is a cross-platform user interface toolkit. It can be used to design graphic user interfaces for X11, Microsoft Windows, and Mac OS X systems. Currently, the programming languages that use Tk as the GUI toolkit include Python, Ruby, Pearl, and Tcl.
A GUI consists of several widgets laid out in a container widget. Widgets can be seen as graphical elements that provide a specific kind of interaction and functionality in the user interface. The top-level container widget is always a window.
Tk provides several widgets for fill-in form type interfaces, as well as menus, windows, standard dialogs, and images. It also supports colors, fonts, styling, and themes for user interfaces.
The widgets that were originally included in the package are called classic widgets and are packaged as the Tkinter module. Tk 8.5 recently added a new set of themed widgets, which are available as a ttk package. However, the ttk package needs to be imported separately if interested in using these new widgets with Tkinter.
The widgets are included in a GUI app as class objects, each with its own appearance and functionality. This means all widgets are sub-classes of Tkinter/ttk and it’s necessary to instantiate the objects of those classes to include the widgets in our GUI.
Tkinter does not have a WYSIWYG editor. So, for creating each widget, we’ll need to write code for it and manage the widgets within that code. The management of any widget typically involves three operations:
1. Setting the appearance of the widget
2. Setting the layout of the widget
3. Adding functionality or behavior to the widget
In Tkinter, the appearance of a widget can be established by setting the configuration options available for it. In the code, these configurations are determined by setting the attributes available for that widget class object.
Setting the layout of the widget means where and how the widget must appear in the container widget (i.e., in the window or frame). Tkinter provides three types of layout (geometry) management: absolute positioning, pack layout, and grid layout.
The widgets can be organized as one of these types by calling the place(), pack(), or grid() methods. The configuration options for a widget can be set any time before or after calling a layout management method on the widget. However, until one of the layout management methods (place, pack, or grid) are called for a widget object, it will not appear on the container widget.
GUI applications are always event-driven and each widget has its own set of events available for it. These events are triggered by user interaction. For example, the user may trigger an event and a block of code will then be executed in response to it.
The process of writing a code behavior for a (widget) event is called event binding. The block of code that’s executed in response to a (widget) event is called an event handler. The event handler must be callable so that it can be a function or a callable attribute/method of a class object.
Adding functionality or code behavior to a widget in Tkinter is the same as event binding for that widget. Event binding to a widget can be done either by assigning a callable (function or class method) to command attribute (if it’s available for that widget) or by using the bind() method.
The Tkinter package with ttk (apart from windows, standard dialogs, menus, and images) offers these basic widgets:
- Frame
- Label
- Button
- Checkbutton
- Radiobutton
- Entry
- Combobox
- Listbox
- Scrollbar
- Sizegrip
- Text
- Progressbar
- Scale
- Spinbox
- Separator
- Labelframe
- Panedwindow
- Notebook
- Canvas
- Treeview
Frame, Label, Button, Checkbutton, Radiobutton, Entry, and Combobox are widgets for the fill-in form type of interfaces. Listbox, Scrollbar, Sizegrip, Text, Progressbar, Scale, and Spinbox are additional widgets for specific purposes. Frame, Labelframe, Panedwindow, and Notebook serve as container widgets within the window widgets.
The Treeview widget displays a list of items arranged in a hierarchical order. The canvas widget is a powerful widget that can be used for drawing, diagramming, or creating other complex widgets. The Separator is a presentational widget that can be used to separate two different groups of widgets.
Of the above-mentioned widgets, Combobox, Notebook, Separator, Progressbar, Sizegrip, and Treeview are new widgets that only come with the ttk package.
We’ll frequently use Frame, Label, Combobox, Entry, Text, Checkbutton, Radiobutton, and Button widgets in the GUI for our Raspberry Pi recipes. These widgets are described below:
Frame – serves as a container widget inside window widgets. They’re rectangular containers that are excellent at grouping and ordering other widgets. A frame is used as a parent class for other widget (class) objects contained in it.
Frames look like this:
A frame can be created by instantiating a frame object. The frame object can be instantiated using the Frame() method of tkinter or the ttk class. The definition of a frame object has this syntax:
var = Frame(parent, [configuration options, ….)
This is a valid example of creating a frame:
LED_def = Frame(root) # root is the name of the parent window:
A frame itself is a container widget that can only be created inside a window widget. The parent of the frame object can be the main window or any child window. The “parent” is a mandatory argument that must be passed to a frame definition. The configuration options, which alter the look and feel of the widget, are optional arguments.
A frame object has these configuration options:
The colors (such as, bg) in Tkinter are assigned as strings, which can either be hexadecimal values for colors (like “#fff,” #fdfdfd, etc.) or color names (like “red,” “blue,” “green” etc.).
It should be noted that all dimensions (border width, padding, etc.) set to an integer will assume a value in pixels. The dimensions can be assigned a unit by suffixing the value by the following indicators:
By default, frames do not have any border. If a “bd” attribute is assigned a value and a “relief” attribute is assigned a value (other than flat), the frame will have a border of that width and style. The relief attribute determines the style of the border.
The relief styles supported in Tkinter are flat, raised, sunken, groove, and ridge. If the frame has a relief style assigned “flat,” its border will not show.
The mouse cursors supported in Tkinter include: arrow, circle, clock, cross, dotbox, exchange, fleur, heart, man, mouse, pirate, plus, shuttle, sizing, spider, spraycan, star, target, tcross, trek, and watch. The default cursor is “arrow.”
The configuration options for the frame can be set as key-value pair arguments within the frame definition or it can be defined explicitly. Here are two valid examples of setting the configuration options for a frame:
LED_def = Frame(root, padx = 25, pady = 10)
LED_def.grid()
or
LED_def = Frame(root)
LED_def.grid()
LED_def[“padx”]= 25
LED_def[“pady”]= 10
The frames only serve as container widgets for other basic widgets and don’t involve event binding.
Label – serves as containers for the text and images. The text inside a label can be updated or changed. Generally, labels are used to indicate the purpose of other widgets in the interface.
The labels look like this:
A label can be created by instantiating a label object. A label object can be instantiated using the Label() method of Tkinter or the ttk class. The definition of a label object has this syntax:
var = Label(parent, [configuration options, ….)
Here’s a valid example for creating a label:
label_LED_gpio = Label(LED_def, text = “Select GPIO”) # LED_def is the name of the parent window or frame
The parent of a label object can be the main window, a child window, or a frame. A label object has these configuration options:
The font of the text in a label can be as specified as a tuple in which the first element must be the font family and, the second, must be the size of the font in the printer’s points. Then, there are other possible optional style modifiers, such as weight, slant, underline, or overstrike.
A font object can also be created after importing Tkinter’s font package. A font can be created by instantiating a font object using this syntax:
Font1 = font.Font(optons, ….)
The options are:
- Font family – either standard, platform-specific, or a named font
- Font size – in the printer’s points
- Weight – normal or bold
- Slant – roman or italic
- Underline – set to “1” for underlined text or “0” for normal
- Overstrike – set to “1” for overstruck text and “0” for normal
In a font definition, the font family and size are mandatory options and the others are optional. After creating a font object, it can be assigned to the font attribute of any widget. This is a valid example of creating and using a font object:
from tkinter import font
labels_font = font.Font(family=’Helvetica’, size=12, weight=’bold’)
label_LED_gpio = Label(LED_def, text = “Select GPIO”, font=labels_font)
It’s also possible to show a bitmap or a static image in a label. The bitmaps available in Tkinter include: error, hourglass, info, guesthead, question, warning, gray75, gray50, gray25, and gray12.
If the bitmap is assigned to a label, the text is not displayed in it. Instead, the bitmap is displayed in the place of the text. The font of the text in a label can be specified as a tuple, where the first element must be the font family and the second must be the size of the font in the printer’s points. There are also other optional style modifiers, such as weight, slant, underline, or overstrike.
A font object can be created after importing Tkinter’s font package by instantiating a font object using this syntax:
Font1 = font.Font(optons, ….)
The options are:
- Font family – either standard, platform-specific, or a named font
- Font size – in the printer’s points
- Weight – normal or bold
- Slant – roman or italic
- Underline – set to “1” for underlined text or “0” for normal
- Overstrike – set to “1” for overstruck text and “0” for normal
In a font definition, the font family and size are mandatory options and the other options are optional. After creating a font object, it can be assigned to the font attribute of any widget. This is a valid example of creating and using a font object:
from tkinter import font
labels_font = font.Font(family=’Helvetica’, size=12, weight=’bold’)
label_LED_gpio = Label(LED_def, text = “Select GPIO”, font=labels_font)
To include a static image, Tkinter’s Python Image Library (PIL) or its recent version, Pillow, must be imported. However, the PIL package does not come bundled with Python. So, you’ll have to install it by executing the following command in the shell (Bash shell on Raspberry Pi):
pip install pillow
This statement can import the PIL or its up-to-date version, called Pillow:
from PIL import ImageTk, Image
Next, you’ll need to create an image object using the PhotoImage() method of the ImageTk class. The image object must be assigned to its image attribute. Here’s a valid example of including an image in a label:
from PIL import ImageTk, Image
myimg = ImageTk.PhotoImage(Image.open(‘myimage.png’))
label = Label(root, image=myimg)
label.grid()
For opening an image in an image object, this syntax can also be used:
myimg = ImageTk.PhotoImage(file = ‘myimage.png’)
Tk supports GIF and PPM/PNM images. Tk 8.6 also supports PNG images. After installing PIL, support for many other image formats (like BMP, JPEG, PNG, TIFF) is also included.
When using PIL/Pillow, images can be populated in labels or as a canvas that’s a child of the parent window or a child of a frame inside the parent window. But, PIL/Pillow does not populate images beyond this hierarchy. For example, it fails to populate images in a frame inside another frame, which is a child of the parent window.
The text of a label can bind to a variable using the textvariable attribute. This way the text can be updated or changed in the code. This is a valid example of binding the text of a label to a StringVar type variable:
from tkinter import *
from tkinter import ttk
def func():
global var
var.set(“Text is changed!”)
root = Tk()
root.title(“Dynamic Text of Label”)
root.minsize(200, 200)
var = StringVar()
label = Label(root, textvariable = var, padx = 50, pady = 50)
var.set(“Click Button to change text of this label”)
label.pack()
button = Button(root, text = “Change Text”, command = func)
button.pack()
root.mainloop()
The labels serve as containers for text and images, which are used to indicate the functionality or purpose of other widgets. But it is still possible to bind mouse events to them.
Combobox – comes from the ttk package. It displays a list of the text values from which a user can select one.
The Combobox looks like this:
A combobox can be created by instantiating the combobox object. A combobox object can be instantiated using the Combobox() method of the ttk class. The definition of a combobox object has this syntax:
var = ttk.Combobox(parent, [configuration options, ….) #ttk is reference to ttk class
This is a valid example of creating a combobox:
combo_LED_config = ttk.Combobox(LED_def, values=[“Source Mode”, “Sink Mode”])
The parent of a combobox object can be the main window, a child window, or a frame. A combobox object has these configuration options:
The values of a combobox can be specified by a Python list or tuple. It’s also possible to dynamically populate values in a combobox using the textvariable attribute. The values can be strings or numbers.
The combobox is used to let the user make a selection from a list of values (text or numerical). It’s also possible to bind keyboard and mouse events with a combobox. However, the most important is a virtual event associated with it. This virtual event is “<<ComboboxSelected>>” and it’s triggered when a user makes a selection in the combobox. A callable (function/class method) can bind to this event using the bind() method.
A default value can be selected in the combobox by using the current() method on it, with the index of the desired value passed as an argument. As the values in a combobox are taken from a list or tuple, the index starts from “0.”
If the current() method is used without arguments on a combobox object, it returns the index of the currently selected value or “-1” if no value is selected. The get() method returns the currently selected value of the combobox. The set(value) method allows setting the selected value of the combobox to the value that’s passed as an argument.
In the following example, we can populate RPI’s GPIO pins using a combobox.
Other methods available for the combobox object include: cget(), configure(), identify(), instate(), and state().
The combobox is a subclass of Entry. From the Entry class, it inherits the bbox(), delete(), icursor(), index(), insert(), selection(), and xview() methods.
Entry – accepts a single line of the text string from the user.
It looks like this:
An entry can be created by instantiating an entry object using the Entry() method of Tkinter or the ttk class. The definition of an entry object has this syntax:
var = Entry(parent, [configuration options, ….)
This is a valid example of creating an Entry:
entry_LED_duration = Entry(LED_def)
The parent of an entry object can be the main window, a child window, or a frame. The entry object has these configuration options:
The entries are used to get numerical or single-line text input from a user. The numerical or text value entered by the user can be retrieved using the get() method of the Entry. A callable can bind to the Entry for any change in its state (the state of the content of the Entry — not the state of the widget) by assigning that to the command attribute.
Generally, a callable is bound to the Entry via the command attribute for data validation. Keyboard or mouse events can bind to an entry using the bind() method. In such a case, the callable (event handler) will have the event as one of its arguments.
These methods are available for the Entry widget:
Text – accepts multi-line text from a user as a string object.
It looks like this:
A text widget can be created by instantiating the text object. The text object can be instantiated using the Text() method of Tkinter or the ttk class. The definition of a text object has this syntax:
var = Text(parent, [configuration options, ….)
This a valid example of creating a text widget:
text_cLCD_message = Text(cLCD_init) #cLCD_init is the name of parent frame or window
The parent of a text object can be the main window, a child window, or a frame. The text object has these configuration options:
It should be noted that the text widget does not have command and text variable attributes. The multi-line text entered by the user in the Text widget can be retrieved using get() method. The keyboard or mouse events can be bind to a Text widget using the bind() method. The text widget supports some helper structures like Tabs, Marks, and Index that are useful in the formatting of the text content. The following methods are available for the Text widget:
The text widget has additional methods for tabs, marks, and index.
The index of the text widget has the format of line_number.character_position. So, “1.0” refers to the character at the “0” position on line “1.”
Keywords — such as linestart, lineend, insert, end, wordstart, wordend, and chars — can be used as an index or part of the index.
For example:
- “2.2 linestart” refers to the first character of line 2
- “2.2 lineend” refers to the newline at the end of line 2
- “2.0 + 3 chars” refers to three characters past the line 2…and so on
Checkbutton – allows the user to select values for an option, which can be more than one at a time. These buttons hold binary values that a user can toggle. When the user toggles the value, a command callback can be invoked.
The checkbuttons look like this:
A Checkbutton can be created by instantiating the Checkbutton object. The Checkbutton object can be instantiated using the Checkbutton() method of Tkinter or the ttk class. The definition of a Checkbutton object has this syntax:
var = Checkbutton(parent, [configuration options, ….)
This is a valid example of creating a Checkbutton:
animation_flag = StringVar()
check_cLCD_animate = Checkbutton(cLCD_int, text = “Animate Text”, command = cLCD_Text_Animation, variable = animation_flag) #cLCD_init is the name of parent frame or window
The parent of a Checkbutton can be the main window, a child window, or a frame. The Checkbutton has these configuration options:
The Checkbutton can be set or cleared at one time and paired with a variable to keep a check on its toggle state. By setting “onvalue” and “offvalue” attributes, the non-Boolean values can be retrieved when its state is changed.
A callable can be assigned to its command attribute to execute code behavior during user interaction. A callable can also be executed using the invoke() method.
There’s zero logic for binding keyboard or mouse events to a Checkbutton. But, mouse events can bind to a Checkbutton using the bind() method for some kind of graphical effects.
The Checkbutton has these methods available:
Radiobutton – allows a user to select only one value from multiple values for an option. This can be done using the tab key to navigate through available values. A group of Radiobuttons should be associated with a single variable and each one must hold a unique value.
The Radiobuttons look like this:
A Radiobutton can be created by instantiating the Radiobutton object. The Radiobutton object can be instantiated using the Radiobutton() method of Tkinter or the ttk class. The definition of a Radiobutton object has this syntax:
var = Radiobutton(parent, [configuration options, ….)
This is a valid example for creating a Radiobutton:
watch_mode = StringVar()
radio_datetime_mode = Radiobutton(watch_int, variable = watch_mode, text = “Display only Date and Time”) #watch_init is the name of parent frame or window
radio_datetimetemp_mode = Radiobutton(watch_int, variable = watch_mode, text = “Display Date Time and Temperature”)
radio_datetime_temp_hum_mode = Radiobutton(watch_int, variable = watch_mode, text = “Display Date Time, Temperature and Humidity”)
The parent of a Radiobutton can be the main window, a child window, or a frame. The Radiobutton has these configuration options:
Also, the control variable can track the status of a Radiobutton and can hold a numeric or string value that’s assigned to the selected Radiobutton. The code behavior can be added to a Radiobutton object by assigning a callable to its command attribute.
It’s also possible to execute a callable using the invoke() method. However, there’s no point in a binding keyboard or mouse events to a Radiobutton using the bind() method, although some mouse events can be used to animate certain graphical effects on a Radiobutton.
The Radiobutton has these methods:
Button – allows users to execute certain actions. It’s possible to display text or images in buttons to indicate their purpose. The buttons are associated with a callable that must execute when a user presses it.
The buttons look like this:
A Button can be created by instantiating the Button object. The Button object can be instantiated using the Button() method of Tkinter or the ttk class. The definition of a Button object has this syntax:
var = Button(parent, [configuration options, ….)
Here’s a valid example for creating a Button:
button_LED_signal = Button(LED_def, text = “Generate Signal”, command = thread_generate_LED_signal) #LED_def is name of parent window or frame
The parent of a Button can be the main window, a child window, or a frame. The Button has these configuration options:
The code behavior can be added to a button by assigning a callable to its command attribute. A callable can also be executed using the invoke() method on the Button object.
By default, a callable must execute when a user is pressing the button. There’s no need to bind the other keyboard or mouse events to a Button object. But, it can be useful to bind some keyboard events to a button using the bind() method when it’s necessary to execute action through keyboard shortcuts.
We can design most of the interfaces for our RPi recipes with these widgets. If we include any other widgets in our recipe’s GUI, we’ll introduce them then.
In the next tutorial, we’ll discuss Tkinter/ttk menus, layout management, event binding, and multi-threading. This will be enough to test our first Raspberry Pi recipe – an LED driver.
Do-it-yourself
If you have a microcontroller background, try guessing the use of the different Tkinter/ttk widgets in controlling embedded operations. For example, we can use a scale widget to control the duty cycle of a PWM signal or to control the speed of a DC motor.
We can also use the Entry or Text widgets to contain text messages that may be passed to a character LCD when pressing a button. From your microcontroller experience, think about situations where you can use different Tkinter/ttk widgets in embedded applications. Also, recall situations where you may be reading data from sensors.
Try to evaluate these situations and the possible difficulties you may face when you use Tkinter/ttk widgets to control different hardware components.
One instance is that the Text widget does not have a “textvariable” or command attribute. In this case, will it be possible to pass dynamic text messages from a Text widget to a character LCD? Start brainstorming!
Filed Under: Tutorials