Prevent multiple toplevels from opening? - python

I have a object oriented tkinter program set up.
I have initialized a variable to store Toplevel() in as
self.toplevel = None
Then when I create the actual Toplevel window I simply assign it to the variable:
self.toplevel = Toplevel()
The thing is...when the Toplevel() window is closed, the value still remains in the variable self.toplevel. How can I reset the variable back to None after closing the window so that I can perform a check:
if (self.toplevel == None):
self.toplevel = Toplevel()
Or are there any other methods to prevent multiple Toplevel Windows from opening?

Check this How do I handle the window close event in Tkinter?
Assign the value None to self.toplevel after the Toplevelcloses usign a callback function TopCloses. For this, write a method within the GUI class to access the toplevel attribute and set it's value to None inside the callback function.
In your main program,
def TopCloses():
top.destroy()
#Call the setTopLevel method and assign the attribute toplevel value None
guiObject.setTopLevel(None)
top.protocol("WM_DELETE_WINDOW", TopCloses)
root.mainloop()

Here is my solution:
#somewhere in __init__ make
self.window = None
#I took this piece of code from my bigger app and I have a function
#self.makevariables(), which is called in init, which contains the line above.
def instructions(self):
if self.window == None: #here I check whether it exists, if not make it, else give focus to ok button which can close it
self.window = Toplevel(takefocus = True)
#some optional options lol
self.window.geometry("200x200")
self.window.resizable(0, 0)
#widgets in the toplevel
Label(self.window, text = "NOPE").pack()
self.window.protocol("WM_DELETE_WINDOW", self.windowclosed) #this overrides the default behavior when you press the X in windows and calls a function
self.okbutton = Button(self.window, text = "Ok", command = self.windowclosed, padx = 25, pady = 5)
self.okbutton.pack()
self.okbutton.focus()
self.okbutton.bind("<Return>", lambda event = None:self.windowclosed())
else:
self.okbutton.focus() #You dont need to give focus to a widget in the TopLevel, you can give the focus to the TopLevel, depending how you want it
#self.window.focus() works too
def windowclosed(self): #function to call when TopLevel is removed
self.window.destroy()
self.window = None

These are all overly complicated solutions imo.
I just use win32gui as such:
toplevel_hwid = win32gui.FindWindow(None, '<Top Level Window Title>')
if toplevel_hwid:
print(f'Top level window already open with HWID: {toplevel_hwid}')
win32gui.SetForegroundWindow(toplevel_hwid)
return
else:
<create new top level>
Simple, easy, and gives you the flexibility to close, move, focus, etc.

Related

How to access a widget in another widget event handler : Tkinter

I am creating a GUI in tkinter having a listbox and a Text item in a child window which appears after a button click. Listbox is displaying values of a dict which are basically names of files/directories in disk image.
I want to change the text of Text widget on <ListboxSelect> event and display type or path of selected file.
Now I cant make Text global since it has to appear on child window, so I need a way to access it in event handler of Listbox. Can I give handler reference of Textbox?
Here is my code;
def command(event):
... #Need to change the Text here, how to access it?
def display_info(dict,filename):
child_w = Tk()
listbox = Listbox(child_w)
textview = Text(child_w)
...
listbox.bind(<ListboxSelect>,command)
def upload_file():
window = Tk()
upl_button = Button(command=upload_file)
window.mainloop()
Is there a way to create a textview as global and then change its properties later to be displayed in child_window etc.
Two solutions here that I can think of is to make textview a globalized variable or to pass textview to command() as an argument.
Parameter solution:
def command(event,txtbox):
txtbox.delete(...)
def display_info(dict,filename):
child_w = Tk()
listbox = Listbox(child_w)
textview = Text(child_w)
...
listbox.bind('<ListboxSelect>',lambda event: command(event,textview))
Or simply just globalize it:
def command(event):
textview.delete(...)
def display_info(dict,filename):
global textview
child_w = Tk()
listbox = Listbox(child_w)
textview = Text(child_w)
...
listbox.bind('<ListboxSelect>',command)
While saying all this, it is good to keep in mind that creating more than one instance of Tk is almost never a good idea. Read: Why are multiple instances of Tk discouraged?

Is there a way to reopen a window after closing it using destroy() in tkinter?

window6.after(1,lambda:window6.destroy())
is what I've been using to close my windows, is there any way to get them back after doing this?
basically, is there something that is the opposite of this?
ps. these are the libraries that I've imported, if it helps in any way
import tkinter as tk
from tkinter import *
import time
from tkinter import ttk
Is there a way to reopen a window after closing it using destroy() in tkinter?
The short answer is "no". Once it has been destroyed, it is impossible to get back. You should either create the window via a frame or class so that it's easy to recreate, or hide the window by calling .withdraw() rather than .destroy().
If you put the window code into a class or a function then after destroying it you can create a new instance of it by
1: creating a new instance of the class with the window code in the init function
2: call the function the has the code for the window
By doing this you are essentially creating a new instance of the program, but without initiating the script.
from tkinter impot *
from tkinter import ttk
#creating window function, not class
def main_window():
#window code here
root = Tk()
Label(root, text = "Hello World").pack()
#destroying main window
root.destroy()
root.mainloop()
main_window()
Of course, there are a few hurdles such as the window shutting down as soon as it opens, but this is to show that you can create a new instance of a window from your program.
You can wait for user input to see whether or not the window will open or close.
If you took an OOP approach, you can pass a reference to the Parent Widget as argument to the New_Window and store it in a class attribute.
You´ll have a two way reference: Parent knows child and child knows parent.
Then you can set the Parent Reference to the New_Window to None, from within the child Widget self.parent.new_window = None in a close_me() method right after you call self.destroy() on the New_Window:
1st Bonus: this code prevents the opening of more than 1 instance of a Window at a time. You won´t get more than 1 New_Window on the screen. I don´t think having two loggin windows opened or two equal options window makes sense.
2nd Bonus: It is possible to close the window from other parts of the code, as in a MVC patter, the Controller can close the window after doing some processing.
Here´s a working example:
import tkinter as tk
class Toolbar(tk.Frame):
'''Toolbar '''
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
# to store the New Window reference
self.new_window = None
self.button_new_window = tk.Button(self, text = 'New Window', command = lambda : self.get_window(self))
self.configure_grid()
def configure_grid(self):
'''Configures the Grid layout'''
self.grid(row=1, column=0, columnspan=3, sticky=(tk.N,tk.S,tk.E,tk.W))
self.button_new_window.grid(row = 2, column = 2, padx=5, pady=5)
def get_window(self, parent):
''' If window exists, return it, else, create it'''
self.new_window = self.new_window if self.new_window else Window(parent)
return self.new_window
class Window(tk.Toplevel):
'''Opens a new Window.
#param parent -- tk.Widget that opens/reference this window
'''
def __init__ (self, parent : tk.Widget):
# Stores reference to the Parent Widget, so you can set parent.new_window = None
self.parent = parent
super().__init__(master = parent.master)
self.title('New Window')
self.button_dummy = tk.Button(self, text = 'Do the thing', width = 25, command = lambda : print("Button pressed on window!"))
self.button_close = tk.Button(self, text = 'Close', width = 25, command = self.close_me)
self.configure_grid()
def configure_grid(self):
'''Grid'''
self.button_dummy.grid(row = 1, column = 0)
self.button_close.grid(row = 2, column = 0)
def close_me(self):
'''Tkinter widgets are made of two parts. 1. The python Object and 2. The GUI Widget.
The destroy() method gets rid of the widget part, but leaves the object in memory.
To also destroy the object, you need to set all of its references count to ZERO on
the Parent Widget that created the new Window, so the Garbage Collector can collect it.
'''
# Destroys the Widget
self.destroy()
# Decreasses the reference count on the Parent Widget so the Garbage Collector can destroy the python object
self.parent.new_window = None
if __name__ == '__main__':
root = tk.Tk()
toolbar = Toolbar(root)
root.mainloop()
I don´t know if this: .destroy() and re-instantiate approach is more efficient than the .withdraw() and .deiconify(). Maybe if you have a program that runs for long periods of time and opens a lot of windows it can be handy to avoid stackoverflow or heapoverflow.
It sure frees up the object reference from memory, but it has the additional cost of the re-instantiation, and that is processing time.
But as David J. Malan would say on CS50, “There´s always a tradeoff”.

Why doesn't Tkinter Entry's get function return anything?

I'm trying to use two dialogs to get manual input, and then work with that data.
All source I've found claim I should use the get() function, but I wrote a simple mini program yet, and I can't make the second dialog work.
I hope someone can tell me what I'm doing wrong. Here's a file:
from tkinter import *
from tkinter.filedialog import askdirectory
from tkinter import messagebox
def getpath():
def selectPath():
path_ = askdirectory()
path.set(path_)
root = Tk()
root.title('select path')
path = StringVar()
def close():
if(path.get()==""):
messagebox.showinfo("","nothing")
else:
root.withdraw()
root.quit()
Label(root,text="path:").grid(row=0,column=0)
Entry(root,textvariable = path).grid(row=0,column=1)
Button(root,text="select",command=selectPath).grid(row=0,column=2)
Button(root,text="enter",command=close).grid(row=0,column=3)
root.mainloop()
return path.get()
def getname():
def get_need_name():
name = need_name.get()
print('hereherehere'+name) #does not work
root = Tk()
root.title('select name')
need_name = StringVar()
Label(root,text="name:").grid(row=0,column=0)
entry = Entry(root,bd=10,textvariable=need_name)
entry.grid(row=0,column=1)
Button(root,text="enter", font=16, bg="silver", relief='groove', command=get_need_name).grid(row=0,column=2)
root.mainloop()
return name.get()
def main():
path = getpath()
print("mypath:"+path)
print('******************')
print('done!')
name = getname()
print("myname:"+name)
if __name__ == '__main__':
main()
This give me two dialogs I can type in, but only the first dialog works.
The reason is that you are creating multiple instances of Tk, and you don't destroy the instances when you are done with them. This causes two problems. First is a memory leak. Each time you call one of these functions you create a new window and a new tcl interpreter.
The second problem is that the first root window becomes the default window when a root isn't specified. When you create a StringVar in the second function, because you didn't specify which root window it belongs to it will be assigned to the first root window. When you use it as the target of textvariable in a second instance of Tk, tkinter thinks the variable doesn't exist so it creates a new one for the second window. However, your reference is still to the one created in the first root window and is never updated by user input in the second window.
Confusing? Yes, which is why you typically shouldn't be creating more than one instance of Tk.
To make your code work with as few changes as possible and to remove the memory leak caused by not destroying the windows, you can change the last couple of lines in your method to look like the following. This destroys the root window when you are done with it, removing the memory leak and the side effect of having more than one root window.
root = Tk()
...
root.mainloop()
value = path.get()
root.destroy()
return value
The second dialog should look similar:
root = Tk()
...
root.mainloop()
value = name.get()
root.destroy()
return value
This retrieves the value after mainloop exits but before the underlying tcl interpreter is deleted, and then destroys the window and its tcl interpreter.
The next time you create an instance of Tk, that instance will become the new default root, and any new instance of StringVar will go to that root.
Another solution would be to specify the master for the StringVar instance, but that leaves the memory leak in place so it's only half of a solution.
Arguably a better solution is to create a single root window, and either reuse it or create instances of Toplevel rather than Tk. Effbot has some decent documentation on how to create a modal window with wait_window.
After some testing, and googling the root.quit() is the problem
here is a working example for you to look at and mess with.
from tkinter import *
from tkinter.filedialog import askdirectory
from tkinter import messagebox
root = Tk()
path = StringVar()
def select_path():
#uses the return value to set no need to create an additional variable
path.set(askdirectory())
def close():
if path.get() == "":
messagebox.showinfo("","Please select path")
else:
get_name_frame.tkraise()
def get_name():
print("hereherehere", name.get())
get_path_frame = Frame(root)
get_path_frame.grid(row=0, column=0, sticky="nsew")
Label(get_path_frame,text="path:").grid(row=0,column=0)
Entry(get_path_frame,textvariable = path).grid(row=0,column=1)
Button(get_path_frame,text="select",command=select_path).grid(row=0,column=2)
Button(get_path_frame,text="enter",command=close).grid(row=0,column=3)
get_name_frame = Frame(root)
get_name_frame.grid(row=0, column=0,sticky="nsew")
Label(get_name_frame, text="name: ").grid(row=0, column=0)
name = StringVar()
entry = Entry(get_name_frame, bd=10, textvariable = name)
entry.grid(row=0, column=1)
Button(get_name_frame,text="enter", font=16, bg="silver", relief='groove', command=get_name).grid(row=0,column=2)
get_path_frame.tkraise()
root.mainloop()

tkinter run function in parent window when child window closes

I have a parent window in tkinter that opens a child window like this:
def openChild(data):
callChildFunc(data,"y")
Button(dash, text="Open", command=partial(openChild, data))
This calls a function callChildFunc(data,fill) which takes input and saves it to a json file. The child window closes like this:
def getInput(file):
#bunch of commands to save data from input fields...
childform.destroy()
Button(childform, text="Save", command=partial(getInput, data))
I want to call a function in the parent window when the child window is destroyed. Is there any way for the parent window to detect when the child is destroyed, or a way to pass an argument from the destroyed window back to the parent?
Found a solution that does what I want, execute a function when the child window is destroyed:
child.bind("<Destroy>",parentfunction())
You could just use a global variable, to which getInput could save it's data, and the parent window could then access afterwards.
If you don't mind rewriting your code, another way of doing it would be to use classes to manage your windows, and opening the child as a TopLevel widget ie.:
import tkinter as tk
class parent(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
button1 = tk.Button(self, text="Open", command=lambda : self.openChild())
# class variable for saving input of child
self.data = None
def openChild(self):
self.child = tk.TopLevel()
# entry box for data
self.entry1 = tk.Entry(self.child)
# button for saving data
button1 = tk.Button(self.child, text='Save', command= lambda : self.getInput())
def getInput(self):
if self.child:
self.data = self.entry1.get()
self.child.destroy()
Don't know if that's the most elegant way of handling that, but using classes gives you a lot more options for tkinter, it's worth a look.

TKInter checkbox variable is always 0

I'm using Python's TkInter module for a GUI. Below is a simple checkbox code.
def getCheckVal():
print cbVar.get()
windowTime=Tk.Tk()
cbVar = Tk.IntVar()
btnC = Tk.Checkbutton(windowTime, text="Save", variable = cbVar, command=getCheckVal)
btnC.grid()
windowTime.mainloop()
This code works fine. Each time I tick the checkbox, I get 1, else 0.
However, when I run the same code in a function that is called from another TkInter command (when a button is pressed), it stops working. I always get 0 as the value.
class GUIMainClass:
def __init__(self):
'''Create the main window'''
self.window = Tk.Tk()
def askUser(self):
def getCheckVal():
print cbVar.get()
windowTime=Tk.Tk()
cbVar = Tk.IntVar()
btnC = Tk.Checkbutton(windowTime, text="Save", variable = cbVar,
command=getCheckVal)
btnC.grid()
windowTime.mainloop()
def cmdWindow(self):
frameShow=Tk.Frame(self.window)
frameShow.grid()
btnSwitch = Tk.Button(frameShow, text='Show Plots', command=self.askUser)
btnSwitch.grid()
self.window.mainloop()
GUIObj=GUIMainClass()
GUIObj.cmdWindow()
This is very unusual. What could be going wrong?
EDIT: I've used 2 mainloops because I want a separate window (windowTime) to open up when I click "Show Plots" button. This new window should have the checkbox in it.
Your windowTime, cbVar, etc. variables are defined in the function's local scope. When askUser() completes execution, those values are thrown away. Prepend self. to them to save them as instance variables.
There should only be one mainloop() in your program, to run the main Tkinter root object. Try putting it as the very last line in the program. I recommend doing some reading on Effbot for how to set up a Tkinter application.
I'm not sure what all you're trying to do, but one problem is that the TK.IntVar called cbVar that you create in your askUser() method will be deleted when the function returns, so you need to attach it to something that will still exist after that happens. While you could make it a global variable, a better choice would be to make it an attribute of something more persistent and has a longer "lifespan".
Another likely issue is that generally there should only be one call to mainloop() in a single Tkinter application. It appears what you want to do is display what is commonly known as a Dialog Window, which Tkinter also supports. There's some standard ones built-in, plus some more generic classes to simplify creating custom ones. Here's some documentation I found which describes them in some detail. You may also find it helpful to look at their source code.
In Python 2 it's in the /Lib/lib-tk/tkSimpleDialog.py file and
in Python 3 the code's in a file named /Lib/tkinter/simpledialog.py.
Below is code that takes the latter approach and derives a custom dialog class named GUIButtonDialog from the generic one included the Tkinter library which is simply named Dialog.
try:
import Tkinter as Tk # Python 2
from tkSimpleDialog import Dialog
except ModuleNotFoundError:
import tkinter as Tk # Python 3
from tkinter.simpledialog import Dialog
class GUIButtonDialog(Dialog):
"""Custom one Button dialog box."""
def __init__(self, btnText, parent=None, title=None):
self.btnText = btnText
Dialog.__init__(self, parent, title)
def getCheckVal(self):
print(self.cbVar.get())
def body(self, master):
"""Create dialog body."""
self.cbVar = Tk.IntVar()
self.btnC = Tk.Checkbutton(master, text=self.btnText, variable=self.cbVar,
command=self.getCheckVal)
self.btnC.grid()
return self.btnC # Return the widget to get inital focus.
def buttonbox(self):
# Overridden to suppress default "OK" and "Cancel" buttons.
pass
class GUIMainClass:
def __init__(self):
"""Create the main window."""
self.window = Tk.Tk()
def askUser(self):
"""Display custom dialog window (until user closes it)."""
GUIButtonDialog("Save", parent=self.window)
def cmdWindow(self):
frameShow = Tk.Frame(self.window)
frameShow.grid()
btnSwitch = Tk.Button(frameShow, text='Show Plots', command=self.askUser)
btnSwitch.grid()
self.window.mainloop()
GUIObj = GUIMainClass()
GUIObj.cmdWindow()

Categories