Tkinter .after 永远运行并且 .mainloop 永远不会运行

Tkinter .after runs forever and .mainloop never runs

我正在我的 RPi3 上创建一个 python 程序,它根据 Tkinter 比例更改 GPIO 引脚的频率。

这是我的代码:

import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
from Tkinter import *
import time

freq = 1.0

master = Tk()

def update():
    period = 1.0/float(freq)

    GPIO.output(8, GPIO.HIGH)
    time.sleep(period/2.0)
    GPIO.output(8, GPIO.LOW)
    time.sleep(period/2.0)

    master.after(0, update)

scale = Scale(master, from_=1, to=20000, orient=HORIZONTAL, variable=freq)
scale.pack()

GPIO.setup(8, GPIO.OUT)

master.after(0, update)
master.mainloop()

GPIO.cleanup()

出于某种原因,master.after(0, update) 永远运行而 master.mainloop() 永远不会运行。我知道是因为刻度永远不会出现,引脚 8 打开半秒,然后关闭半秒,然后循环重复。
如果我按 Ctrl+C 然后 master.after(0, update) 停止 运行 并且 master.mainloop() 开始 运行,比例出现,但是当我向左拖动滑块时没有任何反应正确的。

我通过在终端中输入 sudo python tone.py 然后按回车键来 运行 程序。

Fix/Alternative?

mainloop() 是 运行,但 Tkinter 除非处于空闲状态,否则不会更新视图。 KeyboardInterrupt 将告诉它进行清理,此时它完成其事件队列(包括更新界面)并退出。

解决方案:给 mainloop 时间空闲 - 你真的只需要改变你的 after(0, update) 以在内部有几毫秒或告诉 master.update_idletasks() 更新 GUI。

一个稍微好一点的解决方案是让你的 high/low 部分成为它们自己的函数,这些函数在需要的延迟之后调用每个 - sleep 在主循环 gui 中运行是个坏主意,因为你的 GUI如果程序正在休眠,则无法更新任何内容。 (它也不能接受输入等。)在再次更新之前,您将有毫秒帧来更改输入,同时使用两个相互调用的函数 after 选择的毫秒可以让您在等待时调整时间等翻到另一个 on/off.

处理事件

您犯了两个比较常见的错误:您不应该 after(0, ...),并且您不应该调用 sleep

  1. after(0, ...)表示每处理完一个事件,就立马追加一个事件。事件循环永远没有机会处理队列中的其他事件,包括处理滑块、屏幕更新等的事件

  2. 当您调用 sleep 时,GUI 会执行此操作:它会休眠。当它处于休眠状态时,它无法处理任何事件。

解决办法是只使用after合理的时间跨度,根本不调用sleep

例如:

def update():

    ...
    # set the pin high
    GPIO.output(8, GPIO.HIGH)

    # set the pin low in half the period
    master.after(period/2, GPIO.output, 8, GPIO.LOW)

    # do this again based on the period
    master.after(period, update)

另一种方式,如果你想每半秒连续切换一次 pin,将是这样的:

def update(value=GPIO.HIGH):
    GPIO.output(8, value)
    next_value = GPIO.LOW if value == GPIO.HIGH else GPIO.HIGH
    master.after(500, update, next_value)

使用滑块

当您使用滑块的 variable 属性时,变量必须是特殊 tkinter 变量之一的实例,例如 IntVar。然后您需要调用 getset 来获取或设置该值。

例如:

freq = IntVar()
freq.set(1)

def update(value=GPIO.HIGH):
    period = 1.0/float(freq.get())
    ...