Reputation: 261
I have a Tkinter GUI with multiple buttons each which call different functions using threads. I have to automate the clicking of the buttons in sequence. So I am using a single START button, which will click first button, wait for the respective function's execution to complete and then click the next button, and so on.
I used threads as I needed to keep a Progressbar
running while any function is running.
I am also changing the color of the button text from Red (not run yet) to Blue (being run) to Green (finished execution). I know I need to use join()
somewhere, but it's not working.
This current code runs all the button invoke()
methods at once, and not in a sequence.
import tkinter as tk
from tkinter import ttk
from threading import Thread
def sample_function():
for i in range(1,10000) :
print(i)
def run_function(name, func,btn_variable):
# Disable all buttons
btn_variable.configure(style = 'blue.TButton')
processing_bar.start(interval=10)
print(name, 'started')
func()
processing_bar.stop()
print(name, 'stopped')
btn_variable.configure(style = 'green.TButton')
def run_thread(name, func,btn_variable):
Thread(target=run_function, args=(name, func,btn_variable)).start()
def prepare_clicked():
run_thread('prepare', sample_function,prepare_btn)
prepare_btn.configure(style = 'green.TButton')
def social_clicked():
run_thread('social', sample_function,social_btn)
social_btn.configure(style = 'green.TButton')
def anomaly_clicked():
run_thread('anomaly', sample_function,anomaly_btn)
anomaly_btn.configure(style = 'green.TButton')
def scoring_clicked():
run_thread('scoring', sample_function,scoring_btn)
scoring_btn.configure(style = 'green.TButton')
def dashboard_clicked():
run_thread('dashboard', sample_function,dashboard_btn)
dashboard_btn.configure(style = 'green.TButton')
def start_all():
prepare_btn.invoke()
anomaly_btn.invoke()
social_btn.invoke()
scoring_btn.invoke()
dashboard_btn.invoke()
window = tk.Tk()
#window = tk.Toplevel()
topFrame = tk.Frame(window)
bottomFrame = tk.Frame(window)
# Tell the Frame to fill the whole window
topFrame.pack(fill=tk.BOTH, expand=1)
bottomFrame.pack(fill=tk.BOTH, expand=1)
# Make the Frame grid contents expand & contract with the window
topFrame.columnconfigure(0, weight=1)
for i in range(5):
topFrame.rowconfigure(i, weight=1)
bottomFrame.rowconfigure(0, weight=1)
for i in range(3):
bottomFrame.columnconfigure(i, weight=1)
ttk.Style().configure('blue.TButton', foreground='blue')
ttk.Style().configure('green.TButton', foreground='green')
ttk.Style().configure('red.TButton', foreground='red')
prepare_btn = ttk.Button(topFrame, command=prepare_clicked, text='Button 1',style = 'red.TButton')
anomaly_btn = ttk.Button(topFrame,command=anomaly_clicked, text='Button 2',style = 'red.TButton')
social_btn = ttk.Button(topFrame, command=social_clicked, text='Button 3',style = 'red.TButton')
scoring_btn = ttk.Button(topFrame, command=scoring_clicked, text='Button 4',style = 'red.TButton')
dashboard_btn = ttk.Button(topFrame, command=dashboard_clicked, text='Button 5',style = 'red.TButton')
commentary = ttk.Button(bottomFrame,text='START',width=10,command = start_all)
commentarylabel = ttk.Label(bottomFrame,text=' Commentary ',width=25)
processing_bar = ttk.Progressbar(bottomFrame, orient='horizontal', mode='indeterminate')
buttons = [prepare_btn, anomaly_btn, social_btn,scoring_btn,dashboard_btn]
prepare_btn.grid(row=0, column=0, columnspan=1, sticky='EWNS')
anomaly_btn.grid(row=1, column=0, columnspan=1, sticky='EWNS')
social_btn.grid(row=2, column=0, columnspan=1, sticky='EWNS')
scoring_btn.grid(row=3, column=0, columnspan=1, sticky='EWNS')
dashboard_btn.grid(row=4, column=0, columnspan=1, sticky='EWNS')
commentary.grid(row=0, column=0, columnspan=1, sticky='EWNS')
commentarylabel.grid(row=0,column = 1, columnspan=2, sticky='EWNS')
processing_bar.grid(row=0, column=3,columnspan=1, sticky='EWNS')
window.mainloop()
Upvotes: 1
Views: 1039
Reputation: 123473
Here's something to demonstrates how to do what you want. It works because the code running in threads other than the main one don't make it any tkinter
calls. To get the threads to run sequentially one-after-the-other, it uses a FIFO Queue
of entries representing each one and starts new ones running when the last one has finished.
To "schedule" a sequence of steps to be run in a certain order as is done in the start_all()
function, all that needs to be done is to put()
the information about each one in the order they should be executed in this job_queue
.
This is all accomplished by repeatedly using the universal after()
method to periodically run a "polling" function (named poll
) which, among other things, checks to see if another thread is currently executing or not and reacts accordingly.
In the code, the processing each thread does is referred to as either a "step" or a "job".
from queue import Queue, Empty
import random
import tkinter as tk
from tkinter import ttk
import tkinter.messagebox as tkMessageBox
from threading import Thread
from time import sleep
random.seed(42) # Generate repeatable sequence for testing.
ITERATIONS = 100
POLLING_RATE = 100 # millisecs.
# Global variables
cur_thread = None # Current running thread.
cur_button = None
cur_name = None
job_queue = Queue() # FIFO queue.
def sample_function():
for i in range(1, ITERATIONS):
print(i)
sleep(0.01) # Simulate slow I/O.
def start_thread(name, func, btn_variable):
global cur_thread, cur_button
if cur_thread is not None:
tkMessageBox.showerror('Error', "You can't start a step when there"
" are some already running.")
return
cur_thread = Thread(target=func)
cur_button = btn_variable
btn_variable.configure(style='blue.TButton')
cur_thread.start()
def poll(window, processing_bar):
global cur_thread, cur_button
if cur_thread is not None:
if cur_thread.is_alive():
processing_bar.step()
else:
cur_thread.join() # Should be immediate.
cur_thread = None
processing_bar.stop()
cur_button.configure(style='green.TButton')
window.update()
elif not job_queue.empty(): # More to do?
try:
job_info = job_queue.get_nowait() # Non-blocking.
start_thread(*job_info)
processing_bar.start()
window.update()
except Empty: # Just in case (shouldn't happen).
pass
window.after(POLLING_RATE, poll, window, processing_bar)
# Button commands.
def prepare_clicked():
start_thread('prepare', sample_function, prepare_btn)
def social_clicked():
start_thread('social', sample_function, social_btn)
def anomaly_clicked():
start_thread('anomaly', sample_function, anomaly_btn)
def scoring_clicked():
start_thread('scoring', sample_function, scoring_btn)
def dashboard_clicked():
start_thread('dashboard', sample_function, dashboard_btn)
def start_all():
global job_queue
# Put info for each step in the job queue to be run.
for job_info in (('prepare', sample_function, prepare_btn),
('social', sample_function, social_btn),
('anomaly', sample_function, anomaly_btn),
('scoring', sample_function, scoring_btn),
('dashboard', sample_function, dashboard_btn)):
job_queue.put(job_info)
# Start the polling.
window.after(POLLING_RATE, poll, window, processing_bar)
####
window = tk.Tk()
#window = tk.Toplevel()
topFrame = tk.Frame(window)
bottomFrame = tk.Frame(window)
# Tell the Frame to fill the whole window
topFrame.pack(fill=tk.BOTH, expand=1)
bottomFrame.pack(fill=tk.BOTH, expand=1)
# Make the Frame grid contents expand & contract with the window
topFrame.columnconfigure(0, weight=1)
for i in range(5):
topFrame.rowconfigure(i, weight=1)
bottomFrame.rowconfigure(0, weight=1)
for i in range(3):
bottomFrame.columnconfigure(i, weight=1)
ttk.Style().configure('blue.TButton', foreground='blue')
ttk.Style().configure('green.TButton', foreground='green')
ttk.Style().configure('red.TButton', foreground='red')
prepare_btn = ttk.Button(topFrame, command=prepare_clicked, text='Button 1', style='red.TButton')
anomaly_btn = ttk.Button(topFrame, command=anomaly_clicked, text='Button 2', style='red.TButton')
social_btn = ttk.Button(topFrame, command=social_clicked, text='Button 3', style='red.TButton')
scoring_btn = ttk.Button(topFrame, command=scoring_clicked, text='Button 4', style='red.TButton')
dashboard_btn = ttk.Button(topFrame, command=dashboard_clicked, text='Button 5', style='red.TButton')
commentary = ttk.Button(bottomFrame, text='START', width=10, command=start_all)
commentarylabel = ttk.Label(bottomFrame, text=' Commentary ', width=25)
processing_bar = ttk.Progressbar(bottomFrame, orient='horizontal', mode='indeterminate')
prepare_btn.grid (row=0, column=0, columnspan=1, sticky='EWNS')
anomaly_btn.grid (row=1, column=0, columnspan=1, sticky='EWNS')
social_btn.grid (row=2, column=0, columnspan=1, sticky='EWNS')
scoring_btn.grid (row=3, column=0, columnspan=1, sticky='EWNS')
dashboard_btn.grid (row=4, column=0, columnspan=1, sticky='EWNS')
commentary.grid (row=0, column=0, columnspan=1, sticky='EWNS')
commentarylabel.grid(row=0, column=1, columnspan=2, sticky='EWNS')
processing_bar.grid (row=0, column=3,columnspan=1, sticky='EWNS')
window.after(POLLING_RATE, poll, window, processing_bar)
window.mainloop()
Upvotes: 2