Home > Blockchain >  Python tkinter When Toplevel object is destroyed, With toplevel binding for <Destroy> the boun
Python tkinter When Toplevel object is destroyed, With toplevel binding for <Destroy> the boun

Time:12-09

In the following code, When the toplevel window is destroyed, the command in the bind statement is executed multiple times. Probably once for each child widget within the Top Level. When I change the toplevel to a Frame, the bind command only executes once. In the example, quit() or raise SystemExit are deferred until the command finishes its looping. Why is this happening?

import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showinfo

class PlainFrame(tk.Frame):
    def __init__(self,parent):
        super().__init__(parent)
        self.butts = []
        for i in range(20) :
            # button
            self.butts.append(ttk.Button(self, text=f'Click Me {i}'))
            self.butts[i]['command'] = self.button_clicked
            self.butts[i].grid(column=0,row=i)
        self.pack()

    def button_clicked(self):
        showinfo(title='Information',
                 message='Hello, Tkinter!')

class MainFrame(tk.Toplevel):
#class MainFrame(tk.Frame):
    def __init__(self, container,*args,**kwargs):
        super().__init__(container,kwargs)

        options = {'padx': 5, 'pady': 5}

        # label
        self.label = ttk.Label(self, text='Hello, Tkinter!')
        self.label.pack(**options)
        self.quit_button = ttk.Button(self,text='Quit',
                                      command = self._quit)
        self.quit_button.pack()
        self.frame = PlainFrame(self)
        # add when frame
        #self.pack()

    def button_clicked(self):
        showinfo(title='Information',
                 message='Hello, Tkinter!')

    def _quit(self):
        self.destroy()
    

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        # configure the root window
        self.title('My Awesome App')
        self.geometry('600x100')
    
        def quitting(self,event):
            print ('just passing through')
            quit()
            raise SystemExit

if __name__ == "__main__":
    app = App()
    frame = MainFrame(app)
    # app.withdraw()
    frame.bind('<Destroy>',app.quitting)
    app.mainloop()

CodePudding user response:

With toplevel binding for the bound command executes multiple times

Yes, this is how tkinter is designed to work.

When you bind to something, you don't bind to a widget. Rather, you bind to a binding tag. Every widget has a set of binding tags. When a widget receives an event, tkinter will check each of its binding tags to see if there's a function bound to it for the given event.

So, what are the widget binding tags? Every widget gets the binding tag "all". Each widget also gets a binding tag named after the widget itself. It gets a third binding tag that is the name of the widget class (eg: "Button", "Label", etc). The fourth tag -- the one causing you trouble -- is the name of the window that contains the widget. The order goes from most to least specific: widget, widget class, window, "all".

You can see this by printing out the binding tags for a widget. Consider the following code:

import tkinter as tk

root = tk.Tk()
toplevel = tk.Toplevel(root)
label = tk.Label(toplevel)
print(f"binding tags for label: {label.bindtags()}")

When run, the above code produces this output:

binding tags for label: ('.!toplevel.!label', 'Label', '.!toplevel', 'all')

The first string, .!toplevel.!label is the internal name of the label widget. Label is the widget class, .!toplevel is the name of the toplevel widget, and then there's the string all.

What happens when you click on the label? First, tkinter will check to see if there is a binding for the button click on the tag .!toplevel.!label. It will have one if you bound to the widget itself. Next, it will check to see if there is a binding on the widget class. Widgets like scrollbars and buttons will have a binding on the widget class, but label won't. Next, tkinter will see if there's a binding on the window itself for the event. And finally, it will see if there is a binding on the special tag all.

You can alter the bindtags for a widget by passing the list of binding tags to the bindtags command. For example, if you want every widget to have a bind tag for the frame, you could set the bindtags to include the frame, and then every widget will respond to an event that is bound to the frame.

You can use the same technique to remove bindings as well. For example, if you wanted to remove all default bindings from a Text widget, you could remove Text from its list of bind tags. Once you do that, the widget will not respond to any key presses or key releases.

The canonical description of how binding tags works is in the bindtags man page for tcl/tk.

  • Related