Tkinter

GUI Programming with Tkinter

Programming a GUI (Graphic User Interface) is different from console programming. Indeed for a console program, we display text with print and we expect input from the user with input. The code blocks until the user presses the Enter key.




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:

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 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:

Type Description
Button A 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)
ButtonRelease The 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
KeyRelease The user released a key
Enter The user has moved the mouse in the Widget on which the event is linked
Leave The 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:

 

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:

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:

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:

 

 

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.

About the author

admin

Leave a Comment