在TKinter中显示一个倒计时计时器,同时使代码阻塞但不冻结GUI



请仔细阅读我的问题-我知道有很多方法可以在不冻结窗口的情况下在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()

相关内容

最新更新