Running a Tkinter form in a separate thread - python

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")

Related

Tkinter window not displaying until after program is run

global window
window = Tk()
window.geometry('300x200')
window.minsize(300,200)
window.maxsize(300,200)
window.configure(bg='black')
window.title("testupdate")
global outputtext
outputtext = tk.StringVar()
outputtext.set("starting window...")
my_label = tk.Label(window, textvariable = outputtext, bg = 'black', fg = 'white', font = 'terminal')
my_label.pack()
class ChangeLabel():
def __init__(self, text):
outputtext = tk.StringVar()
outputtext.set(text)
my_label.config(textvariable = outputtext)
Here's the main code: https://replit.com/#YourPetFinch/textadventure#main.py
I've been trying to make a text adventure game with Tkinter so I can package it all nicely as an app. I created a function that I can call from another file to update a label as the game output so I can keep the GUI setup simple, but I've been having a problem where the window won't show up until the code has finished running. I'm new to tkinter (and honestly not very good at Python in general) so this is probably a stupid question.
That's not how global is used. The global statement is only used inside a function, to state that you're going to modify a global. You can't actually make a global that crosses files, but your from gui import * will handle that.
The issue here is understanding event-driven programming. When you create a window, nothing gets drawn. All that does is send a number of messages to signal the core. The messages will not get fetched and dispatched until it gets into the .mainloop(). The main loop processes all the messages and does the drawing, which might queue up more messages.
So, you cannot use time.sleep(2) like that. As you saw, that will interrupt the process and prevent any drawing from being done. Instead, you will have to use window.after to request a callback after some number of seconds. The .mainloop monitors the timers, and will give you a call when time runs out. You cannot use time.sleep inside an event handler either, for the same reason; your GUI will freeze until your event handler returns.

Tkinter mainloop in background

I want to run the tkinter mainloop in the background because I´m following the MVC Pattern, so the Controller gets a Interfaceobject to handle all cares. So I tried to thread it. RuntimeError: Calling Tcl from different apartment occures. Saw no further answers on this topic.
Does anyone have a clue what to do?
Thanks, Max
I don´t have the solution you want for this problem. Maybe this question is can show you what happens.
Anyway you can try by defining all tkinter objects inside the function like this:
from tkinter import Tk
import threading
def mainloop():
root = Tk()
#Your tkinter objects goes here
root.mainloop()
t = threading.Thread(target=mainloop)
t.start()
or you can run the mainloop without threading and thread the Controller:
from tkinter import Tk
import threading
root = Tk()
root.mainloop()
def controler():
while True:
pass
t = threading.Thread(target=controler)
t.start()
thanks #Tomás Gomez Pizarro for one solution. I myself came up with a solution. My Controller etc are set up with a reference(Object) to my Gui. the Gui itself, so the mainloop function is not called until everything is ready. So in Runtime every GuiEvent is passed to the Controller via a thread so that the main loop is not interferred with.
Regards, Max

Killing a multiprocess pool using a tkinter button

I'm building an application that does thousands (possibly millions) of calculations based off of what the user inputs. Because these calculations can take a long time, I want to use Python's multiprocessing module. Here is my dilemma; I want to set up a tkinter window with a cancel button to stop the calculations running throughout the processors I set up using Pool. I tried using threads to allow the popup window to run, but some funny things happen when I run the code.
When I press the 'start' button, the Pool starts going as expected. However, my popup window does not show even though it is on its own thread. Even weirder, when I tell my IDE (PyCharm) to stop the program, the popup window shows and the calculations are still running until I either press the 'exit' button from the popup window, or kill the program altogether.
So my questions are: Why won't my popup window show even though it is on its own thread? Am I utilizing the multiprocessing module correctly? Or is the problem something totally different?
import tkinter as tk
from tkinter import ttk
import random
import threading
import time
from multiprocessing import Pool
def f(x):
print(x*x)
time.sleep(random.randrange(1,5)) # simulates long calculations
# Extra math stuff here...
def processor():
global calc
calc = Pool(4)
calc.map(f, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30])
print("Done")
def calculation_start():
p = threading.Thread(target=progress_window) # Thread to open a new window for the user to cancel a calculation
p.start()
processor() # Calls the function to run the calculations
def progress_window():
# This is the window that pops up to let a user cancel a calculation.
popup = tk.Toplevel()
endButton = ttk.Button(popup, text="End", command=lambda :(calc.terminate()))
endButton.pack()
master = tk.Tk()
startButton = ttk.Button(master, text="Start", command=calculation_start)
startButton.pack()
tk.mainloop()
EDIT:
I tried switching the processor function to a thread instead of the progress_window function.
def calculation_start():
p = threading.Thread(target=processor) # Thread for the function to run the calculations
p.start()
progress_window() # Open a new window for the user to cancel a calculation
My popup window appears and the 'end' button looks like it stops the processor when pressed, but it never continues past that point. It's like it's stuck at calc.map(f, [1,2,3,...] in the processor() function.
From the Pool.map documentation:
It blocks until the result is ready.
That means that, yes, the work is being done on other threads, but the main thread (the one calling map) will not execute anything else until the other threads are complete. You want to use map_async and (for a long-running, interactive process), provide a callback for what to do when the pool is finished (probably hide the pop-up).
If you want your program to simply show the pop-up then exit, you want to use the AsyncResult returned by map_async. Set up an idle handler using after_idle (See tkinter universal widget methods and remember you'll need to manually re-add the handler after every call). After each idle call, you could call either AsyncResult.ready() or AsyncResult.get(<timeout>) to see if the pool has completed. Once it's finished, you're safe to process the results and exit.

Destroying a Toplevel in a thread locks up root

I have a program I've been writing that began as a helper function for me to find a certain report on a shared drive based on some information in that report. I decided to give it a GUI so I can distribute it to other employees, and have ran into several errors on my first attempt to implement tkinter and threading.
I'm aware of the old adage "I had one problem, then I used threads, now I have two problems." The thread did, at least, solve the first problem -- so now on to the second....
My watered down code is:
class GetReport(threading.Thread):
def __init__(self,root):
threading.Thread.__init__(self)
# this is just a hack to get the StringVar in the new thread, HELP!
self.date = root.getvar('date')
self.store = root.getvar('store')
self.report = root.getvar('report')
# this is just a hack to get the StringVar in the new thread, HELP!
self.top = Toplevel(root)
ttk.Label(self.top,text="Fooing the Bars into Bazes").pack()
self.top.withdraw()
def run(self):
self.top.deiconify()
# a function call that takes a long time
self.top.destroy() #this crashes the program
def main():
root = Tk()
date,store,report = StringVar(),StringVar(),StringVar()
#####
## labels and Entries go here that define and modify those StringVar
#####
def launchThread(rpt):
report.set(rpt)
# this is just a hack to get the StringVar in the new thread, HELP!
root.setvar('date',date.get())
root.setvar('store',store.get())
root.setvar('report',report.get())
# this is just a hack to get the StringVar in the new thread, HELP!
reportgetter = GetReport(root)
reportgetter.start()
ttk.Button(root,text="Lottery Summary",
command=lambda: launchThread('L')).grid(row=1,column=3)
root.mainloop()
My expected output is for root to open and populate with Labels, Entries, and Buttons (some of which are hidden in this example). Each button will pull data from the Entries and send them to the launchThread function, which will create a new thread to perform the foos and the bars needed to grab the paperwork I need.
That thread will launch a Toplevel basically just informing the user that it's working on it. When it's done, the Toplevel will close and the paperwork I requested will open (I'm using ShellExecute to open a .pdf) while the Thread exits (since it exits its run function)
What's ACTUALLY happening is that the thread will launch its Toplevel, the paperwork will open, then Python will become non-responsive and need to be "end processed".
As far as I know you cannot use Threading to alter any GUI elements. Such as destroying a Toplevel window.
Any Tkinter code needs to be done in the main loop of your program.
Tkinter cannot accept any commands from threads other than the main thread, so launching a TopLevel in a thread will fail by design since it cannot access the Tk in the other thread. To get around this, use the .is_alive method of threads.
def GetReport(threading.Thread):
def __init__(self,text):
self.text = text
super().__init__()
def run(self):
# do some stuff that takes a long time
# to the text you're given as input
def main():
root = Tk()
text = StringVar()
def callbackFunc(text):
top = Toplevel(root)
ttk.Label(top,text="I'm working here!").pack()
thread = GetReport(text)
thread.start()
while thread.is_alive():
root.update() # this keeps the GUI updating
top.destroy() # only when thread dies.
e_text = ttk.Entry(root,textvariable=text).pack()
ttk.Button(root,text="Frobnicate!",
command = lambda: callbackFunc(text.get())).pack()

close a window and continue execution in the other one?

I'm really lost...I open a window with two buttons, and when you click on the button called "REGISTER SOME KEY PRESSES" it runs the script called registerSomeKeyPresses.py, BUUUUT once finished I want to close that execution but keep the first window displaying...it's being impossible for me....
Please, i would reaaaally appreciate any help...
Thanks!
#!/usr/bin/env python
from Tkinter import *
import threading
v0 = Tk()
def finishApplication(): v0.destroy()
def registerSomeKeyPresses():
t = threading.Thread(target=execfile("registerSomeKeyPresses.py"))
t.start()
def waitAndRun(f): v0.after(200, f)
b1=Button(v0,text="TERMINAR APLICACION",command=lambda: finishApplication()).pack()
button_keyPresses=Button(v0,text="REGISTER SOME KEY PRESSES",command=lambda: waitAndRun(registerSomeKeyPresses())).pack()
v0.mainloop()
================ registerSomeKeyPresses.py ===========================
Do several things and last command:
io.quit()
When you destroy the instance of Tk, your programm will (and should) exit. If you want to create and destroy windows, create and destroy an instance of Toplevel while keeping the main window active. If you don't want to see the main window you can hide it.
Also, tkinter and threads don't mix very well. You cannot call any methods on any widgets from another thread. I've heard other people say you can call event_generate from another thread, but I think that's the only tkinter function you can call from another thread.
Edit 1
A second try as a response to your comment:
from Tkinter import *
from subprocess import call
import sys
t = Tk()
def click():
t.iconify()
try:
call([sys.executable, 'script.py'])
finally:
t.deiconify() # if it should close do t.quit() and t.destroy()
b = Button(t, command= click)
b.pack()
t.mainloop()
Old Version
What does that do?
================ registerSomeKeyPresses.py ===========================
v0.quit()
v0.destroy()
io.mainloop()
An other error is:
threading.Thread(target=execfile, args = ("registerSomeKeyPresses.py",))
if you really neeed a thread.
Do never mix tkinter mainloop things with threads. Threads can use event_generate - thats safe.

Categories