In Python using tkinter, what is the difference between root.destroy() and root.quit() when closing the root window?
Is one prefered over the other? Does one release resources that the other doesn't?
root.quit() causes mainloop to exit. The interpreter is still intact, as are all the widgets. If you call this function, you can have code that executes after the call to root.mainloop(), and that code can interact with the widgets (for example, get a value from an entry widget).
Calling root.destroy() will destroy all the widgets and exit mainloop. Any code after the call to root.mainloop() will run, but any attempt to access any widgets (for example, get a value from an entry widget) will fail because the widget no longer exists.
quit() stops the TCL interpreter. This is in most cases what you want, because your Tkinter-app will also stop. It can be a problem, if you e.g. call your app from idle. idle is itself a Tkinker-app, so if you call quit() in your app and the TCL interpreter gets terminated, idle will also terminate (or get confused ).
destroy() just terminates the mainloop and deletes all widgets. So it seems to be safer if you call your app from another Tkinter app, or if you have multiple mainloops."
taken from http://www.daniweb.com/forums/thread66698.html
The tkinter.Tk "quit" method exits the "mainloop" event-handler, and "destroy" destroys all the embedded widgets and only then exits the "mainloop". So is "destroy" the better of the two? Well, sometimes not. If "destroy" fails to destroy all the widgets for some reason, then "mainloop" is never exited and Python locks up. It can be better to just let Python shut things down in an orderly manner at the end of the script.
For example, if you embed a Matplotlib plot in a Tkinter window, that is useful because Matplotlib's own widgets are somewhat clunky to use. Unfortunately, if you then try to close the window by clicking the usual "X" in the title-bar, the window closes alright but leaves Python running. If the script had been started from a terminal, you'd have to mash Ctrl-C for a couple of minutes to get the prompt back. The reason being that the window-close event is bound to "destroy" which does not destroy the Matplotlib objects but leaves them orphaned.
The fix is to bind the window-close event to "quit" instead. But... if the script is started in a Tkinter-based IDE like IDLE, then that creates a new problem in that the window does not close because IDLE holds Tkinter running. So now "destroy" must be added after the mainloop. Finally, all is well.
Below is a minimal example of a Matplotlib plot that can be inverted with a press of a Tkinter button. Its window can be closed without problems. But if the windows-close event had been bound to "destroy" instead of "quit" then a locked-up Python process would remain.
#!/usr/bin/env python3
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
root = tk.Tk()
data, = plt.plot([0,5,3,4,-5,3])
canvas = FigureCanvasTkAgg(plt.gcf(), master=root)
invert = lambda: (data.set_ydata(-data.get_ydata()), canvas.draw())
tk.Button(master=root, text="Invert", command=invert).pack()
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=1)
root.protocol("WM_DELETE_WINDOW", root.quit)
root.mainloop()
root.destroy()
Edit: I'll add that by binding the window-close event to both methods, you can avoid adding a line after the "mainloop", should that be desirable for some reason:
root.protocol("WM_DELETE_WINDOW", lambda: (root.quit(), root.destroy()))
My experience with root.quit() and root.destroy() ...
I have a dos python script, which calls a tkinter script (to choose from set of known values from combobox), then returns to the dos script to complete other things.
The TKinter script I've added onto the parent script. I may convert all to tkinter, but a combo works for the time being. It works in the following way:
To get rid of the windows box after selection was implemented, I needed to
1) root.quit() inside the callback function where all my keypresses were being processed.
2) root.destroy() after mainloop to destroy the windows box.
If I used root.destroy() inside the callback, I got an error message saying tkinter objects were no longer accessable.
Without the root.destroy() after mainloop, the windows box STAYED ONSCREEN until the whole parent script had completed.
Related
I'm learning Tkinter and just had a doubt.
Is there any method that prevents me from working with the window in the background until I close the main window? I saw that there is an argument about this here, but I ended up losing it and decided to ask.
Yes, it is possible (IIUC) and if I do understand correctly you simply want to block user for accessing other windows while your window is open. It has a slight flaw in that if user does a lot of alt-tabbing they may get keyboard focus on some other window but only the keyboard, as soon as they do anything with mouse they should get back to the main window. This can be handled using some module like keyboard to listen for global keyboard input but also perhaps it could be done using Windows API but for a tkinter only solution that is/should be effective enough you can use this:
import tkinter as tk
def check_focus():
if root.focus_get() is None:
root.focus_force()
root.after(100, check_focus)
def create_shield():
shield = tk.Toplevel(root)
shield.attributes('-topmost', True)
shield.attributes('-fullscreen', True)
shield.attributes('-alpha', 0.01)
shield.overrideredirect(True)
shield.bind('<FocusIn>', lambda _: root.focus_force())
root = tk.Tk()
root.attributes('-topmost', True)
create_shield()
check_focus()
root.mainloop()
First there is the after "loop" that checks for focus to see if the window has any, this prevents some attempts of trying to alt-tab away but enough attempts may cause some kind of timeout and the window will get out of focus.
The other part is a "shield" that simply is another fullscreen window that is almost completely invisible (on other OS the attribute name may be -splash instead of -alpha) and just keeps it in top to assist in focusing on the main window.
Or just make the window fullscreen and topmost (again the issues with keyboard focus on other windows but now user has no issues whatsoever with accessing the other windows except they wouldn't see what they are typing) and this requires to implement some button to close the window (currently doable by alt+f4 or shutting down the computer or killing the process via run without looking at what you are typing and perhaps some other way I haven't thought about, but for user convenience probably a button should be made that would simply destroy the window):
import tkinter as tk
root = tk.Tk()
root.attributes('-topmost', True)
root.attributes('-fullscreen', True)
root.mainloop()
I realize there are many examples of how to properly close a Tkinter GUI by calling the root.destroy() function. They work with my setup except I've determined that including a variable of type tkinter.intvar causes the gui process to live on even after I close the window. Here's a working example:
import mtTkinter as Tkinter #special threadsafe version of Tkinter module, required for message windows. You can use regular Tkinter if needed
root = Tkinter.Tk()
root.wm_title("KillmePlz")
nexusvar = Tkinter.IntVar()
def ClosebyX():
root.destroy()
closebutton = Tkinter.Button(root,text='Quit',command=ClosebyX)
closebutton.pack()
root.protocol('WM_DELETE_WINDOW', ClosebyX)
root.mainloop()
On my machine if I remove the creation of "nexusvar", the Tkinter.IntVar, when I close the GUI the process also stops. If this variable is included as shown above, I see the process linger after the gui is closed. I don't think mtTkinter makes any difference.
Does anyone know why this might be happening?
Windows 7 64 bit, Python 2.7.12
UPDATE 9/20/16:
mtTkinter is the source of this problem. The solution below is for regular Tkinter module use. For solving this problem using mtTkinter see the following post
nexusvar isn't a child of root, so when you destroy root it doesn't know to destroy nexusvar as well - the two things are separate. You can set an IntVar to have root as a parent by supplying root to the constructor. nexusvar should then be able to destroy itself when root dies.
I have an issue whereby a user can open a top_level window which sits waiting for a variable via the wait_variable() method. But while the top_level window is open, the root window is still visible and the user is able to close the root window via usual methods (this is intentional). What I'd like (and sort of expect tkinter to do) is that calling .destroy() or .quit() on the root window will cause all of root's children to be terminated. But what appears to be happening is that the top_level window remains stuck in its local event loop and can only be killed via the task manager once its parent has gone.
So what am I doing wrong? How do I get the top_level window to listen out for it's parents destruction while its in a local event loop?
.
Below is some example code that demonstrates the issue. If you run the code and follow the steps below your IDE WILL CRASH!! probably, so save you work. Push the Click button in root. The top level window will appear with another button that says Test. Now close the root window. The IDE will hang.
import tkinter
root = tkinter.Tk()
def toplevel(event=None):
def set1(event=None):
vr.set(1)
tp = tkinter.Toplevel(root)
vr = tkinter.IntVar()
bt_ = tkinter.Button(tp,text='Test',command=set1)
bt_.grid()
tp.wait_variable(vr)
tp.destroy()
bt = tkinter.Button(root,text='Click',command=toplevel)
bt.grid()
root.mainloop()
Edit: My current solution is to repoint the WM_DELETE_WINDOW protocol when the top_level window starts, to a function that sets the variable that the local event loop is waiting for, and then destroys the root window.
The correct solution IMO is to not wait on the variable, but rather, wait on the window. Your button then must take on the responsibility of destroying the window instead of or in addition to setting the variable.
This is the right solution for another reason: as written your program will also hang if the user destroys the toplevel with the buttons in the titlebar. Since you're waiting on a variable, because the window is destroyed the variable will never be set.
(the solution you proposed in your edit is also valid -- register with WM_DELETE_WINDOW on the toplevel to set the variable when the window is destroyed. The effect will be the same).
I'm making a simple window to hold my NetworkX graph, and I want this GUI component to be separate from my logic. So what I'm trying to achieve is to make a function that makes the window, initializes the graph, starts the mainloop in a Thread and returns the gui reference. I've done this exact thing without networkX and canvas involved, and it works great, but now this doesn't work for some reason. What I have is:
def getNewGraphWindow():
root = Tk()
app = GraphUI(root)
root.mainloop()
#mainThread = Thread(target=root.mainloop)
#mainThread.start()
return app
So this code works perfectly, it creates the window, draws the graph and all that, but obviously I don't get the "app" reference cause its stuck in the mainloop. But if I replace that code with the 2 commented out lines, to simply run root.mainloop() in its own thread, all the code runs, but the window doesn't respond and the graph is not drawn. What's wrong?
You have to run the mainloop in the thread where you created the Tcl interpreter, according to the _tkinter source code:
The Tcl interpreter is only valid in the thread that created it, and
all Tk activity must happen in this thread, also. That means that the
mainloop must be invoked in the thread that created the interpreter.
So you'll need to run the mainloop in the main thread, and do whatever else you need to do in a background thread. You actually might be able to run the mainloop in a background thread, as long as you also create the Tk element in that same thread.
My Python program consists of two parts, one gets user credentials through an Tkinter and passes it to the other script which then processes them.
It works fine but the only problem is that although my GUI passes data and then the processing script starts its work, The GUI starts not-responding and causes havoc as it freezes until the download completes (which could potentially take hours)
I create an object of the Interface class in the processing script by importing the GUI script
root = Tk.Tk()
root.title('Coursera-dl')
root.geometry("345x100")
app = GUI.Interface(root)
app.mainloop()
This is my GUI script's destruct method defined in a Class which is executed automatically after the data has been received by the processing script: However when the user clicks 'OK' the GUI freezes and doesn't exit and If I force quit it, the processing script also ends as python.exe is terminated
*code*
....................................
def destruct(self):
if tkMessageBox.askokcancel(title=None,message='Download would start upon closing the Interface, If any changes are to be made to the credentials Please do so by selecting Cancel'):
self.quit()
How can I make my program so that when the user clicks on 'OK' the GUI quits safely and the processing script does its work
root.quit() just Bypasses the root.mainloop() i.e root.mainloop() will still be running in background if quit() command is executed.
Use root.destroy()
this will stop root.mainloop() itself but it wont close the python program and everything will still be ran just without the GUI