How can I close a Tkinter window without killing my process? - python

I have a visualization tool written in Tkinter. Typically, I execute it standalone and then manually close the window when I'm finished. However, I'd like to be call it from another Python program, execute the main loop once (and save the canvas as an SVG), and then close the window, allowing the program to continue.
If I could not even bother opening a window, but just re-use my code to draw an SVG, that would work too.
My tk application looks like this:
class MainApplication(tk.Frame):
def __init__(self, output_path, parent=None):
tk.Frame.__init__(self, parent)
self.parent = parent
# draw visualization, save as svg
#...
#
From another part of my code, I call the
kill = True
root = tk.Tk()
root.title("Placement Visualizer")
MainApplication(output_path, root ).pack(side="top", fill="both", expand=True)
if kill:
root.destroy()
root.mainloop()
I get this error: Tcl_AsyncDelete: async handler deleted by the wrong thread Aborted (core dumped)
I've tried using root.quit() or removing root.mainloop(), but I don't get the desired result.
Thank you

In "the other part of my code":
if kill:
root.quit()
else:
root.mainloop()
To not open a window at all, but to draw everything and save the SVG, just call root.quit(). To open the window normally (and have to close it, killing the process), call root.mainloop().

Related

How to Stop Tkinter Program When Using a Class

I'm using python to create a class object that controls a Tkinter window. My code looks like this:
class MyGui:
def __init__(self):
self.root = tk.Tk()
self.root.title("My Gui")
return
def makeButton(self, text, on_click_function, should_pack = True):
""" Makes a button widget """
button = tk.Button(self.root, text=text, command=on_click_function)
if should_pack:
button.pack()
return button
def start(self):
self.root.mainloop()
def stop(self):
self.root.destroy()
I want to be able to start an instance of the class, then stop it like so:
a = MyGui()
a.makeButton('STOP', a.stop)
a.start()
... // OTHER CODE HERE EVENTUALLY // ...
Everything works fine and the window is successfully created on start() and it vanishes when the button is clicked and stop() executes. The issue is that the mainloop continues to run. I have to manually kill the program using Ctrl+C and it shows it being killed at self.tk.mainloop(n).
If I just do all my tkinter code in a normal file rather than a class, everything works the same but the mainloop also stops when I call destroy on root, and the program ends. If I put it in a class, however, mainloop doesn't stop in spite of the window being destroyed.
Why? How can I get it to stop mainloop too? (I can stop the whole program by calling sys.exit, but I want to keep executing non-Gui stuff after I call stop via the button-press).
I figured out I can solve the problem if I put self.root.quit() and self.root.destroy() together in the stop() function, but not if I use them separately (using just destroy closes the window but leaves the mainloop running; using just quit cancels the mainloop but leaves the window open and in a weird state that causes crashing when you try to close it manually).
So the class function actually needs to look like this:
def stop(self):
self.root.quit()
self.root.destroy()

tkinter: event binding remains after application terminates?

I'm running tkinter in Python 3.4. A button event seems to remain bound to a command even after the application terminates. Code snippet:
# application class
class DataSel:
def __init__(self,parent):
self.parent = parent
<...>
self.button_sel = tk.Button(self.parent,text='Select')
self.button_sel.grid(row=1,sticky='nesw')
self.button_sel.bind('<Button-1>',self.sel_click)
self.button_quit = tk.Button(self.parent,text='Quit')
self.button_quit.grid(row=2,sticky='nesw')
self.button_quit.bind('<Button-1>',self.quit_click)
def sel_click(self,event):
self.filename = askopenfilename(parent=self.parent)
<...>
def quit_click(self,event):
self.parent.destroy()
# main part of application
root = tk.Tk()
root.lift()
sel = DataSel(root)
root.lift()
root.mainloop()
When I restart the interpreter from scratch and run this application, there is no error message. However, the button_sel button remains pressed (in low relief) after the sel_click method is finished. Then, if I quit the application and rerun it, I get the following message in the shell:
invalid command name ".94227256"
while executing
"$w cget -state"
(procedure "tk::ButtonDown" line 12)
invoked from within
"tk::ButtonDown .94227256"
(command bound to event)
where the number .94227256 changes each time I rerun.
Apart from this message, and the fact that the button remains in low relief, all other functionality is OK. But it seems like the button event somehow stays bound to a stale command!
What is happening is that your binding happens before the button widget is able to process the same event. You are doing this during the processing of the events and you aren't telling Tkinter to stop processing the events further. Therefore, when Tkinter gets around to having the widget process the click event, the window no longer exists and tkinter throws an error.
The root of the problem is that you are putting bindings on a button. You shouldn't do that. If you want to call a function from a button you need to use the command attribute of the button.
If you really think you need to do this via a binding (rather than via the command attribute), you need your function to return '"break"` to tell tkinter to stop any further processing of the event.

Multiple tkinter windows look different when closing and reopening window

I got the following code from a tutorial. I then modified main() so that two windows are created as seperate threads. When I run it, only one window is created. Then when I press the Quit button in that window, a second window appears. In this new window the button has a different look than the first one (a look which I like better) and then if I press either of the two Quit buttons, both windows close and the program exits.
Why does the second window not appear until the first Quit button is pressed, and why does it look different when it does appear?
EDIT: This happens when no threads are used as well, where only one window is created at a time.
EDIT: This is a screenshot of the two windows that are created. The one on the left is created with the program is run, the one on the right is created after clicking the "Quit" button on the first.
from Tkinter import Tk, BOTH
from ttk import Frame, Button, Style
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Quit button")
self.style = Style()
self.style.theme_use("default")
self.pack(fill=BOTH, expand=1)
quitButton = Button(self, text="Quit",
command=self.quit)
quitButton.place(x=50, y=50)
from threading import Thread
def main():
for i in range(2):
root = Tk()
root.geometry("250x150+300+300")
app = Example(root)
Thread(target=root.mainloop()).start()
if __name__ == '__main__':
main()
You cannot use tkinter this way. Tkinter isn't thread safe, you can only ever access tk widgets and commands except from the thread that created the root window.
As for one window only showing after the other is destroyed even without threading, it's hard to say since you don't show the code. If you're creating more than one instance of Tk, and calling mainloop more than once, that's the problem. Tkinter is designed to work when you create precisely one instance of Tk, and call mainloop precisely once.
If you want more than one window, create a single instance of Tk for the first window, and instances of Toplevel for additional windows.

tk destroy() and grid_forget() don't always work

i'm hoping anyone can help me out here. i'm having an issue with a tkinter gui i built. the issue only happens in windows. My GUI creates a results frame with some labels in it, when it's time to calculate something else, the user clicks on the "newPort" button and that button is supposed to remove the results frame and set to False some instance attributes internal to the calculation. The issue i'm having, which is apparent only in windows is that sometimes the results frame, and its descendant labels don't disappear every time. Sometimes they do, sometimes they don't. The instance variable is correctly set to False but the widgets are still visible on the main GUI. The GUI also contains a couple checkboxes and radiobuttons but they don't impact the creation of the results frame nor its expected destruction. I have not been able to pin point a pattern of actions the user takes before clicking on the newPort button which causes the frame and labels to not get destroyed. This happens when i freeze my app with py2exe, as well as running the app from the python interpreter within the eclipse IDE. I have not tried running the app from the python interpreter directly (i.e. without the IDE) and this problem does not happen on my Mac when i run the app using the eclipse python interpreter. Thanks very much all! My code looks like this:
import Tkinter as TK
class widget(object):
def __init__(self,parent=None):
self.parent = TK.Frame(parent)
self.parent.grid()
self.frame = TK.Frame(self.parent)
self.frame.grid()
newLedger = TK.Button(self.parent,command=self.newPort).grid()
self.calcButton = TK.Button(self.frame,command=self.showResults)
self.calcButton.grid()
self.calcVariable = True
def newPort(self):
self.calcVariable = False
try:
self.second.grid_forget()
self.first.grid_forget()
self.resultsFrame.grid_forget()
self.second.destroy()
self.first.destroy()
self.resultsFrame.destroy()
except:
raise
self.frame.update_idletasks()
def showResults(self):
self.resultsFrame = TK.Frame(self.frame)
self.resultsFrame.grid()
self.first = TK.Label(self.resultsFrame,text='first')
self.first.grid()
self.second = TK.Label(self.resultsFrame,text='second')
self.second.grid()
if __name__ == '__main__':
root = TK.Tk()
obj = widget(root)
root.mainloop()
You don't need to destroy or call grid_forget on the labels, and you don't need to call grid_forget on the resultsFrame; when you destroy the resultsFrame it will cause all off its children to be destroyed, and when these widgets are destroyed they will no longer be managed by grid.
The only way I can get widgets to not be destroyed is if I click on the "calc" button twice in a row without clicking on the "new" button in-between. I'm doing this by running your program from the command line.

Running a Tkinter form in a separate thread

I have written a short module that can be passed an image and simply creates a Tkinter window and displays it. The problem that I am having is that even when I instantiate and call the method that displays the image in a separate thread, the main program will not continue until the Tkinter window is closed.
Here is my module:
import Image, ImageTk
import Tkinter
class Viewer(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
self.grid()
def show(self,img):
self.to_display = ImageTk.PhotoImage(img)
self.label_image = Tkinter.Label(self,image=self.to_display)
self.label_image.grid(column = 0, row = 0, sticky = "NSEW")
self.mainloop()
It seems to work fine, except when I call it from my test program like the one below, it will not seem to allow my test program to continue, even when started in a different thread.
import Image
from viewer import Viewer
import threading
def showimage(im):
view = Viewer(None)
view.show(im)
if __name__ == "__main__":
im = Image.open("gaben.jpg")
t = threading.Thread(showimage(im))
t.start()
print "Program keeps going..."
I think that perhaps my problem is that I should be creating a new thread within the module itself, but I was wanting to just try and keep it simple, as I am new to Python.
Anyway, thanks in advance for any assistance.
edit: To clarity, I am just trying to make a module that will display an image in a Tkinter window, so that I can use this module any time I want to display an image. The problem that I am having is that any time a program uses this module, it cannot resume until the Tkinter window is closed.
Tkinter isn't thread safe, and the general consensus is that Tkinter doesn't work in a non-main thread. If you rewrite your code so that Tkinter runs in the main thread, you can have your workers run in other threads.
The main caveat is that the workers cannot interact with the Tkinter widgets. They will have to write data to a queue, and your main GUI thread will have to poll that queue.
If all you're doing is showing images, you probably don't need threading at all. Threading is only useful when you have a long running process that would otherwise block the GUI. Tkinter can easily handle hundreds of images and windows without breaking a sweat.
From your comments it sound's like you do not need a GUI at all. Just write the image to disk and call an external viewer.
On most systems it should be possible to launch the default viewer using something like this:
import subprocess
subprocess.Popen("yourimage.png")
From what I can tell, Tkinter doesn't like playing in other threads. See this post...I Need a little help with Python, Tkinter and threading
The work around is to create a (possibly hidden) toplevel in your main thread, spawn a separate thread to open images, etc - and use a shared queue to send messages back to the Tk thread.
Are you required to use Tkinter for your project? I like Tkinter. It's "quick and dirty." - but there are (many) cases where other GUI kits are the way to go.
I have tried to run tkinter from a separate thread, not a good idea, it freezes.
There is one solution that worked. Run the gui in the main thread, and send events to the main gui. This is similar example, it just shows a label.
import Tkinter as t
global root;
root = t.Tk()
root.title("Control center")
root.mainloop()
def new_window(*args):
global root
print "new window"
window = t.Toplevel(root)
label = t.Label(window, text="my new window")
label.pack(side="top", fill="both", padx=10, pady=10)
window.mainloop()
root.bind("<<newwin>>",new_window)
#this can be run in another thread
root.event_generate("<<newwin>>",when="tail")

Categories