python-3.x 如何停止Tkinter定时器线程?

a5g8bdjr  于 2022-12-15  发布在  Python
关注(0)|答案(1)|浏览(234)

我正在尝试在tkinter中做一个简单的计数计时器。计时器启动并计数,但我不知道如何停止它。我正在尝试使用threading.event,但我错过了一些东西,我就是不能得到正确的代码。
运行下面的代码会得到“attributeError:“MainFrame”对象没有属性“x”“。

#!/usr/bin/python3.9
   
import tkinter as tk
from tkinter import ttk
from threading import Thread
from threading import Event
import time

class Timer(Thread):
    def __init__(self, count):
        super().__init__()

        self.stop_threads = Event()
        self.entry1 = None
        self.count = count

    def run(self):
        while True:
            if self.stop_threads.is_set():
                break;
            else:
                mins, secs = divmod(self.count, 60)
                timeformat = '{:02d}:{:02d}'.format(mins, secs)
                #print(timeformat, end='\r')
            
                self.entry1 = timeformat
                time.sleep(1)
                self.count += 1 

    def stop_threads(self):
        self.stop_threads.set()

class MainFrame(ttk.Frame):
    def __init__(self, container):
        super().__init__(container)
     
        ### TOP BUTTON ROW
        self.frame1=ttk.Frame(self, padding = (0, 50, 0, 30), ) ## LH padding, TOP padding, RH padding, BOTTOM padding
        self.frame1.pack(fill="x", ipadx=0, ipady=0, padx=0, pady=0, expand=False)

        ### TOP TEXT BOX
        self.frame2=ttk.Frame(self, padding = (0, 0, 0, 0), ) ## LH padding, TOP padding, RH padding, BOTTOM padding
        self.frame2.pack(fill="x", ipadx=0, ipady=0, padx=0, pady=0, expand=False)

        ### MID TEXT BOX
        self.frame3=ttk.Frame(self, padding = (40, 20, 40, 0))
        self.frame3.pack(fill="x", ipadx=0, ipady=0, padx=0, pady=0, expand=False)

        # show the frame on the container
        self.pack()

        ### START BUTTON
        self.button1=tk.Button(self.frame1, text='START')
        self.button1['command'] = self.delay
        self.button1.pack(expand=True, padx=0, pady=0, ipadx=0, ipady=0, side="left")

        ### STOP BUTTON
        self.button2=tk.Button(self.frame1, text='STOP')
        self.button2['command'] = self.stop
        self.button2.pack(expand=True, padx=0, pady=0, ipadx=0, ipady=0, side="right")

        ### TIMER DISPLAY LABEL
        self.entry1_label=tk.Label(self.frame2 , text='TIMER', bg="white", fg="blue")
        self.entry1_label.pack(padx=10, pady=0, ipadx=0, ipady=0, expand=False, side="left")

        ### TIMER DISPLAY
        self.entry1_var = tk.StringVar()
        self.entry1=tk.Entry(self.frame2, textvariable="entry6_var", width=10, justify="center")
        self.entry1.pack(padx=0, pady=0, ipadx=0, ipady=4, expand=False, side="left")

        ### MSG DISPLAY
        self.text1 = tk.Text(self.frame3, height=1)
        self.text1.pack(expand=False, padx=0, pady=0, ipadx=0, ipady=0, side="left")        

    def delay(self):
        event = Event()
        self.button1['state'] = tk.DISABLED
        timer_thread=Timer(0)
        timer_thread.start()
        self.monitor(timer_thread)  
 
    def stop(self):
        x = Timer(0)
        x.stop_threads()
        self.text1.insert("end", "stopping")

    def monitor(self,thread):
        if thread.is_alive():
            self.entry1.delete('0', tk.END)
            self.entry1.insert("end", thread.entry1)
            #check the thread every 100ms
            self.after(100,lambda:self.monitor(thread))
        else:
            self.text1.insert("end", "thread is stopped")
            self.button1['state']=tk.NORMAL

class App(tk.Tk):
    def __init__(self):
        super().__init__()  

        # configure the root window
        self.geometry("600x300")
        self.option_add('*Font', 'Helvetica 9') ## drop down menu font & size / https://coderslegacy.com/python/problem-solving/change-font-in-tkinter/
        self.configure(bg="white") ## bottom background colour
        self.resizable(False, False)

        self.title("TEST TIMER")

        ### frame style 
        f = ttk.Style()
        f.configure('TFrame', background='white') ## background colour

    #def exit(self):
        #self.destroy() 

if __name__ == "__main__":
    app = App()
    frame = MainFrame(app)
    app.mainloop()
lskq00tm

lskq00tm1#

移除

def stop_threads(self):
    self.stop_threads.set()

Timer开始,尝试使用x.stop_threads.set()而不是x.stop_threads()
问题的一部分在于你的Timer.__init__()self.stop_threads = Event(),但是def stop_threads(self)重写了它,因为它有相同的名字。最后,因为你用self.stop_threads示例化了Event类,所以你不需要这个方法。
但是,您可能会遇到一些麻烦,因为MainFrame.delayMainFrame.stop方法都在示例化Timer,并且这些计时器将具有单独的stop Event
您还可以稍微修改一下run方法:

def run(self):
    while not self.stop_threads.is_set():
        mins, secs = divmod(self.count, 60)
        timeformat = '{:02d}:{:02d}'.format(mins, secs)
        # print(timeformat, end='\r')
        self.entry1 = timeformat
        time.sleep(1)
        self.count += 1

除此之外,在tkinter旁边使用Threadsleep时我会很小心,如果你想要一个倒计时器,你最好使用tkinter.after,因为它不会导致你的应用挂起。
如果你想使用after,你可以完全删除线程。

"""Count down from a given number of seconds"""
class App(tk.Tk):
    def __init__(self):
        super().__init__()
        ...  # code removed for brevity
        self.time = 120  # time in sec; grab this from a user input
        self.btn_start = ttk.Button(self, text='Start Timer', command=self.timer)
        self.btn_start.pack()

    def timer(self):
        if self.time:  # if there is time remaining...
            # I'm updating the button's text, but you don't have to
            self.btn_start.configure(text=self.time)
          
            # do whatever else you want here; update a label,
            # play a sound, etc.

            # wait 1 sec, call this function again
            self.after_id = self.after(1000, self.timer)
            self.time -= 1  # decrement time remaining
        else:
            self.after_cancel(self.after_id)  # stop the loop
            self.btn_start.configure(text='Start Timer')  # reset btn txt

相关问题