我正在用 Python 构建一个 GUI,并使用线程生成多个独立工作的倒计时器。我有一个最小化的测试模块,我将其包括在下面。我希望按钮开始倒计时,然后当再次单击时停止倒计时,然后重置,以便可以在下次单击时启动。一旦时间耗尽,也会自行重置。唯一的问题是,它停止后,我无法让它重新启动。我收到“无法启动线程两次”错误。我一直在尝试使用条件循环来让线程退出自身,但它没有起作用。我真的很感激一些见解
实际上我希望这个程序能够做两件事。 1) 一直运行到定时器然后自动重置以便可以重新启动 2) 倒计时中途停止,自动重置以便重新启动
我认为解决这些问题对于社区来说是有值(value)的,因为这是一个现实世界解决方案的示例,该问题是很多人在论坛上讨论的问题,即如何解决不重新启动线程的问题。
__author__ = 'iKRUSTY'
'''
there are two things i would like this code to be able to do
1) run all the way through the timer and then reset so that it can be restarted
2) be stopped before completing and then reset so that it can be restarted
'''
from tkinter import *
import time
import os
import threading
#Variables
global FRYER_ONE_TIME_VAR # holds the value for changing label text to update timer
global BASKET_ONE_TARGET_TIME #this is a user input that will determine the length of the countdown
global timerOneStayAlive #this is the value that i am attempting to use so that the thread closes after it is set to false
timerOneStayAlive = FALSE #initializes to false because the the invert function changes it to true once the button is clicked
FRYER_ONE_TIME_VAR=" " #used to pass time between functiuons
#Font Profiles
SMALLEST_FONT = ("Verdana", 9)
SMALL_FONT = ("Verdana", 10)
LARGE_FONT = ("Verdana", 12)
LARGEST_FONT = ("Verdana", 18)
class timer():
global BASKET_ONE_TARGET_TIME
BASKET_ONE_TARGET_TIME = 5 #Just setting it manually for now
def __init__(self):
self.s = 0 #these values are used to convert from seconds to a minute:second format
self.m = 0 #these values are used to convert from seconds to a minute:second format
def SetTime(self, seconds):
self.seconds=seconds #this is a counter that will be used to calculate remaining time
def TimerReset(self):
self.seconds = BASKET_ONE_TARGET_TIME #resets counter to target time
def StartCountdown(self, FryerLabel): #takes a label as an argumet to tell it where to display the countdown
global timerOneStayAlive
print("StartCountdown Started!") #USED FOR TROUBLE SHOOTING
self.seconds = BASKET_ONE_TARGET_TIME #set start value for seconds counter
self.seconds=self.seconds+1 #makes the full time appear upon countdown start
while self.seconds > 0:
FRYER_ONE_TIME_VAR = self.CalculateTime() #Calculate time reduces the counter by one and reformats it to a minute:second format. returns a string
FryerLabel.config(text=FRYER_ONE_TIME_VAR) #Update Label with current value
print(self.seconds) #USED FOR TROUBLE SHOOTING
time.sleep(1)
# reset label with default time
if self.seconds == 0: #Reset once counter hits zero
print("resetting time") #USED FOR TROUBLE SHOOTING
self.seconds = BASKET_ONE_TARGET_TIME + 1
FRYER_ONE_TIME_VAR = self.CalculateTime()
FryerLabel.config(text=FRYER_ONE_TIME_VAR)
break
print("TimerStayAlive before invert: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
timerOneStayAlive = invert(timerOneStayAlive) #inverts the value back to FALSE so that Ideally the loop breaks
# and the thread completes so that it can be called again
print("TimerStayAlive after invert: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
print("end startcountdown") #USED FOR TROUBLE SHOOTING
def CalculateTime(self):
#print("CalculateTime has been called")
lessThanTen=0
self.seconds = self.seconds - 1
self.m, self.s = divmod(self.seconds, 60)
if self.s<10:
lessThanTen=1
#create time String Variables
colonVar=':'
minutesString = str(self.m)
secondsString = str(self.s)
#insert variables into string array
timeArray = []
timeArray.append(minutesString)
timeArray.append(colonVar)
if lessThanTen == 1:
timeArray.append("0")
timeArray.append(secondsString)
#prepare for output
self.timeString = ''.join(timeArray)
return self.timeString
def invert(boolean):
return not boolean
def BasketOneButtonClicked():
print("button clicked") #USED FOR TROUBLE SHOOTING
global timerOneStayAlive
timerOneStayAlive = invert(timerOneStayAlive) #Changes from FALSE to TRUE
if timerOneStayAlive == TRUE:
print("timerOneStayAlive: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
basketOneThread.start()
updateButtonStatus() #changes text of button depending on whether timerOneStayAlive is TRUE or FALSE
else:
print("timerOneStayAlive: ", timerOneStayAlive) #USED FOR TROUBLE SHOOTING
return
def updateButtonStatus():
global timerOneStayAlive
if timerOneStayAlive == FALSE:
basketOneStartButton.config(text="Start")
if timerOneStayAlive == TRUE:
basketOneStartButton.config(text="Stop")
def BasketOneThreadComShell(): # I used this so that i can ideally call multiple functions with a single thread
'''
This is where i think the problem may be. this is what is called when the thread is initialized and theoretically
when this completes the thread should come to a halt so that when the button is reset, the thread can be called again
I think that the function is completing but for some reason the thread keeps on running.
'''
global timerOneStayAlive
print("ComShell Started") #USED FOR TROUBLE SHOOTING
while timerOneStayAlive:
basketOneTimer.StartCountdown(countdownLabelBasket1)
updateButtonStatus()
if timerOneStayAlive == FALSE: #redundant because while loop should do it. i just tried it because i couldnt get the process to end
break
print("Threadshell has ended") #USED FOR TROUBLE SHOOTING
return
print("after return check") #USED FOR TROUBLE SHOOTING
root = Tk()
'''
the following is all just building the GUI Stuff
'''
Container = Frame(root)
Container.grid(row=1, column=0, padx=10, pady=10)
countdownContainerBasket1 = Label(Container, width=10, height=5)
countdownContainerBasket1.grid(row=2, column=0, sticky=NSEW)
countdownLabelBasket1 = Label(countdownContainerBasket1, text="Basket 1", background="white", anchor=CENTER, width=10, height=6, font=LARGE_FONT, padx=20)
countdownLabelBasket1.pack()
basketOneTimer = timer()
basketOneTimer.SetTime(5)
basketOneStartButton = Button(Container, text="Start", font=LARGE_FONT, command=BasketOneButtonClicked)
basketOneStartButton.grid(row=3, column=0, padx=10, pady=10)
basketOneThread = threading.Thread(target=BasketOneThreadComShell) #this is where the thread is initialized. start() is called in BasketOneButtonClick
print(threading.active_count) #tried to use this to see if the thread was exiting but it didnt help
root.mainloop()
您可能想运行一下,只是为了看看 GUI 是什么样子以及我说的计时器是什么意思。尝试让它一直运行并重置以了解我在说什么。
如果有其他有用的信息,请告诉我,请记住这是我使用 python 的第一周,所以我不是专家。谢谢大家
最佳答案
Python 社区非常幸运,拥有一个被广泛接受的风格指南 https://www.python.org/dev/peps/pep-0008/ 。由于您的非 pep8 命名和格式,我很难快速理解您所讨论的问题。
其他线程无法与您的小部件交互,只有 tkinter 主循环可以做到这一点。您可以使用 tkinter 提供的 after 方法在主循环中的给定时间段后运行函数。
看这个例子 Mutli-threading python with Tkinter举个例子!
需要明确的是,使用 tkinter 进行线程处理可能会起作用,但它不可靠,并且您会得到难以调试的意外行为。不要这样做。
建议
将调用 tkinter after 方法来启动一个函数,该函数无限期地检查队列中是否有要运行的函数。然后它使用 after 方法安排这些函数由 tkinter 运行
我会有一个带有启动、停止和暂停方法的计时器类。 启动时,它会向上/向下计数,并在完成后重新启动。您创建的每个计时器实例还需要对 tkinter 按钮的引用。
要更新按钮,您可以在队列中放置一个函数,如下所示
tkinter_queue.put(lambda: but.config(text=i))
现在检查队列的函数将更新您的按钮计时器
此外,由于您正处于 Python 编程的第一周,因此您可以确信,您遇到的大多数问题都会在这里找到答案(如果在其他地方找不到答案)。请事先做好研究。 GUI 编程和线程不是最简单的主题,因此更有理由进行研究。
关于python - 如何使用线程来创建多个独立的可重用对象实例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31154926/