在函数中使用简单的定时器循环
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.
目的:选择模式时,将无限期地执行该模式在其自己的循环中,并使用前缀的可见计时,例如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.