Error in tkinter with Toplevel when I close the main window

1

When instantiating a class, it enters a function that opens a window (toplevel) that the user must choose a certain option so that the program can continue executing, for that in the code I added the statement wait_window() .

I happen to be there without closing that window (waiting), but when I give it close to the main window with the X in the windows window, I see an error in the terminal after closing saying:

  

_tkinter.TclError: can not invoke "toplevel" command: application has been destroyed

What would come after closing the daughter window that was pausing the program would be some more statements and then create another window (Toplevel) that would continue with the execution of the program, there in that sentence is when I throw error, the funny thing is that it appears after close everything, as the execution of the program continues. If anyone has any idea of this and could give me a hand I appreciate it.

This is my code:

main.py:

import tkinter as tk
from tkinter import ttk
from App import App

root = tk.Tk()
root.title('programa prueba')
app = App(root)
app.mainloop()

App.py:

import tkinter as tk
from tkinter import ttk
from Win import Win


class App(tk.Frame):

    def __init__(self, root=None):
        tk.Frame.__init__(self, root)
        self.pack(padx=(10, 10), pady=(10, 10))
        self.create_widgets()

    def create_widgets(self):
        open_frame = tk.Frame(self)
        ttk.Button(
            open_frame,
            text="Boton",
            command=self.callback_button).pack()
        open_frame.pack()

    def callback_button(self):

        top_level = tk.Toplevel(self)
        self.new_window = Win(top_level,self)
        self.new_window.pack()
        self.new_window.wait_window()

        top_level = tk.Toplevel(self)

Win.py:

import tkinter as tk
from tkinter import ttk

class Win(tk.Frame):

     def __init__(self, parent,calc):
        tk.Frame.__init__(self, parent)
        self.pack(padx=(10, 10), pady=(10, 10))
    
asked by Miqueas Rodríguez 27.04.2018 в 15:30
source

1 answer

0

Let's start by explaining that it does the wait_window() method, simply this method does not return until the window / widget it is applied to is not destroyed. It could be more readable to call it self.wait_window(self.new_window) .

But it would not be any fun if the call were blocking because it would block the mainloop and the whole app would stop responding accordingly. Therefore, what it actually does is create a local eventloop that runs parallel to the mainloop so the code to execute from the call will not be executed until wait_window() return but without blocking the mainloop meanwhile.

What happens in your case?

  • When the button is pressed the callback_button() method is executed, the first TopLevel is created and self.new_window.wait_window() is reached, at that point the execution of the method is blocked and does not continue until self.new_window is destroyed which is when wait_window() will return.

  • If you close the self.new_window window, the wait_window() method returns and the next line of top_level = tk.Toplevel(self) is executed, all fine.

  • On the other hand, if with self.new_window open you close the main window you get the aforementioned error. What happens is that the destruction of the main window causes the destruction of all its child windows, at that moment wait_window() returns and tries to finish the execution of the callback_button() method, the problem is that when you reach top_level = tk.Toplevel(self) the window main has been destroyed as its mainloop and we can not create a child window without it as is logical. The key is to understand that the destruction of the window does not imply that the call to the method callback_button() is not yet pending to be completed.

It is a little contradictory that the main window can be closed when in theory we are explicitly telling you to wait for the daughter to finish. If we want to allow this behavior and prevent the exception from showing, you could simply capture it:

def callback_button(self):

    top_level = tk.Toplevel(self)
    self.new_window = Win(top_level, self)
    self.new_window.pack()
    self.new_window.wait_window()

    try:
        top_level = tk.Toplevel(self)
    except tk.TclError:
        pass

Of course the exception is still occurring and we can consider it a fea solution. With something more work we can avoid the exception occurs directly:

  • We define an attribute (Boolean) that indicates if the app is working or not in the main window.

  • We capture and manage by ourselves the closing event of the main window, when this happens the previous attribute is passed to False .

  • This allows you to condition the rest of the code of the callback_button method to whether the app is still running or not.

It might look like this:

class App(tk.Frame):

    def __init__(self, root=None):
        tk.Frame.__init__(self, root)
        self.root = root
        self.pack(padx=(10, 10), pady=(10, 10))
        root.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.create_widgets()
        self._is_runnig = True


    def create_widgets(self):
        open_frame = tk.Frame(self)
        ttk.Button(
            open_frame,
            text="Boton",
            command=self.callback_button).pack()
        open_frame.pack()
        self.new_window = None


    def callback_button(self):
        top_level = tk.Toplevel(self)
        self.new_window = Win(top_level, self)
        self.new_window.pack()
        self.new_window.wait_window()

        if not self._is_runnig:
            return

        top_level = tk.Toplevel(self)


    def on_closing(self):
        self._is_runnig = False
        self.root.destroy()
    
answered by 29.04.2018 / 16:49
source