tkinter:能够在脚本运行时单击其他按钮

jas*_*son 0 python tkinter

我有一个简单的 Tkinter gui,上面有大约 20 个按钮。当我单击一个按钮时,脚本会运行大约 5 分钟。在此期间,我必须等到脚本停止运行才能单击其他按钮。有没有办法设置窗口,以便我可以在第一个单击的脚本运行时单击其他按钮?

from Tkinter import *
import Tkinter as tk
import time

def function1():
    time.sleep(60)
    print 'function1'


def function2():
    time.sleep(60)
    print 'function2'

root = Tk()

w = 450 # width for the Tk root
h = 500# height for the Tk root

frame = Frame(root, width=w,height =h)
button1=Button(frame, text = 'function 1',fg='black',command=function1).grid(row=1,column=1) 
button2=Button(frame, text = 'function 2',fg='black',command=function2).grid(row=1,column=2) 

frame.pack()
root.mainloop()
Run Code Online (Sandbox Code Playgroud)

我希望能够在仍在运行function2后点击function1

aba*_*ert 5

如果触发需要 1 分钟运行的回调,则 1 分钟内不会返回主循环,因此 GUI 无法响应任何内容。

对此有两种常见的解决方案。


第一种是使用后台线程:

def function1():
    time.sleep(60)
    print 'function1'

def function1_background():
    t = threading.Thread(target=function1)
    t.start()

button1 = Button(frame, text='function 1', fg='black', command=function1_background)
Run Code Online (Sandbox Code Playgroud)

这很简单,但它仅在您的代码纯粹执行后台工作时才有效,不涉及任何 tkinter 小部件。


这里唯一的问题是你必须有def20 个额外的功能。你不想重复那么多——那是 80 行重复的样板代码,这些代码妨碍了查看重要代码的方式,还有 20 次机会在复制粘贴中犯下一个很难追踪的愚蠢错误,并且如果您以后决定使用进程而不是线程,那么您必须更改 20 个地方,以便工作可以更好地并行化,或者 4 个线程池与排队的后台任务。

您可以通过几种不同的方式解决该问题。请参阅此问题以获得更深入的解释,但简而言之,您可以让 Python 为您完成一些重复性工作。


您可以def使用单个辅助函数:

def background(func):
    t = threading.Thread(target=func)
    t.start()
Run Code Online (Sandbox Code Playgroud)

...然后是lambda20 个单独的函数:

button1 = Button(frame, text='function 1', fg='black', command=lambda: background(function1))
Run Code Online (Sandbox Code Playgroud)

或者,您可以使用partial以下方法部分应用该功能:

button1 = Button(frame, text='function 1', fg='black', command=functools.partial(background, function1))
Run Code Online (Sandbox Code Playgroud)

或者,如果您不想在后台之外调用函数,您可以编写一个装饰器并将其应用于每个函数def

def background(func):
    @functools.wraps(func)
    def wrapper():
        t = threading.Thread(target=func)
        t.start()
    return wrapper

@background
def function1():
    time.sleep(60)
    print 'function1'
Run Code Online (Sandbox Code Playgroud)

如果你不能使用线程(例如,因为后台工作涉及摆弄你的 tkinter 小部件),另一种方法是重构你的代码,这样,而不是一个需要 1 分钟的单一任务,它是一堆单独的任务,每个都需要几分之一秒并安排下一部分:

def function1(count=60):
    if count > 0:
        time.sleep(0.1)
        frame.after(0, function1, count-0.1)
    else:
        print 'function1'

button1 = Button(frame, text='function 1', fg='black', command=function1)
Run Code Online (Sandbox Code Playgroud)

这总是有效的,如果你能找到一种方法来做到这一点。您的实际工作可能不像 a 那样容易分成 0.1 秒的块sleep(60)