在 TKinter 中显示倒数计时器,同时使代码阻塞但不冻结 GUI
Display a countdown timer in TKinter while making the code BLOCKING but do not freeze the GUI
请仔细阅读我的问题 - 我知道有很多方法可以在不冻结 window 的情况下在 Tkinter 上实现倒数计时器,但是所有现有的解决方案也会导致代码成为非阻塞的。对于我的用例,我需要在时间结束后自动将任务安排到 运行,同时保持 GUI 处于活动状态(未冻结)。我的猜测是我需要以某种方式阻止下一个任务的执行,但这也会冻结 GUI window。那么有什么出路吗?
我目前拥有的:
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 再次冻结。
注意:我可能想多了,另一个答案实际上可能更简单
根据我的理解,你想创建一个程序,在一定时间后 运行 另一个任务,但任务和倒计时都不应干扰 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()
目前,你的问题对我来说意义不大。据我了解,您希望在倒计时后调用 job()
函数。
不需要为此使用线程。您可以在计时器到达 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()
请仔细阅读我的问题 - 我知道有很多方法可以在不冻结 window 的情况下在 Tkinter 上实现倒数计时器,但是所有现有的解决方案也会导致代码成为非阻塞的。对于我的用例,我需要在时间结束后自动将任务安排到 运行,同时保持 GUI 处于活动状态(未冻结)。我的猜测是我需要以某种方式阻止下一个任务的执行,但这也会冻结 GUI window。那么有什么出路吗?
我目前拥有的:
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 再次冻结。
注意:我可能想多了,另一个答案实际上可能更简单
根据我的理解,你想创建一个程序,在一定时间后 运行 另一个任务,但任务和倒计时都不应干扰 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()
目前,你的问题对我来说意义不大。据我了解,您希望在倒计时后调用 job()
函数。
不需要为此使用线程。您可以在计时器到达 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()