Johiasburg Frowell
Johiasburg Frowell

Reputation: 588

Reject events during function execution in TKinter

I have a TKinter application (Python) that reacts to button presses and keyboard input. However, it is possible that a function that is called after one of these events may take some time to execute. I would like for the GUI to reject all user input while the function is executing. [EDIT: Just want to clarify: all user input refers to any and all buttons/key bindings that exist in the app - I am not just trying to reject the event from a particular button.]

Here is a very simple example to show that TKinter currently seems to add events to a queue that gets executed after each event is handled. Each time the button is pressed, the text in the button has a zero appended to it. However, if the button is pressed when the text reads 00, the function takes a while to execute (I put in a self.after(3000)), and if the button is pressed while that function is executing, then each of those presses will register. So if I press the button 5 times in under the 3 seconds that I have it "stall", then each of those clicks registers, and I end up seeing 0000000 on the button.

import tkinter as tk

# To demonstrate that keystrokes/button clicks do register while a function is executing (and then subsequently fire)
class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)

        # Build the app
        self.text = tk.StringVar()
        self.pack()
        self.create_widgets()

    def create_widgets(self):
        self.btn = tk.Button(self, textvariable=self.text, command=self.DoSomething, padx=30, pady=30)
        self.btn.grid(row=0, column=0)
        self.text.set('0')

    def DoSomething(self):
        # self.RejectEventsWhileThisFuncExecutes()
        old_msg = self.text.get()
        if old_msg == '00':
            self.after(3000)
        self.text.set(old_msg + '0')
        # self.BeginAcceptingEventsAgain()

root = tk.Tk()
app = Application(master=root)
app.mainloop()

Basically, what I'd like is maybe something I can call in DoSomething() at the beginning and end of the function, say self.RejectEventsWhileThisFuncExecutes() and self.BeginAcceptingEventsAgain(), that will make sure that no clicks are registered while the function is executing.

Upvotes: 2

Views: 1148

Answers (2)

Bryan Oakley
Bryan Oakley

Reputation: 385870

A common strategy is to create an invisible widget (eg: a 1x1 frame in the corner of the window), and do a grab (ie: call grab_set) on that widget. That causes all events to be funneled to that widget. As long as there are no bindings on that window, the net effect is that the events get ignored. You just need to flush the event queue (call the update method) before removing the grab.

Upvotes: 3

fhdrsdg
fhdrsdg

Reputation: 10532

You can remove the command from the button at the start of DoSomething:

self.btn.config(command=lambda: None)

And then reset it at the end. Just make sure to update to process all queued events before rebinding:

self.update()
self.btn.config(command=self.DoSomething)

Upvotes: 1

Related Questions