在长时间处理过程中与Tkinter窗口进行交互

5

我有一个基本的Python类,使用标准的Tkinter库创建一个窗口:

import Tkinter

class GUI(Tkinter.Tk):

    def __init__(self,parent):
        Tkinter.Tk.__init__(self,parent)
        self.parent = parent
        self.initialize()

    def lock_func(self):
        while 1==1:
            print "blah"

    def initialize(self):
        self.processBtn = Tkinter.Button(self, text="Process", command=self.lock_func)
        self.processBtn.pack()        

app = GUI(None)
app.mainloop()

当我点击“处理”按钮时,窗口没有响应。当lock_func正在运行时,我希望能够通过x按钮关闭程序。


你所描述的并不是直接可行的。Tkinter 是单线程的。你需要使用 multiprocessing 模块或 threading 模块来实现这个功能。 - Eric Urban
3个回答

4
您可以使用生成器来在循环中保存状态,使用yield将控制权交还给主循环。然后使用self.after来重复调用生成器的next方法,以模拟while True的效果,但是以一种友好于Tkinter的主循环的方式实现。
import Tkinter as tk

class App(object):
    def __init__(self, master):
        self.master = master
        self.initialize()

    def lock_func(self):
        def step():
            while True:
                print("blah")
                self.nextstep_id = self.master.after(1, nextstep)
                yield
        nextstep = step().next
        self.nextstep_id = self.master.after(1, nextstep)

    def stop(self):
        self.master.after_cancel(self.nextstep_id)
        print("stopped")

    def initialize(self):
        self.nextstep_id = 0
        self.process_button = tk.Button(self.master, text="Process",
                                        command=self.lock_func)
        self.stop_button = tk.Button(self.master, text="Stop",
                                     command=self.stop)        
        self.process_button.pack()
        self.stop_button.pack(expand='yes', fill='x')

root = tk.Tk()
app = App(root)
root.mainloop()

我建议使用一些与 update 不同的变量名,因为它很容易与小部件方法 update() 混淆 - 当我回复时,我差点犯了这个错误,直到我再看了一眼并意识到 update 是一个临时变量而不是函数调用。 - Bryan Oakley
不要忘记更改调用update/nextstep的两个位置(即:在while循环内部)。 - Bryan Oakley
在每次迭代中进行100毫秒的暂停会使循环变得非常缓慢,我希望它尽可能快。 - Ghilas BELHADJ
我相信你知道如何解决那个问题 :) - unutbu

3

您可以使用window.update()方法,在每次更改GUI后使其保持活动和功能。在根mainloop期间,这会自动发生,但如果您延长了主循环,则最好手动执行。将window.update()放入需要一段时间的循环中。注意:window是一个Tk()对象。


2
window.update() 不仅更新窗口,还会处理任何类型的挂起事件 - 打字、按钮点击、滚动等。如果您只想更新显示,应调用 window.update_idletasks()。您还应该警告称调用 update 有一定的风险,因为如果发生导致再次调用此函数的事件,您将得到嵌套的事件循环。 - Bryan Oakley
我假设 OP 希望在处理过程中 GUI 仍然保持功能。 - The-IT
听明白了。虽然在示例程序中调用update是一种不错的快速修复方法,但在构建一个真正的程序时,通常有更好的结构方式。只是你的回答有点误导人,说update只是"更新窗口"其实它做的事情远不止这些。 - Bryan Oakley
我一直在使用update方法。我从来没有真正考虑过你在第一条评论中说的话,所以谢谢。 - The-IT

0

一种方法是使用线程:

import Tkinter
import thread

class GUI(Tkinter.Tk):

    def __init__(self,parent):
        Tkinter.Tk.__init__(self,parent)
        self.parent = parent
        self.initialize()

    def lock_func(self):
        while 1==1:
            print "blah"

    def initialize(self):
        self.processBtn = Tkinter.Button(self, text="Process", command=lambda: thread.start_new_thread(self.lock_func, ()))
        self.processBtn.pack()        

app = GUI(None)
app.mainloop()

然而,它会一直打印直到你关闭Python控制台。

为了停止它,你可以使用另一个按钮来改变一个变量:

import Tkinter
import thread

class GUI(Tkinter.Tk):

    def __init__(self,parent):
        Tkinter.Tk.__init__(self,parent)
        self.parent = parent
        self.shouldPrint = True
        self.initialize()

    def lock_func(self):
        while self.shouldPrint:
            print "blah"

    def setShouldPrint(self, value):
        self.shouldPrint = value

    def initialize(self):
        self.processBtn = Tkinter.Button(self, text="Process", command=lambda: thread.start_new_thread(self.lock_func, ()))
        self.stopBtn = Tkinter.Button(self, text = "Stop", command = lambda: self.setShouldPrint(False))
        self.processBtn.grid(row = 1)
        self.stopBtn.grid(row = 2)


app = GUI(None)
app.mainloop()

没有其他按钮可以停止它吗? - Ghilas BELHADJ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接