check if Toplevel windows was closed? - python

I have a tkinter app, created with customtkinter:
import customtkinter
class App(customtkinter.CTk):
def __init__(self):
super().__init__()
Extra()
self.mainloop()
class Extra(customtkinter.CTkToplevel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.geometry("400x300")
self.label = customtkinter.CTkLabel(self, text="ToplevelWindow")
self.label.pack(padx=20, pady=20)
App()
I am trying to figure out code that checks if the Extra window has been closed. I've been looking around and cannot seem to find anything useful. Is there a way of doing this?

Based on answers in this thread How do I handle the window close event in Tkinter?:
If a WM_DELETE_WINDOW message arrives when you haven't defined a handler, then Tk handles the message by destroying the window for which it was received.
We could add a protocol named WM_DELETE_WINDOW and use the self.destroy() method as such:
class Extra(customtkinter.CTkToplevel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.geometry("400x300")
self.label = customtkinter.CTkLabel(self, text="ToplevelWindow")
self.label.pack(padx=20, pady=20)
self.protocol("WM_DELETE_WINDOW", self.closed) # <-- adding the protocol
def closed(self):
print("I've been closed!")
self.destroy()
Resulting in:
I've been closed!
And we then terminate the extra window.

Related

Detect main 'close button' was clicked from overriden TopLevel widget

I just want to close/destroy an overriden TopLevel widget when the main close button ('X' button) is clicked.
The overriden TopLevel widget is not created from the root of tKinter, but from a frame.
class MyToplevel(Tki.Toplevel):
def __init__(self, parent, *args, **kwargs): # parent is not the root of tKinter, it's a frame
super().__init__(*args, **kwargs)
self.protocol("WM_DELETE_WINDOW", self.on_closing) # Not working
self.wm_protocol("WM_DELETE_WINDOW", self.on_closing) # Not working
def on_closing(self):
# The key is, how can I call this method when main close button is clicked?
self.destroy()
I also have tried self.winfo_ismapped() and self.winfo_exists(), but when I click in the close button, nothing happens, because the main window exists.
If you want to close the toplevel when the close button ("X") on the root window is clicked, then you need to bind the protocol "WM_DELETE_WINDOW" on the root window as well:
class MyToplevel(Tki.Toplevel):
def __init__(self, parent, *args, **kwargs): # parent is not the root of tKinter, it's a frame
super().__init__(parent, *args, **kwargs)
# find root window
self.root = self.winfo_toplevel()
while self.root.master:
self.root = self.root.master.winfo_toplevel()
self.handler = self.root.protocol("WM_DELETE_WINDOW")
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
self.protocol("WM_DELETE_WINDOW", self.on_closing)
def on_closing(self):
# restore default handler for root window
self.root.protocol("WM_DELETE_WINDOW", self.handler)
self.destroy()
Note that this does not work when more than one toplevel is open simultaneously.

Using super() on tk.TopLevel, but do not generate second window?

I am attempting to implement a trivial MVC application.
When super().__init__() is called, two windows are generated in my python application.
What is the appropriate usage when attempting to inherit from tk.TopLevel, when I only want to generate one window?
import tkinter as tk
from tkinter import ttk
class View(tk.Toplevel):
def __init__(self, controller):
super().__init__()
self.controller = controller
def exec_main(self):
self.mainloop()
class Controller:
def __init__(self):
self.view = View(self)
if __name__ == '__main__':
app = Controller()
app.view.exec_main()
Doing something like super().__init__(master=self) or super().__init__(self) does not seem to be the solution.
An alternative approach would be to do something like this for the main:
import tkinter as tk
class View(tk.Toplevel):
def __init__(self, master):
tk.Toplevel.__init__(self, master)
class Controller:
def __init__(self, root):
self.view = View(root)
if __name__ == '__main__':
root = tk.Tk()
root.withdraw()
app = Controller(root)
root.mainloop()
But this seems wasteful.
Without exception, every widget except the root window requires a parent. If you don't create a root window then one will be created for you. When you create an instance of Toplevel and call super().__init__(), if you don't have a root window then tkinter will create one for you.
As you've observed, the correct workaround is to explicitly create a root window and then hide it. You have to make sure to give the user a way to destroy this root window since it won't automatically be destroyed when you close the Toplevel windows.

Having trouble with Tkinter using OOP

I have created a small application in tkinter before using just top down programming but I am starting another project, this time using OOP and classes. But I'm having a hard time getting started, I just need someone to point me in the right direction. I've already dabbled in OOP with PyGame but I'm having difficulty with tkinter. Heres my code, where i'm just trying to display a button to the screen:
import tkinter as tk
from tkinter import ttk as ttk
import sqlite3
class Button(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
tk.Button(root, text = "Hello", width = 25)
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.button = Button()
self.button.pack(side="bottom",fill="x")
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
Try this:
import tkinter as tk
from tkinter import ttk # The `as tkk` isn't needed
# Here you might want to consider inheriting from `tk.Button` but it isn't going to change anything
class Button(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
# It's always a good idea to keep a reference to all of your widgets
self.button = tk.Button(self, text="Hello", width=25)
# You should call `.pack`/`.grid`/`.place` here:
# Note it doesn't really matter which one you choose
self.button.pack(fill="both")
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent # Technically this isn't needed because the line above sets `self.master` to parent
self.button = Button(self) # Pass in self into the Button class
self.button.pack(side="bottom", fill="x")
if __name__ == "__main__":
root = tk.Tk()
main_app = MainApplication(root)
main_app.pack(side="top", fill="both", expand=True)
root.mainloop()
I passed in self when creating the Button object in self.button = Button() and I called self.button.pack(...) inside the Button class.
The whole point of OOP programming it to limit global variables and group similar objects in a single class definition. That is why both your Button and MainApplication classes shouldn't rely on root. Apart from that, your code is very nice :D

How to handle Invalid command name error, while executing (“after” script) in tkinter python using OOP Approach

I have seen a lot of questions similar to mine on this thread however none of them are relatable to the OOP tkinter method that I am using. I am getting the error:
invalid command name "2452827444616callback"
while executing "2452827444616callback"
("after" script)
This happens because I am using the "after" function in a recursive loop but get this error whenever I close the tkinter window and try to open it back up again.
A simple example of my code structure which generates this problem is below:
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self._parent = parent
self.var = tk.IntVar()
tk.Label(self._parent, textvariable=self.var).pack()
self.callback() #This starts the recursion loop
def callback(self):
global after_id
self.var.set(self.var.get() + 1)
after_id = root.after(500, self.callback)
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
Similar issues have been solved like in this thread:
How to handle Invalid command name error, while executing ("after" script) in tkinter python
However, I cannot seem to get it to work due to the different structure that I am using with the OOP tkinter approach. Any help would be greatly appreciated.
You can cancel the after task when the frame is being destroyed by calling after_cancel() inside __del__() function of MainApplication class:
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.var = tk.IntVar()
tk.Label(self, textvariable=self.var).pack()
self.callback() #This starts the recursion loop
def __del__(self):
self.after_cancel(self.after_id)
def callback(self):
self.var.set(self.var.get()+1)
self.after_id = self.after(500, self.callback)

Using .get() to pull a Tkinter variable from another class

I am writing a Tkinter application that requires parts of the user display to exist in two different class, both imported from another file. I need to take a piece of user input and pass it from one class to another. In the toy example below, the user is supposed to type something into my_entry_input which later the class PullVariable is supposed to access.
Code is below. I had a few thoughts, such as somehow using globals or creating a function within the original class to get the variables and then pass them back. In all cases, I get:
AttributeError: type object 'Application' has no attribute 'my_entry'
The best solution I can think of is to create a function that responds to the binding, then pass the .get() to the other class from that function. My feeling is that tkinter doesn't like .get() in between classes.
Thanks to the community for your help.
MAIN
from import_test1 import *
root=Tk()
ui = Application(root)
ui.hello_world()
ui.entry()
pv = PullVariable()
if __name__ == '__main__':
root.mainloop()
IMPORTED CODE
from tkinter import *
from tkinter import ttk
class Application(Frame):
def __init__(self, parent, *args, **kwargs):
print('Application init')
Frame.__init__(self, parent, *args, **kwargs)
self.parent=parent
self.parent.grid()
def entry(self):
self.my_entry = StringVar(self.parent)
my_entry_input = Entry(self.parent, textvariable=self.my_entry,
width=16)
my_entry_input.bind('<FocusOut>', self.show_entry)
my_entry_input.grid(column=0, row=1)
self.show_label = Label(self.parent, text = '')
self.show_label.grid(column=0, row=2)
def hello_world(self):
print('hello world')
self.hw = Label(self.parent, text='Hello World!')
self.hw.grid(column=0, row=0)
def show_entry(self, event):
PullVariable(Application).find_entry()
class PullVariable:
def __init__(self, app):
self.app = app
print('Pull initiated')
def find_entry(self, event=None):
self.pulled_entry = self.app.my_entry.get()
self.app.show_label['text'] = self.pulled_entry
my_entry is not a attribute of Application class, so you can't do Application.my_entry, by it is an attribute of instance of Application class, so you can do Application().my_entry. You can probably add either instance of Application or my_entry to the __init__ method of PullVariable. I'll use the former.
# ...
class PullVariable:
def __init__(self, app): # add app here
self.pulled_entry = app.my_entry.get() # use app here
print(self.pulled_entry)
# and then
root=Tk()
ui = Application(root)
ui.hello_world()
ui.entry()
pv = PullVariable(ui) # supply PullVariable with instance of Application

Categories

Resources