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:\Users\user\AppData\Local\Programs\Python\Python38\lib\tkinter_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
CodePudding user response:
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 theToplevel
- 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()