Python. Tkinter. Waiting for release 3.13

Working on a project svgwidgets I actively used the functionality tk busywhich appeared in the release Tcl/Tk 8.6.0. I wondered if this functionality was supported in Python, or more precisely in Tkinter. Imagine my surprise when I learned that right now in Tkinter, which is part of Python version 3.13functionality is added tk busywhich has long been included in tcl/tk. Python 3.13 is expected to be released in October of this year. I thought it would be useful to talk about the functionality tk busyor more precisely about new methods for widgets in Tkinter. These methods are – tk_busy_hold(), tk_busy_configure(), tk_busy_cget(), tk_busy_forget() And tk_busy_current().

Teamy tk busy provides a simple way to block a widget from user actions.

How blocking methods work tk_busy V Tkinter Let's look at an example. We will use classic widgets.
But first I had to build a Python distribution from the Python-3.13.0rc1.tgz source codes. I did all this in Linux on Mageia release 9.
So, let's create a graphical interface that will have a main window (mwin) measuring 10 centimeters by 6 centimeters with a panel widget (frame1), in which a data entry field (ent1) and a button (but1) will be placed:

bash-5.2$ /usr/local/bin64/python3.13 Python 3.13.0rc1 (main, Aug 21 2024, 15:48:04) [GCC 12.3.0] on linux Type "help", "copyright", "credits" or "license" for more information.

from tkinter import *
… mwin=Tk()
… #Set the size of the main window
… w1=mwin.winfo_pixels('10c')
… h1=mwin.winfo_pixels('6c')
… gg=str(w1) +'x'+ str(h1)
… mwin.geometry(gg)
… #Set the main window background to yellow
… mwin.configure(bg='yellow')
… #create a panel/frame on the main window with the color cyan
… fr1=Frame(mwin, bg='cyan')
… #Place the fr1 panel in the main window
… fr1.pack(fill=”both”,expand='1',padx='1c',pady='5m',side=”top”)
… #Let's create a field for entering data on the fr1 panel
… ent1=Entry(fr1)
… #Place the ent1 field
… ent1.pack(fill=”x”, expand='0',padx='1c', pady='5m', anchor=”nw”)
… #Let's create a button Input on the fr1 panel
… but1=Button(fr1, text=”Enter”)
… #Place the button but1
… but1.pack(anchor=”n”)
… but1.pack(anchor=”n”, pady='0')
… #Let's define a function to handle the event, which will print the name of the widget on which this occurred
event
… def on_enter(event):
… print('Widget=' + str(event.widget) + '\r')
… fr1.unbind('')
… mwin.bind('', on_enter, add=None)
… #Function for pressing the button
… def put_str (data):
… print ('Button=' + data + '\r')
… #Connect the function call when the button is pressed:
… but1.configure(command=lambda: put_str(“but1”))
… #cursor focus is removed from the main window
… mwin.focus()
… fr1.tk_busy_hold()

Now let's create a do_enter function that will be called when the cursor hovers over the main window and print the identifier of this widget:

def on_enter(event):
    print('Виджет=' + str(event.widget) + '\r')

In order for this function to work, it must be associated with the main window and the event:

mwin.bind('<Enter>', on_enter, add=None)

Let's recall the commands for canceling the call to the handler:

mwin.unbind('<Enter>')

Let me remind you in case someone updates the handler, In order for the updated handler to work, you must first disable the old one. Otherwise, both handlers may work.

Feature of event handling binding And for the main window is that this processing will also be called when the mouse cursor hovers over any widget in this window.

After the event handler has been connected, when you hover the cursor over a widget, its identifier will be printed.
To complete the picture, let's add another function that will print the string passed to it:

def  put_str (data):
	print ('Кнопка=' + data + '\r') 

This function will be called when we press the Enter button:

but1.configure(command=lambda: put_str("but1"))

Now, when the cursor lands on a widget, the widget name (identifier) ​​will be printed, and when you press the “Enter” button, the text will be printed:

Кнопка=but1

Let's assume that we want to protect ourselves for some period of time and make it so that any events and actions for the panel fr1 and the input fields located on it ent1 and buttons but1 were blocked. Note that blocking certain widgets and operations with them is an integral part of the application's security, protection from both intentional and accidental destructive actions.
Let's take the bull by the horns and apply the method tk_busy_hold() to lock the panel fr1 and let's see what happens:

fr1.tk_busy_hold() 

But before performing this operation, let's move the mouse cursor focus to the main window:

mwin.focus()

Why we do this will be discussed below.
So, after executing the command fr1.tk_busy_hold() In our example, a busy or blocked cursor will appear as a rotating circle with a blue and red border:

This cursor will be on the entire space of the panel, including the input field and the button. Outside the panel fr1 the cursor will return to normal. By the way, the type of busy cursor can be changed by setting its type as a parameter in the method tk_busy_hold(cursor=”“)for example, fr1.tk_busy_hold(cursor=”gumby”):

The standard busy cursor has an identifier watch.
The locking effect is impressive. Button “Input” is completely blocked, we can't press the button, and it doesn't react to the mouse cursor appearing on its surface. The input field behaves similarly. But moving the mouse cursor to the surface of the panel itself fr1 causes the following text to be printed:

Виджет=.!frame_Busy

Before the panel was blocked, when you hovered over it with the mouse, a slightly different text was printed:

Виджет=.!frame

The locking feature is implemented in a simple and elegant way by creating and displaying a transparent window that completely covers the widget being locked. This window is created with the postfix _Busy and it inherits event handling And defined for the main window. Blocking transparent window .!frame_Busy closes widgets ent1 And but1so the mouse cursor does not hit them and the event does not occur for them.

Unfortunately, if our event handler indicates the presence of a widget .!frame_Busythen methods winfo_children() And children.values() don't show the blocking window. Maybe they haven't implemented it yet? Let's wait for the release. The method comes with practice call():

mwin.call('winfo', 'children', mwin)

The result of executing this command will be as follows:

('.!frame', '.!frame_Busy')

Here we see the widget that we are blocking. .!frame and the actual blocking widget .!frame_Busy.

Using the method tk_busy_current() you can find out to which widgets the blocking method was applied tk_busy_hold()i.e. which widgets are blocked, for example:

mwin.tk_busy_current()

The result of execution will be as follows:

[<tkinter.Frame object .!frame>, <tkinter.Tk object .>]

In this example, the main window (tkinter.tk) with the name “.» (dot) and panel (tkinter.frame) with the name “.!frame“.
If we want to know the current status of the widget, we can use the method tk_busy_status()which returns either False or True:

ent1.tk_busy_status()

The result of executing this command will be Falseto the widget ent1 method tk_busy_hold() was not used.

You can find out what busy cursor is set or change it by using the method tk_busy_configure(cursor=”“)For example, you can set the cursor to an hourglass using the following command:

fr1.tk_busy_configure(cursor="clock")

But not everything is so rosy, there are nuances. These will be discussed below.

Recall that before the fr1 widget was blocked, the cursor focus was associated with the main window:

mwin.focus()

This is because the blocking window does not prevent keyboard events from being sent to widgets.

Let's say we entered the text “Cursor here” into the input field and immediately blocked the panel fr1leaving the cursor in the input field:

The top screenshot shows the status gui at the time of panel blocking fr1the cursor was in the input field. The screenshot below shows that despite the fact that the panel is locked, but if the main window is active and input is from the keyboard, then it follows the cursor. To avoid this, you need to set the cursor to a neutral position. Personally, I put it on the main window. You can create a temporary widget (the same panel), be sure to place it (place, pack, grid), set the cursor focus on it, and after that the temporary widget can be destroyed.

Naturally, we could not block the entire panel fr1but simply block the input field separately ent1 and the button but1:

#Разблокируем панель fr1
fr1.tk_busy_forget()
#Курсор  мыши на панель fr1
fr1.focus()
#Блокируем поле ввода ent1
ent1.tk_busy_hold()
# Блокируем кнопку but1
but1.tk_busy_hold()

Now when you hover over the panel fr1 the message ” will be printedWidget=.!frame“, but when the cursor hits the input field or button, the identifier of the blocking window will be printed”Widget=.!frame.!entry_Busy” or “Widget=.!frame.!button_Busy“.

And so that everything is as when blocking the panel fr1 you can set a cursor for it watch:

fr1.configure(cursor="watch")

after saving the current cursor:

cur=fr1.cget('cursor')

You can return the cursor to its original state like this:

fr1.configure(cursor=cur)

Now let's go back to the original state when the panel is locked fr1:

First, let's unlock the input field and the button:

ent1.tk_busy_forget()
fr1 tk_busy_forget()

And again we block the fr1 panel:

#Прячем курсор
fr1.focus()
#Блокируем панель fr1
fr1.tk_busy_hold()

Everything, both the input field and the button, are unavailable to us.

Now try using the lift() method on the locked panel:

fr1.lift()

And you will see that the panel, and therefore the input field and buttons, have become available!
In this case, the method tk_busy_status() shows that the panel is locked:

fr1.tk_busy_status() 
True 

As well as the method tk_busy_current():

fr1.tk_busy_current() 
[<tkinter.Frame object .!frame>] 

As well as the method call():

mwin.call('winfo', 'children', mwin) 
('.!frame', '.!frame_Busy'

It's all very simple, a blocking window (.!frame) and blocking (.!frame_Busy) are at the same level of hierarchy and methods are applicable to them lift() And lower().

Method lower() will allow you to lower the panel under the blocking window:

fr1.lower('.!frame.!button_Busy')

Methods lift() And lower() open up a wide scope for the application of family methods tk_busy to block single-level widgets. But this is a topic for a separate article.
Now let's wait for Python release 3.13.
All with the beginning of the new school year!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *