Tkinter

GUI Programming with Tkinter19 min read

Tkinter is a Python module that provides a simple and efficient way to create graphical user interfaces (GUIs) in Python. It is based on the Tk toolkit, which was originally developed for the Tcl programming language.

Using Tkinter, you can create a variety of GUI elements such as buttons, labels, menus, and text boxes, and arrange them in a window using a grid layout. You can also add event handlers to your GUI elements to respond to user input, such as button clicks.




Here is an example of a simple Tkinter GUI that displays a label and a button, and exits when the button is clicked:

In a GUI application, the graphical interface that includes various widgets (such as buttons, menus, text entries, …) appears to the user and the program waits for the user to interact through an action. This action is called an event. Some of the most common events include:

  • A click of a mouse button
  • Moving the mouse
  • Pressing a key on the keyboard
  • Releasing a keyboard key
  • A click on the closing cross of the main window
  • A timer has elapsed (this event is not triggered by the user itself)

But how does tkinter wait for an event? Simply with an infinite loop. tkinter will only exit this loop when the user quits the program, most often by clicking on the closing cross of the window. Let’s see how it all works.

Event Loop

A minimalist example for a tkinter program is:

The mainloop function is an infinite loop that waits for user input and processes it as necessary. It is an essential part of any Tkinter GUI, as it is responsible for handling events and updating the GUI as needed.

The mainloop function will not return until the Tkinter window is closed by the user or by calling the destroy method on the root window. Therefore, the line print("We leave the program.") will not be executed until the Tkinter window is closed.

If you want to run code after the mainloop function returns, you can do so by placing it outside the mainloop function, like this:

After creating an instance of Tk, we call the mainloop method. It should be noted that as long as the tkinter window is displayed, the console does not yet show the text Exit program. The reason is, of course, that the mainloop method contains our infinite loop which will only be output when the window is closed. Then we see our text appear in the console.

This mainloop method is often confusing at first. We do not really understand where to place it, we want to place it at various places in the code, and its operation is often mysterious. To use a tool, it is imperative to understand it in order to reason correctly. Let’s see what the mainloop main loop is hiding.

The main loop is thus an infinite loop with which we can only interact in two ways:

By defining a function (callback) that tkinter will call when a given event occurs. Example: when the mouse moves, call the function that will display the position of the mouse in a Label. A callback is linked to an event with the bind method. It will be discussed in more detail in the next section React to an event.

By creating a timer that will execute a function after a given time. This function is commonly called a callback, which means callback. Example: in 30 seconds, call the function that checks if I have a new email. We create a new timer with the after method. This method will also be discussed in more detail in the Manage Time section.

This event loop could be schematized like this:

At each loop turn, tkinter performs these operations:

  • For each event detected since the last loop turn (such as pressing a keyboard key, moving the mouse, …) tkinter executes any callback related to this event.
  • If the elapsed time is greater than the countdown time, the callback is executed.

The implementation of the mainloop function is similar to the way the mainloop function in Tkinter works. Here is a more detailed description of what happens in the Tkinter mainloop function:

  1. The mainloop function enters an infinite loop that waits for events (such as user input or window updates) to occur.
  2. When an event occurs, the mainloop function calls any callbacks that are registered to be called after a certain time interval, if the time interval has elapsed.
  3. The mainloop function then processes the event by checking if it is associated with a callback using the bind method. If a callback is associated with the event, the callback is called.
  4. If the event is a window close event (e.g. the user clicks on the close button of the window), the mainloop function exits the loop and returns.

Note that the mainloop function in Tkinter is implemented in C, so the actual implementation may be slightly different from what is described above. However, the general idea is the same.

The tkinter Widgets also internally use the bind method to produce the expected behaviors. So a Button widget will use a left mouse click event to call its own callback (function). This callback will determine if the mouse position was on the button and if so, will call the function provided by the user when creating the button.

The next two sections will illustrate how to link a function to an event and how to use a timer.

React to an event

As we have just seen, it is possible to link a function to an event using the bind (event, callback) method. We will now see a little more detail how to use this method.

Format of events

The first argument of the bind method is an event. It is defined by a string of the form:

“<Type-detail-modifier>”

The most important element is that of the typical medium. This is the type of event that interests us as Button for a mouse click or Key for pressing a key on the keyboard. modifier and detail add additional information about the type of event we are interested in. For example, the modifying part can indicate that one is only interested in a double-click (instead of a simple click) or if it is the combination of the Alt-X keys that is pressed (unlike the X key only). The detail part will allow us to inform if the click of the button must be the right or the left for example.

The modifier and detail parts can be omitted. For the support of a keyboard key, we simplify even more things, since we only fill in the name of the key that interests us with or without the brackets “<>”. Thus for the support of the X key we will simply have the string “x”. On the other hand if one wants the combination of keys ALT-X one will write “<Alt-x>” in square brackets, since one informs a modifier.

Types of events to start:

TypeDescription
ButtonA click of the mouse. The modifying part can inform if it is a left click (1), a click with the wheel (2) or a right click (3)
ButtonReleaseThe mouse button has been released
KeyPress (or Key more simply)The user has pressed a key. This is the type used by default when only the name of a key is given
KeyReleaseThe user released a key
EnterThe user has moved the mouse in the Widget on which the event is linked
LeaveThe user has moved the mouse out of the Widget on which the event is linked

There is a long list of event types. It is better to read the documentation to find your happiness. Some examples can be found here. Otherwise we will have a little more details with these links:

Some examples of events

  • "<KeyPress-s>" : S button pressed
  • "<Return>" : Enter pressed down. (Warning! It’s different from “<Enter>” which is a type of event)
  • "<Button-1>" : Left mouse click
  • "<Double-Button-1>" : Double left mouse click
  • "<Any-KeyPress>" : Any key is pressed

The callback

The callback is the function that will be called by tkinter when the event occurs. This function must accept an argument, which will be an event object. So his signature will have to be something like:

The event object allows tkinter to give us information about the event that has been fired in different attributes. We access these attributes as for any object, with the notation event.attribute_name. Here are the different attributes available:

  • widget: This is the instance of the widget that triggered this event.
  • x, y: the position of the mouse in relation to the window
  • x_root, y_root: the position of the mouse in relation to the screen
  • char: the character (only for a keyboard event) as a character string
  • keysym: the representation of the character (only for a keyboard event)
  • keycode: a unique code for a key on the keyboard
  • num: the number of the button (only for a mouse event). 1 for left button, 2 for scroll wheel and 3 for right button.
  • width, height: the new width and height of the widget (only for a Configure event)
  • type: The type of the event, represented by a number. We can find here the correspondence between the number and the type of event

As can be seen, certain event types add additional information. The difference between keysym and char is only seen with special keys like for example F4. By pressing this key, char is “” while keysym is “F4“.

Put into practice

In order to see in action how it all works, we will write a simple program that will tell us where the mouse is when we double click left. We will have to react to the “<Double-Button-1>” event, read the x and y attributes of the event object and display the result in the console:

This code creates a simple Tkinter GUI that binds a function to the “double-click” event. When the user double-clicks the left mouse button (Button-1) on the Tkinter window, the on_double_click function is called with an event object as an argument.

The event object contains information about the event, such as the position of the mouse at the time of the event. In this case, the on_double_click function prints the position of the mouse (event.x and event.y) to the console.

The mainloop function is called at the end of the code to start the Tkinter event loop. This will cause the program to wait for user input and call the appropriate event handlers as needed.

Sometimes we want to write a code that will only execute after a delay. The classic mistake is to use functions like sleep that suspend the execution of our program for a given time. With our main loop, we can not suspend the program, otherwise tkinter would no longer handle the events and in the eyes of the user, the application would look frozen (frozen). Indeed, if the user clicked with his mouse on a Widget, there would be no visible reaction.

In order to solve this problem, tkinter offers the after widget method (delay, function, * args, ** kwargs) that queues the specified function. Once the delay time expressed in milliseconds has elapsed, tkinter will execute the function function by passing it the arguments args and kwargs provided.

Let’s take a simple example. We want to make a small application with a counter that increments every second. So we just need to create a Label that will contain the text we want to show (the counter), and a function we will call every second to increment our value of 1:

This code creates a simple Tkinter GUI that displays a counter that increments every second.

The increment function is called every second using the after method, which schedules a function to be called after a certain time interval. The counter variable is incremented by 1 each time the increment function is called, and the value of the counter is displayed in a Label widget (counter_lbl).

The mainloop function is called at the end of the code to start the Tkinter event loop. This will cause the program to wait for user input and call the increment function every second to update the counter.

I used the global keyword online 5 to change the counter value. There is a much cleaner way to achieve this result by using an IntVar object. This will be covered in a future part of the tutorial.

We note that the value of the Label goes from 0 to 1 in 1 second, but then nothing happens. Why ?

The reason is that after placing our function in the queue. After 1 second, tkinter executes the function and removes the function from the queue. In order to execute the function in a repetitive way, the function itself has to get back into the queue using the after method. What gives this modified code:

Output:

This code is similar to the previous code, with the addition of a call to app.after(1000, increment) inside the increment function. This will cause the increment function to be called again after 1 second (1000 milliseconds), resulting in an infinite loop.

As a result, the counter will continue to increment every second until the Tkinter window is closed.

Note that calling the after method inside the increment function causes the increment function to be called again in a recursive manner, so be careful not to create an infinite loop if you do not want the function to be called indefinitely.

Let’s add a second repetitive action to our program. We will not do anything very complicated. We’ll just add another counter but it’s going a little faster than the first one. It will increment by 1 every 0.8 seconds:

This code creates two counters that increment at different rates using the after method. The first counter (counter) increments every second (1000 milliseconds) and the second counter (counter_quick) increments every 0.8 seconds (800 milliseconds).

The values of the counters are displayed in two separate Label widgets (counter_lbl and counter_quick_lbl) and are updated every time the corresponding increment function is called.

The mainloop function is called at the end of the code to start the Tkinter event loop. This will cause the program to wait for user input and call the increment and increment_quick functions at the specified intervals to update the counters.

The two counters are incremented each at their own pace. It is important to realize that the main loop does not stop running while waiting for an event or that the delay of the function in the waiting list has elapsed. It is because this loop rotates continuously that it gives the illusion that actions occur simultaneously. We get the impression that each counter is an independent program. Excluding these are not Threads or other competitive mechanisms. It is simply a loop that rotates quickly and that sometimes executes the increment code and sometimes the increment_quick.

It is now clearer why any of these functions can not contain a sleep function. If this were the case, the program would stop and tkinter would no longer have the opportunity to continue its main loop. It would therefore no longer have the opportunity to observe the time that elapses and could no longer perform any functions placed in the queue thanks to after.

Reasoning with an event loop

To understand the challenges of an event loop, let’s take an example. Imagine that you want to write a little game guess the number I think.

In a console program, this simplistic game could be written as:

There is no management of the bad entries made by the user. For the example it is not important because it would weigh down the code.

If we want to transpose this game to tkinter, we immediately see several differences:

  • To display a message, one must use a tkinter widget. Probably a label will do the trick.
  • To request an entry to the user, we must also use a Widget. An Entry will also do the trick.
  • But how do you know when the user has finished entering his number? We could add a Validate button next to the Widget Entry, or we could link the event <pressing the Enter key> with the function that would validate the input of the user.
  • The messages “The number is bigger“, “The number is smaller” and “You have found the number. should appear in another Label.

But how to coordinate all this? We will first put in place the elements that the user sees at the beginning of the game:

We have now seen the main differences between the structure of a sequential program and event. If you manage to keep in mind that there is a main loop running and all you can do to interact with your program is to respond to events or use countdowns, you will be able to produce consistent programs using graphical interfaces, making the program more user-friendly.

Object Oriented Programming (OOP) can also help to better structure the code.

2 Comments

  • point made by Kristjan I found correct. This wa only prg i tried on this that didnt work until I saw comment

Leave a Comment