Tkinter: Referencing child windows

Question:

Hi i have two tkinter classes:
note.py-opens a notepad editor and
cp.py-control panel where i can select apps ( like note.py and other future apps)

I have it so that the control panel can open and close the note.py in the menu of control panel. I was using subprocess to open the file then tracking the pids of the running applications to see when they close and update the control panel

I wanna transition to child windows. So i edited my files to remove the subprocess and the pids. Then I imported the note.py into the cp.py. The problem I’m having is referencing the running note.py applications. Let’s say i want to be able to open an arbitrary amount of windows in the control panel. How can i differentiate which is open and which is closed. So i need to know how to check when the the child window is closed and then update the control panel to reflect it closed.

Here is some code that can help you understand my code.

import tkinter as tk
import note as b 
import numpy as np

class Main(tk.Tk):
    def __init__(self):
        super().__init__()
        self.min=np.zeros((2,) ,dtype= int)
        self.note = []
        self.o= np.zeros((2,) ,dtype= int)

        b1 = tk.Button(self, text="open app 1", command=lambda:self.oorm(0))
        b1.pack(padx=20, pady=20)
        b2 = tk.Button(self, text="open app 2", command=lambda:self.oorm(1))
        b2.pack(padx=20, pady=20)

    def oorm(self,item):
        if self.o[item]==0:           
            self.note.append(b.App())   
            self.o[item]=1
            self.min[item]=0
        else:

            if self.min[item]:
                self.note[item].deiconify()
                self.min[item]=0
            else: 
                self.min[item]=1
                self.note[item].state('withdrawn')
            
main = Main()
main.mainloop()

Its more of a snippet to see if it works before I implement it. With this I can open and minimize a notepad window. The problem is after closing one and pressing the button to reopen it. I get this error :
File "C:UsersuserAppDataLocalProgramsPythonPython38libtkinter_init_.py", line 2220, in wm_state
return self.tk.call(‘wm’, ‘state’, self._w, newstate)
_tkinter.TclError: bad window path name ".!app"

I’m guessing this is because I’m tryna open a closed window. I tried using the two methods below to see if the child was closed but I can’t reference the list and get the index out of range error.

self.note[item].bind("<Destroy>", self.note[item].destroy())

and

self.note[item].winfo_exists())

How can i check if the window is still running and update to show, like a print statement to see if it work, so i can add it into my main code. I need to reference the child windows in array or list so I can reference it.

Also the note class is initialized in the file as

class App(tk.Toplevel): 
    def __init__(self,master):
        super().__init__()
        self.geometry("300x200+240+125")
        self.configure(background='black')
        self.overrideredirect(1)
        self.attributes("-topmost", True)
        self.cfile=""
        #-----added
        self.attributes('-alpha',1.0)
        self.text_box = tk.Text(self,bg="black", insertbackground='yellow', height=20, width=50, bd=0,fg="green",highlightthickness = 0, borderwidth=0)
        self.text_box.configure(insertwidth = 2.5)

        self.rc = tk.Menu(self, tearoff = 0,bd=0)
        self.rc.add_command(label ="Cut", background='black',
                            foreground='green',
                            activeforeground='black',
                            activebackground='green')
        self.rc.add_command(label ="Copy", background='black',
                            foreground='green',
                            activeforeground='black',
                            activebackground='green')
        self.rc.add_command(label ="Paste", background='black',
                            foreground='green',
                            activeforeground='black',
                            activebackground='green')
  
        self.menubar = tk.Frame(self,bg='black', bd=1, relief='raised')
        self.title = tk.Label(self.menubar, bg='black',fg='green')

        self.file = tk.Menubutton(self.menubar, text='File',
                            background='black',
                            foreground='green',
                            activeforeground='black',
                            activebackground='green'
                            )
        self.file_menu = tk.Menu(self.file,tearoff=0)
        self.file_menu.add_command(label='new',command= lambda: self.new_file(self.text_box),
                            background='black',
                            foreground='green',
                            activeforeground='black',
                            activebackground='green'
                            )
        self.file_menu.add_command(label='open',command= lambda: self.open_file(self.title,self.text_box),
                            background='black',
                            foreground='green',
                            activeforeground='black',
                            activebackground='green'
                            )
        self.file_menu.add_command(label='save',command= lambda: self.save_text(self.title,self.text_box),
                            background='black',
                            foreground='green',
                            activeforeground='black',
                            activebackground='green'
                            )
        self.file_menu.add_command(label='save as',command= lambda: self.save_file(self.title,self.text_box),
                            background='black',
                            foreground='green',
                            activeforeground='black',
                            activebackground='green'
                            )
        self.file.config(menu=self.file_menu)
        self.file.pack(side='left')

        self.edit = tk.Menubutton(self.menubar, text='Edit',
                            background='black',
                            foreground='green',
                            activeforeground='black',
                            activebackground='green'
                            )

        self.edit_menu = tk.Menu(self.edit,tearoff=0)
        self.edit_menu.add_command(label='add',
                            background='black',
                            foreground='green',
                            activeforeground='black',
                            activebackground='green'
                            )

        self.edit.config(menu=self.edit_menu)
        self.edit.pack(side='left')

        self.close = tk.Button(self.menubar, text='X',bg='black',fg='green', command=lambda:self.esc(self.title,self.text_box))
        self.close.pack(side='right')

        self.title.pack(side='right')
        self.menubar.pack(side='top', fill='x')
        self.text_box.pack(expand=True, fill='both')  

        
        self.text_box.bind("<Button-3>", self.do_popup)
        
        self.after(6000, self.autosave, self.title,self.text_box)
        #sizebar
        self.grip = ttk.Sizegrip(self)
        self.grip.place(relx=1.0, rely=1.0, anchor="se")

        self.text_box.bind('<KeyRelease-greater>', lambda event: self.insertTime(event, self.text_box))
        #text_box.bind('<Shift-KeyPress->>',lambda event: root.handle_wait(event, text_box))

        self.grip.bind("<B1-Motion>", lambda event:self.OnMotion(event))
        self.menubar.bind("<Button-1>", self.startMove)
        self.menubar.bind("<ButtonRelease-1>", self.stopMove)
        self.menubar.bind("<B1-Motion>", self.moving)
        self.bind('<Control-s>', lambda event: self.save_text(self.title,self.text_box))

Then I import it into the file. Is this the proper way of making TopLevel child programs. Also is this another reason why the code isn’t working because i didn’t setup the child windows good enough.

Help is appreciated please and thank you.

——–Edited: Added full note class initializer

Asked By: warsame warsame

||

Answers:

To summarize the code below:

  • I’ve created a list where I store the instance of my App1.
  • I do this by access the master of the Toplevel
  • I remove the instance of my list when the app gets destroyed
  • I check the existence of the app by iterating over the list an check for the instance of the class

I’ve left comments and docstrings in the code as additional help. The mechanic will stay the same if you access it from a different file, at least in your example where you instantiate the apps in the root window. If you ever do that from another window you would need to find the root window and parse the class or you do another list of apps.

import tkinter as tk

class Root(tk.Tk):

    def __init__(self):
        super().__init__()
        self.child_apps = []

        tk.Button(self, text='App 1', command = self.open_1).pack()
        return None

    def check_for_app1(self):
        'returns wheter or not an instance of App1 is in child_apps'
        result = []
        for child_app in self.child_apps:
            res = isinstance(child_app, App1)
            result.append(res)
        return not any(result)

    def open_1(self):
        if self.check_for_app1():
            app = App1(self)
        else:
            print('App1 already displayed')
        return
    pass

class App1(tk.Toplevel):

    def __init__(self,master):
        super().__init__(master)
        master.child_apps.append(self) #register app in root
        self.title('App One')
        self.wm_protocol('WM_DELETE_WINDOW', self.on_destroy)
        return None

    def on_destroy(self):
        'Removes the app in the root application'
        self.master.child_apps.remove(self)
        super().destroy() #calls destroy from tk.Toplevel
        return None
    pass

Root().mainloop()
Answered By: Thingamabobs
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.