从 tkinter 中控制另一个应用程序



我不知道我想要实现的目标是否可能。我编写了一个从外部类导入方法的 tkinter 应用程序。此方法运行爬山算法,该算法将永久运行并尝试改进它计算的"分数"。每次传递后,它会向用户显示当前输出和分数,并询问(在命令行上(他们是否希望继续。

实现此工作的第一个挑战是实现线程。我有这个工作,但我不知道我是否正确完成了它。

该算法将继续,直到用户发出信号,表示他们得到了他们正在寻找的答案,或者失去了生存的意愿并按 Ctrl-C。

在我的tkinter主应用程序中,这给我带来了两个问题:

  1. 如何显示此外部方法的输出。我尝试编写一个定期轮询输出字段的 while 循环(请参阅"start_proc"方法的注释部分(,但这显然永远不会起作用,此外,理想情况下,我希望看到实时输出;和
  2. 如何与算法交互以继续或停止(请参阅"my_long_procedure"方法的注释部分(。如您所见,我可以注入一个"停止"属性,这确实会停止算法,但我无法将视线从输出上移开,因为在我按下停止之前,所需的答案可能已经过去了。

我希望下面是一个简化的基本示例,说明我正在尝试做的事情。

这对我来说是一个学习练习,我将不胜感激任何帮助。

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

如果你拥有算法的实现,你可以传递一个回调(MyMainApp的方法(,这样每当他完成一些值得通知用户的"工作"时,算法就会自己发出信号。这看起来像:

def my_long_procedure(self,progress):

回调原型可以是:

def progress(self,iteration,result):

而不是print(self.message)你可以做progress(i,maxscore)

启动线程:

self.control_thread = Thread(target=self.test_run.my_long_procedure, daemon=True,args=(self.progress,))

不幸的是,您需要注意,您无法从主线程以外的其他线程刷新 GUI。这是一个讨论很多的tkinter限制。因此,简而言之,您不能直接从函数progress调用任何 GUI 小部件。此问题的解决方法是将进度存储在进度函数中,并注册要在 tkinter 主循环空闲时执行的函数。你可以从progress方法中做一些像self.after_idle(self.update_ui)的想法。update_ui()将是一种新的方法,使用progress回调传输并保存为MyMainApp属性的数据更新进度条或图形。 有关此"模式"(使用消息队列而不是回调(的更多信息,请参阅此处: https://www.oreilly.com/library/view/python-cookbook/0596001673/ch09s07.html

相关内容