I've got a GUI which gives the user the option to run a selection of tests.
These tests run in threads as they use MIDI data in.
I have a checkQueue() function that is run using after() but once the user selects a test, the checkQueue() function is no longer called until the function finishes.
How do I get the checkQueue to continue running during my buttonTest() function so I can use data from the test to update the GUI?
Here is a simplified version of my code:
import Tkinter as tk
import Queue
class Program(tk.Frame):
def __init__(self,parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.initUI()
self.q = Queue.Queue()
self.after(200, checkQueue, self.q, self)
def initUI(self):
start = tk.Button(self.parent, command=self.runTest, text="Run Test 1")
start.pack()
self.instruction = tk.Label(self.parent, text="Press Button 1")
self.instruction.pack()
def runTest(self):
buttonTest(self)
def checkQueue(q,app):
print "Calling checkQueue"
while not q.empty():
#HandleData (update a label/canvas etc.)
app.update()
app.after(200, checkQueue,q,app)
def buttonTest(gui):
#Does lots of functions but is just a while for this example
x=1
while x==1:
if x == 100:
gui.q.put("Some Data")
def main():
root = tk.Tk()
root.configure(background="black")
app = Program(root)
root.mainloop()
root.destroy()
if __name__ == "__main__":
main()
I'm assuming buttonTest is the function that's calling your tests that are running in other threads. Even though the actual work is done in child threads, buttonTest is still running in the main thread, and its while loop is hogging all the main thread's processing cycles. Try starting buttonTest itself in its own thread. This will give the main thread the breathing room necessary to process your checkQueue calls.
def runTest(self):
Thread(target=buttonTest, args=(self,)).start()
Related
I have made a GUI using TkInter.
Now I am trying to perform an action after a key press.
However, I don't want the action to perform twice when I click the button twice.
But rather, the program should wait until the previous action is finished before it starts waiting for the next key press.
I cannot seem to achieve this, even thought the command handler returns "break".
See below a minimal working example, where if you press the <Left> button multiple times, the code also executes multiple times. How should I change the code to achieve the desired performance?
import tkinter as tk
import time
class App(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self)
# Create handler
def event_handler(event):
print('One run for event', event)
time.sleep(1)
print('Done')
return "break"
# Button
self.button = tk.Button(
master=self,
text='Test',
command = event_handler
)
self.button.grid()
self.bind('<Left>', event_handler)
self.focus()
if __name__ == "__main__":
root = tk.Tk()
root.title("TestTool")
# Start application
app = App(root)
app.pack(fill="both", expand=True)
root.mainloop()
When You execute Your event_handler with time.sleep(1), You are effectively blocking any other event execution. But event triggers are accumulating and executed one after another how each event_handler starts and ends. It creates effect of serial execution of event_handler.
What You want is to set some flag which will indicates Your event state. Then You have to process all accumulated events before Your return from event_handler. This will cause all events to execute, but immediately return, because flag is set to True: event is running.
Here is code snippet:
import time
import tkinter as tk
class App(tk.Frame):
event_running = False
def __init__(self, parent):
tk.Frame.__init__(self)
# Create handler
def event_handler(event=None):
# Check if event is running
if self.event_running:
return False
# Lock event until it's finished
self.event_running = True
print('One run for event', event)
time.sleep(1)
print('Done')
# Update all pending events
self.update()
self.update_idletasks()
# Release event handler
self.event_running = False
return
# Button
self.button = tk.Button(
master=self,
text='Test',
command=event_handler
)
self.button.grid()
self.bind('<Left>', event_handler)
self.focus()
if __name__ == "__main__":
root = tk.Tk()
root.title("TestTool")
# Start application
app = App(root)
app.pack(fill="both", expand=True)
root.mainloop()
I am posting this as informational for folks looking for a way to communicate thread progress back to a tkinter Frame or window. I have seen several approaches detailed in SO and other sites, but none really seemed adequate to me. So here is an approach that displays progress as both a message box update and advancing a Scale Widget. It uses the tkinter variable classes StringVar and DoubleVar rather than trying to use callbacks or continuously poll a queue in the main thread.
Comments are, of course, welcome, but it appears this approach works well.
`
import tkinter as tk
from tkinter import ttk, END, NW, GROOVE
import threading
import queue
import time
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.queue = queue.Queue()
self.msgCt=0
self.listbox = tk.Listbox(self, width=20, height=5)
self.scaleVal=tk.DoubleVar()
self.progressbar = ttk.Scale(self, orient='horizontal',
length=300,
from_=0.0, to=100.0,
variable=self.scaleVal)
self.scaleVal.set(0.0)
self.button = tk.Button(self, text="Start", command=self.spawnthread)
self.msgBtn = tk.Button(self,text="Set Msg", command=self.sendMessage)
self.msgTxt=tk.StringVar(self,"Messages Here...")
self.msgBox=tk.Message(self,textvariable=self.msgTxt,width=200,
anchor=NW,relief=GROOVE)
self.listbox.grid(row=0,column=0,columnspan=2)
self.msgBox.grid(row=1,column=0,columnspan=2)
self.progressbar.grid(row=2,column=0,columnspan=2)
self.button.grid(row=3,column=0)
self.msgBtn.grid(row=3,column=1)
def spawnthread(self):
self.button.config(state="disabled")
self.listbox.delete(0, END)
self.thread = ThreadedClient(self.queue,self.msgTxt,self.scaleVal)
self.thread.start()
self.periodiccall()
def sendMessage(self,msg=None):
if not msg==None:
self.msgTxt.set(msg)
else:
self.msgTxt.set("Message {}".format(self.msgCt))
self.msgCt+=1
def periodiccall(self):
self.checkqueue()
if self.thread.is_alive():
self.after(100, self.periodiccall)
else:
self.button.config(state="active")
def checkqueue(self):
while self.queue.qsize():
try:
msg = self.queue.get(0)
self.listbox.insert('end', msg)
# self.progressbar.step(25)
except queue.Empty:
pass
class ThreadedClient(threading.Thread):
def __init__(self, qu, mtxt,dvar):
threading.Thread.__init__(self)
self.queue = qu
self.msgTxt=mtxt
self.scaleVal=dvar
def run(self):
self.scaleVal.set(0.0)
for x in range(1, 10):
time.sleep(2)
msg = "Function %s finished..." % x
self.msgTxt.set(msg)
self.scaleVal.set(x*10)
self.queue.put(msg)
if __name__ == "__main__":
app = App()
app.mainloop()
`
In response to the several comments, here is a new version of the code:
import tkinter as tk
from tkinter import ttk, END, NW, GROOVE
import threading
import queue
import time
from pickle import FALSE
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.msgCt=0
self.thrdCt=0;
self.scaleVal=tk.DoubleVar()
self.doneSignal=tk.BooleanVar()
self.doneSignal.set(False)
self.doneSignal.trace("w",self.on_doneSignal_set)
self.progressbar = ttk.Progressbar(self, orient='horizontal',
length=300,
maximum=100.0,
variable=self.scaleVal)
self.scaleVal.set(0.0)
self.startBtn = tk.Button(self, text="Start", command=self.spawnthread)
self.stopBtn=tk.Button(self,text="Stop", command=self.stopthread)
self.stopBtn.config(state="disabled")
self.msgBtn = tk.Button(self,text="Set Msg", command=self.sendMessage)
self.msgTxt=tk.StringVar(self,"Messages Here...")
self.msgBox=tk.Message(self,textvariable=self.msgTxt,width=200,
anchor=NW,relief=GROOVE)
self.msgBox.grid(row=0,column=0,columnspan=3)
self.progressbar.grid(row=1,column=0,columnspan=3)
self.startBtn.grid(row=2,column=0)
self.stopBtn.grid(row=2,column=1)
self.msgBtn.grid(row=2,column=2)
def on_doneSignal_set(self,*kwargs):
self.sendMessage("Thread is DONE")
self.startBtn.config(state="active")
self.stopBtn.config(state="disabled")
def stopthread(self):
if self.thread.is_alive():
self.thread.stopNow()
def spawnthread(self):
self.thrdCt=0
self.startBtn.config(state="disabled")
self.stopBtn.config(state="active")
self.thread = ThreadedClient(self.msgTxt,self.scaleVal,self.doneSignal)
self.thread.start()
# self.periodiccall()
def sendMessage(self,msg=None):
if not msg==None:
self.msgTxt.set(msg)
else:
self.msgTxt.set("Message {}".format(self.msgCt))
self.msgCt+=1
class ThreadedClient(threading.Thread):
def __init__(self, mtxt,dvar,dsig):
threading.Thread.__init__(self)
self.msgTxt=mtxt
self.scaleVal=dvar
self._stopNow=False
self._doneSignal=dsig
self._lock=threading.Lock()
def run(self):
self._stopNow=False
self.scaleVal.set(0.0)
for x in range(1, 10):
if not self.checkStopNow():
time.sleep(2)
msg = "Function %s finished..." % x
self.msgTxt.set(msg)
self.scaleVal.set(x*10)
else:
break
self._doneSignal.set(True)
def stopNow(self):
with self._lock:
self._stopNow=True
def checkStopNow(self):
rtrn=False
with self._lock:
rtrn=self._stopNow
return rtrn
if __name__ == "__main__":
app = App()
app.mainloop()
First, the motivation for this exercise in the first place: I have a large python application that uses Scipy.optimize to find a solution to a modeling problem. This can often take a long time, so I wanted a way to have it run, but post messages to the user periodically to let them know things are happening, allow user to abort in the middle, and finally to post a message to the main thread that the modeling is now done. My original code was partly based on a threading example that assumed the producer/consumer model of threading, whereby a producer creates data (put into a Queue) and the consumer consumes it. This is the WRONG model for my problem, so this new code has no Queue. It just simulates a long modeling process using the run() method, in which there are calls to sleep() which could obviously be replaced by steps in the SciPy.minimize function calls.
This new example uses DoubleVar to allow the thread to update a progressBar (thanks to stovfl for that suggestion), a StringVar to update a message box in the main thread from the modeling thread, and finally a BooleanVar to signal the main thread that things are done. This version has no polling in the main thread. To me, that does not seem a very elegant solution!
How do I know the changes to the DoubleVar, StringVar and BooleanVar get through to the main thread? Only that this program works!! Note that the message box can be updated from either the modeling thread or using a button in the main GUI thread.
Again, comments welcome -- Give me reasons this should NOT work, then tell me why it does given those reasons! Does this violate some basic design of Python, or would there be a situation where this would not work for some reason??
im trying to learn python. But i have problems with the threading. First i failed in the "Proces" class because i putted the loop on the wrong place and my program newer returned from the other class.
But now i think all is correct and it still does not work. I need to have a GUI where i want to be able to write my conditions via text entries and i need another class "Proces" that will do stuff, checking status ower internet and so on constantly or in a specified interval...
The Problem is that my tkinter GUI is freezing after pressing something
here is my GUI.py file:
import tkinter as tk
from Proces import Proces
root = tk.Tk()
frame = tk.Frame(root)
frame.pack()
button = tk.Button(frame, text="QUIT", fg="red",command=quit).pack(side=tk.LEFT)
pr = Proces()
print("\nGUI: proces init...")
pr.start()
print("\nGUI: Start ended")
root.mainloop()
here is the Proces.py file:
import time, threading
class Proces(threading.Thread):
def loop(self):
while True:
time.sleep(2)
print("\nProces: looping")
def __init__(self):
threading.Thread.__init__(self)
print("\nProces: Starting proces")
time.sleep(2)
def run(self):
self.deamon = True
print("\nProces: Starting loop")
self.loop()
*This is the output: *
Proces: Starting proces
GUI: proces init...
Proces: Starting loop
GUI: Start ended
Proces: looping
Proces: looping
Proces: looping
Proces: looping
*But the GUI of the tkinter does not react.*
How should i do this kind of task?
Thank you for your help, advice and answer
I think you don't have problem about starting thread etc. However, you should be able to control your thread, meaning your thread should return based on some condition. It seems you have a button to quit. I assume you want to finish the process by clicking the button.
To do that, when you click to button, your main thread should pass a variable to Process in order to break your while loop, which is your thread basically.
Here is a code that you can work on.
import tkinter as tk
import time
import threading
class MainWindow:
def __init__(self, master):
self.master = master
self.quit_button = tk.Button(self.master, text="QUIT", command=lambda:self.quit(), width=20)
self.quit_button.pack(side=tk.LEFT)
self.process = None
print("\nGUI: process init...")
self.start_task()
def quit(self):
self.process.stop_process = True
print("\nGUI: Start ended")
def start_task(self):
self.process = Process()
self.process.start()
class Process(threading.Thread):
def loop(self):
while True:
if not self.stop_process:
time.sleep(2)
print("\nProcess: looping")
else:
print("\nProcess: looping ended")
return
def __init__(self):
threading.Thread.__init__(self)
self.stop_process = False
print("\nProcess: Starting proces")
time.sleep(2)
def run(self):
self.deamon = True
print("\nProcess: Starting loop")
self.loop()
if __name__ == '__main__':
root = tk.Tk()
app = MainWindow(master=root)
root.mainloop()
So you start yout tkinter, which is your main thread. Then iniating another class within your main thread, which is inherits the thread. So you have two seperate thread that working. When you clicked to "quit" button, you pass variable stop_process, which breaks the loop and return, meaning ending your thread. Your main thread is still alive for running your tkinter window.
I hope it will help
I have a Python script that has some processes that take some time such as creating a .shp file and opening an .mxd. I've read Python Progress Bar but I would like to avoid using a external package. So far I have;
from tkinter import *
from tkinter.ttk import *
import os, arcpy
tk=Tk()
progress=Progressbar(tk,orient=HORIZONTAL,length=100,mode='determinate')
def bar():
import time
os.startfile("Assignment.mxd")
progress['value']=20
tk.update_idletasks()
time.sleep(8)
progress['value']=50
tk.update_idletasks()
time.sleep(8)
progress['value']=80
tk.update_idletasks()
time.sleep(8)
progress['value']=100
progress.pack()
Button(tk,text='Open',command=bar).pack()
mainloop()
It seems to be working but I don't think the process to open the .mxd is paying attention to what stage the progress bar is at. I would like to get it so that the process finishing coincides with the progress bar reaching 100%. Is there away to get the process to 'wait' for the progress bar? Could anyone offer some pointers on how I might get this to work?
Thanks
The problem is that the sleep function and possibly any other blocking function you call in your bar function blocks the GUI. A solution could be pushing the actions to a worker thread. A very quick solution I can come up with is this:
from random import randint
from threading import Thread
from tkinter import Tk, DoubleVar, HORIZONTAL
from tkinter.ttk import Frame, Button, Progressbar
from time import sleep
class Worker(Thread):
def __init__(self, reference):
super().__init__()
self.reference = reference
def run(self):
print("running...")
# replace this dummy loop with actual processing tasks
while self.reference.get_progress() < 100:
duration = randint(1, 3)
sleep(duration)
self.reference.add_progress(10 * duration)
print("finished.")
class Example(Frame):
def __init__(self, root):
super().__init__(master=root)
self.progress = DoubleVar(value=0.0)
self.worker = Worker(reference=self)
self.startbutton = Button(master=self, text="Start", command=self.start)
self.startbutton.pack()
self.progressbar = Progressbar(master=self, orient=HORIZONTAL, length=100, mode='determinate', variable=self.progress)
self.progressbar.pack()
self.infobutton = Button(master=self, text="Info", command=self.info)
self.infobutton.pack()
self.pack()
def get_progress(self):
return self.progress.get()
def set_progress(self, value):
self.progress.set(value)
def add_progress(self, value):
self.progress.set(self.progress.get() + value)
def start(self):
# print("sleeping...")
# sleep(10) # uncomment this to block the GUI
print("starting...")
self.worker.start()
def info(self):
print(self.worker.is_alive())
def main():
root = Tk()
app = Example(root)
app.mainloop()
if __name__ == '__main__':
main()
Note that the info button works before, while and after the thread runs. One problem with this quick solution is that the thread can only be run once. To run it multiple times, some kind of resetting needs to be implemented.
Can somebody help me please, I'm making an exercise about class and running task on other thread then tkinter. I want to change the label in another class. Can't get my script to work.
I tried different things but I'm having some troubles with understanding the inheriting from classes and the threads, so this is just an example to learn more about it.
from tkinter import *
import tkinter as tk
from tkinter import ttk
import threading
#Gloabl for stopping the run task
running = True
#class 1 with window
class App():
def __init__(self):
#making the window
self.root = tk.Tk()
self.root.geometry("400x400+300+300")
self.root.protocol("WM_DELETE_WINDOW", self.callback)
self.widgets()
self.root.mainloop()
# stop task and close window
def callback(self):
global running
running = False
self.root.destroy()
# all the widgets of the window
def widgets(self):
global labelvar
#startbutton
self.start_button = tk.Button(self.root, text="Start", command=lambda:App2())
self.start_button.pack()
#stopbutton
self.stop_button = tk.Button(self.root, text="Stop", command=lambda:self.stop())
self.stop_button.pack()
#Defining variable for text for label
labelvar = "Press start to start running"
self.label = tk.Label(self.root, text=labelvar)
self.label.pack()
#stop the task
def stop(self):
global running
running = False
#class 2 with task in other thread
class App2(threading.Thread):
def __init__(self):
global running
#check if task can be run
running = True
threading.Thread.__init__(self)
self.start()
def run(self):
#starting random work
for i in range(10000):
print(i)
labelvar = "running"
App.label.pack()
#checking if task can still be running else stop task
if running == False:
break
labelvar = "stopped"
App.label.pack()
#initiate main app
app = App()
As I said in a comment, tkinter doesn't support multithreading itself, but you can do it as long as only one thread, usually the main one, uses (or "talks") it.
If you want to affect what the GUI displays, the other thread(s) must communicate somehow with the GUI thread. This is often done through a queue.Queue, but in this relatively simple case it can be done through a global variable provided that concurrent access to it is controlled by some means—sharing memory space (i.e. global variables) is one of the advantages of multithreading vs multitasking, but it has to be done and done correctly.
An easy way to share a resource like this is by using a threading.Lock dedicated for that purpose. (See the Wikipedia article Lock (computer science) for more details.)
All references to this shared resource (the running flag) should only be done after "acquiring" the Lock and "releasing" it afterwards. Fortunately it's trivial to do this using a Python with statement (as shown below).
Another crucial aspect of the multithreading problem is how any information exchanged between the two threads is processed. In this case I choose to make the tkinter thread poll the running flag, watch for changes, and update any affected widgets accordingly. This can be done by using the universal widget method after() which tells tkinter to schedule a future call (inside the 'mainloop') to a user-supplied function or method and to pass it certain arguments. To get this to happen repeatedly, the called function can reschedule itself to run again by calling after() before it finishes.
Below is a modified version of your code that does these thing. Note that App2 never calls tkinter or touches any of its widgets, which is why it works.
import threading
from time import sleep
from tkinter import *
import tkinter as tk
from tkinter import ttk
DELAY = 100 # millisecs between status label updates
# global flag and a Lock to control concurrent access to it
run_flag_lock = threading.Lock()
running = False
# class 1 with window
class App():
def __init__(self):
global running
self.root = tk.Tk()
self.root.geometry("400x400+300+300")
self.root.protocol("WM_DELETE_WINDOW", self.quit)
self.create_widgets()
with run_flag_lock:
running = False
self.root.after(DELAY, self.update_status, None) # start status widget updating
self.root.mainloop()
# create all window widgets
def create_widgets(self):
self.start_button = tk.Button(self.root, text="Start", command=self.start)
self.start_button.pack()
self.stop_button = tk.Button(self.root, text="Stop", command=self.stop)
self.stop_button.pack()
self.status_label = tk.Label(self.root, text='')
self.status_label.pack()
def update_status(self, run_state):
""" Update status label text and state of buttons to match running flag. """
# no need to declare run_flag_lock global since it's not being assigned a value
with run_flag_lock:
if running != run_state: # status change?
if running:
status_text = 'Press Stop button to stop task'
run_state = True
else:
status_text = 'Press Start button to start task'
run_state = False
self.status_label.config(text=status_text)
# also update status of buttons
if run_state:
self.start_button.config(state=DISABLED)
self.stop_button.config(state=ACTIVE)
else:
self.start_button.config(state=ACTIVE)
self.stop_button.config(state=DISABLED)
# run again after a delay to repeat status check
self.root.after(DELAY, self.update_status, run_state)
# start the task
def start(self):
global running
with run_flag_lock:
if not running:
app2 = App2() # create task thread
app2.start()
running = True
# stop the task
def stop(self):
global running
with run_flag_lock:
if running:
running = False
# teminate GUI and stop task if it's running
def quit(self):
global running
with run_flag_lock:
if running:
running = False
self.root.destroy()
# class 2 with task in another thread
class App2(threading.Thread):
def __init__(self):
super(App2, self).__init__() # base class initialization
self.daemon = True # allow main thread to terminate even if this one is running
def run(self):
global running
# random work
for i in range(10000):
print(i)
# Normally you shouldn't use sleep() in a tkinter app, but since this is in
# a separate thread, it's OK to do so.
sleep(.25) # slow printing down a little
# stop running if running flag is set to false
with run_flag_lock:
if not running:
break # stop early
with run_flag_lock:
running = False # task finished
# create (and start) main GUI app
app = App()