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.
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 need to run some simple function in multi-threading with a Tkinter GUI, so I've tried mtTkinter.
Everything works fine except for a particular: even if I just start the GUI and then I close it without touching nothing some thread keeps running.
In other words; I have this code:
from Tkinter import *
root = Tk()
#simple GUI code with buttons, labels, text and scrollbars widget
...
...
root.mainloop()
If I run this code the GUI appears and when I close it this python script ends successfully.
Now if I replace Tkinter with mtTkinter
from mtTkinter import *
root = Tk()
#simple GUI code with buttons, labels, text and scrollbars widget
...
...
root.mainloop()
the GUI appears once again, but if I close it there is still some thread from mtTkinter that keeps running!
Any help would be apprecied, thank you in advance and sorry for my bad english!
I ran into a similar problem for my application (https://github.com/joecole889/spam-filter). After some investigation, I realized that when I close my application Tkinter (or possibly Matplotlib) uses a threading._DummyThread instance to delete one of the widgets. I have a Matplotlib graph in a Tkinter canvas widget in my application. In any case, it looks like an “image delete” event is added to the event queue and mtTkinter blocks waiting for a response on the responseQueue that never comes.
I was able to fix the problem by allowing events from instances of threading._DummyThread to run without going through the queue infrastructure of mtTkinter. That is, I changed:
if threading.currentThread() == self._tk._creationThread:
to
if (threading.currentThread() == self._tk._creationThread) or \
isinstance(threading.currentThread(), threading._DummyThread) :
Things seem to be working for me now...hope this helps!
I've "resolved" not using it. mTkinter seems to be a bit buggy.
This is an old topic, but I don't see where it was even closed. I have a python application using 4 threads using the 'theading' module and MtTkinter.
I was having similar problems with MtTkinter. The application worked but would not close. I have searched and tried quite a few solutions, none worked. For my application, using queues would have been a chore.
Here is what I did. Its not elegant, but it worked. Its pretty ruthless.
cleanup():`
pidx = os.getpid()
cmd1 = "kill" + " " + str(pidx)
if __name__ == "__main__":
os.system(cmd1)
The following code hangs without doing anything in Python 3.2.2 in Linux:
import tkinter
from multiprocessing import Process
def f():
root = tkinter.Tk()
label = tkinter.Label(root)
label.pack()
root.mainloop()
p = Process(target=f)
p.start()
The only information I have found about this problem is issue 5527, in which it is noted that the problem is with tkinter being imported before the process is forked, and that it can be fixed by importing tkinter inside the function f, and that the problem occurs in Linux but not Solaris.
What exactly is causing this problem, and is it a bug? Is there any workaround other than to import tkinter locally everywhere I need it (which isn't very Pythonic)? Do any other modules have similar issues with multiprocessing?
My suspicion is that the problem has to do with the connection to the X server (usually a socket). If this is created before the process is fork()-ed, the child process inherits this connection. But if it tries to use it, the X server gets confused.
After a cursory look at at Tkinter.py, it looks like maybe calling the NoDefaultRoot function before starting the process might be useful. It all depends on when the connection to the X server is made.
Otherwise importing Tkinter after the fork seems the way to go.
As of September 2013, there are some additional comments on the bug report that give more insight into what the actual problem is.
http://bugs.python.org/issue5527#msg194848
http://bugs.python.org/issue5527#msg195480
Based on the above, I'm guessing something like the following is happening: Tkinter is not thread safe, so (for whatever reason) Tkinter wants to know which thread is the main thread. Tkinter assumes that the main thread when the Tkinter module is loaded will also be the main thread for program execution. When you fork or multiprocess after loading Tkinter, this assumption is broken. (For example, after a fork, the remembered main thread is in the parent, not the child.)
If you can get a thread started, just do root.mainloop() after it and it should work fine.
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.
I want to spawn another process to display an error message asynchronously while the rest of the application continues.
I'm using the multiprocessing module in Python 2.6 to create the process and I'm trying to display the window with TKinter.
This code worked okay on Windows, but running it on Linux the TKinter window does not appear if I call 'showerror("MyApp Error", "Something bad happened.")'. It does appear if I run it in the same process by calling showerrorprocess directly. Given this, it seems TKinter is working properly. I can print to the console and do other things from processes spawned by multiprocessing, so it seems to be working too.
They just don't seem to work together. Do I need to do something special to allow spawned subprocesses to create windows?
from multiprocessing import Process
from Tkinter import Tk, Text, END, BOTH, DISABLED
import sys
import traceback
def showerrorprocess(title,text):
"""Pop up a window with the given title and text. The
text will be selectable (so you can copy it to the
clipboard) but not editable. Returns when the
window is closed."""
root = Tk()
root.title(title)
text_box = Text(root,width=80,height=15)
text_box.pack(fill=BOTH)
text_box.insert(END,text)
text_box.config(state=DISABLED)
def quit():
root.destroy()
root.quit()
root.protocol("WM_DELETE_WINDOW", quit)
root.mainloop()
def showerror(title,text):
"""Pop up a window with the given title and text. The
text will be selectable (so you can copy it to the
clipboard) but not editable. Runs asynchronously in
a new child process."""
process = Process(target=showerrorprocess,args=(title,text))
process.start()
Edit
The issue seems to be that TKinter was imported by the parent process, and "inherited" into the child process, but somehow its state is inextricably linked to the parent process and it cannot work in the child. So long as you make sure not to import TKinter before you spawn the child process, it will work because then it is the child process that is importing it for the first time.
This discussion could be helpful.
Here's some sample problems I found:
While the multiprocessing module follows threading closely, it's definitely not an exact match. One example: since parameters to a
process must be pickleable, I had to go through a lot of code
changes to avoid passing Tkinter objects since these aren't
pickleable. This doesn't occur with the threading module.
process.terminate() doesn't really work after the first attempt. The second or third attempt simply hangs the interpreter, probably
because data structures are corrupted (mentioned in the API, but this
is little consolation).
Maybe calling the shell command xhost + before calling your program from that same shell will work?
I am guessing your problem lies with the X-server.