请仔细阅读我的问题-我知道有很多方法可以在不冻结窗口的情况下在Tkinter上实现倒计时计时器,但所有现有的解决方案也会导致代码非阻塞。对于我的用例,我需要安排一个任务在时间结束后自动运行,同时保持GUI活动(而不是冻结)。我的猜测是,我需要以某种方式阻止下一个任务的执行,但这也会冻结GUI窗口。那么还有什么出路吗?
目前为止我有什么:
root = Tk.Tk()
def countdown(time, msg='Counting down'):
def tick():
nonlocal time
status(f'{msg} ({60 - time}sec)')
time += 1
root.after(1000, tick)
,其中status()
只是一个更新某些按钮文本的函数。当前的倒计时功能本身不起作用,因为我没有办法在超时时间后停止after()
。
程序的其他部分如下:
countdown(10) # I need this line to be blocking or somehow prevents the code from going to next line
print('starting scheduled job...')
job()
我已经尝试使用线程,但正如我之前所说,这导致代码是非阻塞的,当我使用Thread.join()时,整个GUI再次冻结。
目前,你的问题对我来说没有多大意义。根据我的理解,您希望在倒计时后调用job()
函数。
不需要使用thread。你可以只使用之后,一旦定时器达到0调用job()
函数。
下面是一个最小的例子
import tkinter as tk
def job():
status.config(text="starting job")
def countdown(time, msg='Counting down'):
time -= 1
status.config(text=f'{msg} ({time}sec)')
if time != 0:
root.after(1000, countdown, time)
else:
job() # if job is blocking then create a thread
root = tk.Tk()
status = tk.Label(root)
status.pack()
countdown(20)
root.mainloop()
注意:我可能想多了,另一个答案实际上可能更简单。
根据我的理解,你想创建一个程序,它将在一段时间后运行另一个任务,但任务和倒计时都不应该干扰GUI(但任务必须只在倒计时后运行),在代码注释中解释:
# import what is needed
from tkinter import Tk, Button, Label
from threading import Thread
from queue import Queue, Empty
import time
# the function that the button will call to start the countdown
# and after that the task
def start_countdown():
# disable button so not to accidentally run the task again
# before it has even started
button.config(state='disabled')
# create a queue object
queue = Queue()
update_label(queue)
# set daemon=True to kill thread if the main thread exits
Thread(target=countdown, args=(queue, ), daemon=True).start()
# the task you want to do after countdown
def do_task():
for _ in range(10):
print('doing task...')
time.sleep(0.5)
# the actual countdown (btw using `time.sleep()` is more precise
# and only the thread will sleep)
# put data in queue so that it can easily be accessed
# from the main thread
def countdown(queue):
seconds = 10
for i in range(1, seconds + 1):
queue.put(f'Seconds left: {seconds + 1 - i}')
time.sleep(1)
queue.put('Starting task')
# place a sentinel to tell the reading part
# that it can stop
queue.put('done')
# do the task, this will run it in the same thread
# so it won't block the main thread
do_task()
# function to update the label that shows the users how many seconds left
def update_label(queue):
try:
# since block=False it will raise an exception
# if nothing is in queue
data = queue.get(block=False)
except Empty: # therefore except it and simply pass
pass
else:
# if no error was raised check if data is sentinel,
# if it is, stop this loop and enable the button (if needed)
if data == 'done':
button.config(state='normal')
return
# otherwise just update the label with data in the queue
label.config(text=data)
finally:
# and (almost) no matter what happens (nothing much should) loop this again
root.after(100, update_label, queue)
# basic tkinter setup
root = Tk()
root.geometry('300x200')
button = Button(root, text='Start countdown', command=start_countdown)
button.pack(expand=True)
label = Label(root)
label.pack(expand=True)
root.mainloop()