Destroy events executes the binded function 5 times
Asked Answered
L

1

1

I have some code in python using the tkinter module. I've been trying to bind a function to the "<Destroy>", but each time I run the code and I close the window, the binded function executs 5 times. Here is the code:

def bind_events(self):
        ''' binding the events to their functions '''

        self.master.bind("<Destroy>", lambda x: print("OK"))

This code is a class function. Then the output I obtain this:

>>> OK
OK
OK
OK
OK

Is there any solution for this problem. Thank you for your time, and sorry for my English.

Lest answered 10/10, 2019 at 18:2 Comment(0)
L
4

If you bind an event to the root window, that binding is attached to every widget. Thus, if you have a root window with four other widgets, when you destroy the window your bound function will be called five times - once for every widget.

An easy way to see this happen is to modify your function to not just print "OK", but to also print the widget associated with the event:

self.master.bind("<Destroy>", lambda event: print("{}: OK".format(event.widget)))

This has to do with the fact that you aren't actually binding to a widget per se, you are binding to a binding tag that has the same name as the widget. Every widget has an ordered set of binding tags associated with it in addition to itself, in the following order:

  • It will have a binding tag for itself
  • It will have a binding tag for the internal widget class (which is how widgets get their default behavior)
  • It will have a binding tag for the toplevel window (or root) for that widget,
  • It will have the special binding tag "all"

Thus, when you bind to the root window you are binding to a tag that is shared by every widget. If you want to bind to the root window and only the root window, the most common solution is to bind to a function, and in that function only take an action if the widget is the root window.

For example:

def on_destroy(self, event):
    if event.widget == event.widget.winfo_toplevel():
        print("{}: OK".format(event.widget))

self.master.bind("<Destroy>", self.on_destroy)
Lui answered 10/10, 2019 at 18:23 Comment(5)
Hum. That is interesting. I have never found an issue when binding to root cause a problem so I never thought to print on bind. I guess when you think about it thought it makes sense. If I bind to a frame any time I do something in that frame that is the trigger sets off the bind. Thought I assumed it was just because I was within the frame. So is the real reason because everything inside of that frame has inherited the bind?Noriega
@Mike-SMT: "If I bind to a frame any time I do something in that frame that is the trigger sets off the bind." I'm not sure what you mean by that. This only is an issue when binding to the root window, or to toplevel windows. A widget inside a frame does not get respond to events bound to the frame itself.Lui
so my original assumption on how binding to a frame is correct and this behavior is only related to root or toplevel binds? So to clarify what I meant is my original assumption when binding to any container was that if my focus was anywhere within that container then the bind would be triggered by whatever condition I set. But you are saying that is not true for root and top. That it sets the bind to all children widgets. Unless I am misunderstanding.Noriega
@Mike-SMT: widgets have binding tags, and it is the tags which determine which events a widget responds to. The binding tags include a tag for the toplevel or root window. They do include a tag for any other container.Lui
Thank you so much for your time, I really appreciate it. I found this problem while I was trying to create a simple tkinter text editor and, in order to ask the user if he wanted to savethe file, I had to bind the destroy event to a specific destroy function which will open the question dialog.Lest

© 2022 - 2024 — McMap. All rights reserved.