在函数中使用简单的定时器循环

using a simple timer loop within a function

目的:选择模式时,将无限期地执行该模式在其自己的循环中,并使用前缀的可见计时,例如60秒。它将在 Raspberry Pi 中用于控制某些自动化。 除了计时器,我已经成功地制作了所有东西。我尝试使用 tk 计时器、倒计时、whiles 和 fors,但部分成功或没有成功。可能是我经验不足,不清楚变量声明的时间和地点

感谢任何帮助,代码如下。

import tkinter as tk
from tkinter import *
import sys
import os
import time

if os.environ.get('DISPLAY','') == '':
    print('no display found. Using :0.0')
    os.environ.__setitem__('DISPLAY', ':0.0')

def mode1():
        print("Mode 1")
        #do stuff

def mode2():
        print("Mode 2")
        #do stuff

def mode3():
        print("Mode 3")
        #do stuff

master = tk.Tk()
master.attributes('-fullscreen', True)
master.title("tester")
master.geometry("800x480")

label1 = tk.Label(master, text='Choose Mode',font=30)
label1.pack()

switch_frame = tk.Frame(master)

switch_frame.pack()

switch_variable = tk.StringVar()
off_button = tk.Radiobutton(switch_frame, bg="red", text="Off", variable=switch_variable,
                            indicatoron=False, value="off", width=20, command=quit)
m1_button = tk.Radiobutton(switch_frame, selectcolor="green", text="Mode 1", variable=switch_variable,
                            indicatoron=False, value="m1", width=20, height=10, command=mode1)
m2_button = tk.Radiobutton(switch_frame, selectcolor="green", text="Mode 2", variable=switch_variable,
                            indicatoron=False, value="m2", width=20, height=10, command=mode2)
m3_button = tk.Radiobutton(switch_frame, selectcolor="green", text="Mode 3", variable=switch_variable,
                             indicatoron=False, value="m3", width=20, height=10, command=mode3)
off_button.pack(side="bottom")
m1_button.pack(side="left")
m2_button.pack(side="left")
m3_button.pack(side="left")

timertext = tk.Label(master, text="Next execution in:")
timertext.place (x=10, y=150)
#timerlabel = tk.Label(master, text=countdown)
#timerlabel.place (x=200, y=150)

master.mainloop()

我尝试将此计时器包含在我的脚本中,但我没有在单独的 window 中显示计时器,而是尝试将其包含在父 window.

import tkinter as tk
from tkinter import *
import sys
import os
import time


class ExampleApp(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.label = tk.Label(self, text="", width=10)
        self.label.pack()
        self.remaining = 0
        self.countdown(10)

    def countdown(self, remaining = None):
        if remaining is not None:
            self.remaining = remaining

        if self.remaining <= 0:
            self.label.configure(text="Doing stuff!")
            self.update_idletasks()
            time.sleep(0.3)
            os.system('play -nq -t alsa synth {} sine {}'.format(0.5, 440))
            #do stuff
            self.remaining = 10
            self.countdown()
        else:
            self.label.configure(text="%d" % self.remaining)
            self.remaining = self.remaining - 1
            self.after(1000, self.countdown)


if __name__ == "__main__":
    app = ExampleApp()
    app.mainloop()
`

在 tkinter 应用程序中使用 time.sleep 不实用 - 基本上它会阻止整个应用程序使其无响应。 为了避免这种情况,可以使用线程并将阻塞部分包含在线程中,这样 tkinter 的主循环就不会被阻塞。 You can see some solutions here.

尽管 tkinter 的 self.after - 只要您想要 运行 的基础任务尽可能快 - 例如。 (几乎)非阻塞,它仍然会慢慢地关闭你的计时器,但可以使用(使用时间感知调度程序而不是倒计时)。

对您来说,这意味着 play 命令应该立即 return 执行,或者花费比您的倒计时更少的时间执行(或者在我的示例中,安排一个循环的时间)。

import functools
import os
import time
import tkinter as tk

BUTTON_ACTIVE_BG_COLOR = "green"
BUTTON_INACTIVE_BG_COLOR = "grey"

BASE_COMMAND = "echo {}"  # 'play -nq -t alsa synth {} sine {}'.format(0.5, 440)
CMD_LONG_BLOCKING = "sleep 5"

# mode_name, mode_command
MODES = [
    ("Mode 1", BASE_COMMAND.format("mode1")),
    ("Mode 2", BASE_COMMAND.format("mode2")),
    ("Mode 3", BASE_COMMAND.format("mode3")),
    ("Mode BLOCKING TEST", CMD_LONG_BLOCKING),
]

SCHEDULER_DELTA = 10  # run every N s


def get_next_sched_time():
    return time.time() + SCHEDULER_DELTA


class ExampleApp(tk.Tk):
    running = False
    current_mode_command = None
    current_active_button = None
    scheduler_next = time.time() + SCHEDULER_DELTA

    # helper for start/stop button to remember remaining time of last scheduler loop
    scheduler_last_remaining_time = SCHEDULER_DELTA

    def __init__(self):
        tk.Tk.__init__(self)
        self.label = tk.Label(self, text=self.running, width=10)
        self.label.pack(fill=tk.X, pady=5)

        self.create_mode_buttons()

        self.stop_button = tk.Button(
            self, text="Stopped", command=self.run_toggle, width=30, bg=None
        )
        self.stop_button.pack(pady=100)

        self.after(0, self.scheduler)

    def create_mode_buttons(self):
        for mode_name, mode_cmd in MODES:
            mode_button = tk.Button(
                self, text=mode_name, width=15, bg=BUTTON_INACTIVE_BG_COLOR
            )

            mode_button.configure(
                command=functools.partial(
                    self.button_set_mode, cmd=mode_cmd, button=mode_button
                )
            )
            mode_button.pack(pady=5)

    def run_toggle(self):
        """
        Method for toggling timer.
        """
        self.running = not self.running
        self.stop_button.configure(text="Running" if self.running else "Stopped")
        self.update_idletasks()

        if self.running:
            # False => True
            self.scheduler_next = time.time() + self.scheduler_last_remaining_time
        else:
            # True => False
            # save last remaining time
            self.scheduler_last_remaining_time = self.scheduler_next - time.time()

    def color_active_mode_button(self, last_active, active):
        if last_active:
            last_active.configure(bg=BUTTON_INACTIVE_BG_COLOR)

        active.configure(bg=BUTTON_ACTIVE_BG_COLOR)
        self.update_idletasks()

    def button_set_mode(self, cmd, button):
        """
        Method for changing the 'mode' of next execution.

        Clicking the buttons only changes what command will be run next.
        Optionally it can (should?) reset the timer.
        """
        if self.current_active_button == button:
            return

        self.color_active_mode_button(
            last_active=self.current_active_button, active=button
        )
        self.current_active_button = button
        print("Mode changed to:", button["text"])

        # self.scheduler_next = get_next_sched_time()  # Reset countdown
        self.current_mode_command = cmd

    def scheduler(self):
        if self.running:
            time_now = time.time()
            if time_now >= self.scheduler_next:
                # TIME TO RUN
                # this can block the whole app
                print("Executing mode: ", self.current_mode_command)
                os.system(self.current_mode_command)

                # Reusing the time before running the command instead of new/current `time.time()`.
                # Like this the time it took to execute that command won't interfere with scheduling.
                # If the current "now" time would be used instead, then next scheduling time(s)
                # would be additionally delayed by the time it took to execute the command.
                self.scheduler_next = time_now + SCHEDULER_DELTA

                # Be wary that the time it took to execute the command
                # should not be greater then SCHEDULER_DELTA
                # or the >scheduler won't keep up<.

            self.label.configure(
                text="Remaining: {:06.2f}".format(self.scheduler_next - time.time())
            )
            self.update_idletasks()
        # This will re"schedule the scheduler" within tkinter's mainloop.
        self.after(10, self.scheduler)


if __name__ == "__main__":
    app = ExampleApp()
    app.mainloop()

一旦 运行ning,尝试阻塞睡眠的“模式”,一旦它执行剩余时间的标签将停止并且 UI 冻结,因为整个 tkinter 应用程序现在被阻塞。如果这对您的用例来说还不够,那么您将需要 threads.