Reputation: 179
I don't know if what I'm seeking to achieve is even possible. I have written a tkinter app which imports a method from an external class. This method runs a hill-climbing algorithm which will run perpetually and try to improve upon the "score" that it has calculated. After each pass, it presents the current output and score to the user and asks (on the command line) if they wish to continue.
The first challenge in getting this working was to implement threading. I have this working, but I don't don't know if I have done it correctly.
The algorithm will continue until the user signals that they have got the answer they were looking for, or loses the will to live and presses CTRL-C.
In my tkinter main app, this presents me with two problems:
Below is, I hope, a simplified bare-bones example of what I am trying to do.
This is a learning exercise for me and I would be grateful of any help.
import tkinter as tk
from threading import Thread
from random import randint
import time
class MyTestClass: # This would actually be imported from another module
def __init__(self):
self.stopped = False
def my_long_procedure(self):
# Fake method to simulate actual algorithm
count = 0
maxscore = 0
i = 0
while count < 1000 and not self.stopped:
i += 1
score = randint(1,10000)
if score > maxscore:
maxscore = score
self.message = f'This is iteration {i} and the best score is {maxscore}'
print(self.message)
# self.carry_on = input("Do you want to continue? ")
# if self.carry_on.upper() != "Y":
# return maxscore
time.sleep(2)
print('OK - You stopped me...')
class MyMainApp(tk.Tk):
def __init__(self, title="Sample App", *args, **kwargs):
super().__init__()
self.title(title)
self.test_run = MyTestClass()
self.frame1 = tk.LabelFrame(self, text="My Frame")
self.frame1.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky=tk.NSEW)
self.frame1.columnconfigure(0, weight=1)
self.frame1.rowconfigure(0, weight=1)
start_button = tk.Button(self.frame1, text="Start!",
command=self.start_proc).grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
stop_button = tk.Button(self.frame1, text="Stop!",
command=self.stop_proc).grid(row=0, column=2, padx=5, pady=5, sticky=tk.E)
self.output_box = tk.Text(self.frame1, width=60, height=8, wrap=tk.WORD)
self.output_box.grid(row=1, column=0, columnspan=3, sticky=tk.NSEW)
def start_proc(self):
self.test_run.stopped = False
self.control_thread = Thread(target=self.test_run.my_long_procedure, daemon=True)
self.control_thread.start()
time.sleep(1)
self.output_box.delete(0.0, tk.END)
self.output_box.insert(0.0, self.test_run.message)
# self.control_thread.join()
# while not self.test_run.stopped:
# self.output_box.delete(0.0, tk.END)
# self.output_box.insert(0.0, self.test_run.message)
# time.sleep(0.5)
def stop_proc(self):
self.test_run.stopped = True
if __name__ == "__main__":
MyMainApp("My Test App").mainloop()
Upvotes: 0
Views: 77
Reputation: 3333
If you own the implementation of the algorithm you could pass a callback (a method of MyMainApp
) so that the algorithm signals on his own whenever he has done some "work" worth notification to the user. This would look like:
def my_long_procedure(self,progress):
The callback prototype could be:
def progress(self,iteration,result):
and instead of print(self.message)
you could do progress(i,maxscore)
starting the thread:
self.control_thread = Thread(target=self.test_run.my_long_procedure, daemon=True,args=(self.progress,))
Unfortunatly you need to be aware that you cannot refresh the GUI from another thread than the main thread. This is a much discussed tkinter limitation. So in a nutshell you cannot directly call any of your GUI widgets from progress
function. The workaround to this issue is to store the progress in the progress function and register a function to be executed whenever the tkinter main loop will be idle. you can do somethink like self.after_idle(self.update_ui)
from progress
method. update_ui()
would be a new methode updating eg a progress bar or your graph using data transmitted by the progress
callback and saved as MyMainApp
properties.
More on this "pattern" (using message queues instead of callback) here:
https://www.oreilly.com/library/view/python-cookbook/0596001673/ch09s07.html
Upvotes: 1