Tkinter次级窗口需等脚本运行完毕才打开的解决方案咨询
问题原因与解决方法
你的问题核心是Tkinter的事件循环被阻塞,加上错误使用多主窗口导致的。下面是具体问题和修复方案:
核心问题点
- 多主窗口冲突:Tkinter设计上只允许一个
Tk()主实例,你创建第二个tk.Tk()会导致事件循环混乱,窗口无法及时渲染。 - 主线程阻塞:Phidget的初始化、
time.sleep(1)都在Tk的主线程执行,阻塞了窗口的绘制和事件处理,所以次级窗口要等这些代码跑完才显示。 - UI未实时更新:标签文本只在初始化时设置,count变化后没有同步更新到界面。
- 监听时长过短:
time.sleep(1)后直接关闭Phidget设备,导致只能监听1秒的状态变化,无法持续工作。
修复后的代码
import tkinter as tk from Phidget22.Devices.DigitalInput import * import threading class Parts: def __init__(self): # 主窗口,仅创建一个Tk实例 self.root = tk.Tk() self.root.geometry("500x350") self.root.title("Parts Counter Program") self.label = tk.Label(self.root, text="Welcome To Parts Counter", font=('Arial', 20)) self.label.pack(padx=10, pady=10) self.button1 = tk.Button(self.root, text="Create New Record", font=('Arial', 15), command=self.openNewWindow1) self.button1.pack(padx=25, pady=25) # 将计数变量设为类属性,替代全局变量 self.count = -1 self.count1 = -1 self.root.mainloop() def openNewWindow1(self): # 次级窗口用Toplevel,基于主窗口创建 self.new_window = tk.Toplevel(self.root) self.new_window.title("Create New Record") Parts_Info_Frame = tk.LabelFrame(self.new_window) Parts_Info_Frame.grid(row=5, column=0, padx=5, pady=5) Parts_Label = tk.Label(Parts_Info_Frame, text="Parts No.") Parts_Label.grid(row=5, column=0) # 保存标签引用,方便后续更新文本 self.parts_box = tk.Label(Parts_Info_Frame, width=5, height=2, text=self.count) self.parts_box.grid(row=5, column=1) Broken_Label = tk.Label(Parts_Info_Frame, text="Broken No.") Broken_Label.grid(row=5, column=2) self.broken_box = tk.Label(Parts_Info_Frame, width=5, height=2, text=self.count1) self.broken_box.grid(row=5, column=3) # 绑定窗口关闭事件,清理Phidget资源 self.new_window.protocol("WM_DELETE_WINDOW", self.close_phidgets) # 启动独立线程处理Phidget监听,避免阻塞Tk主线程 threading.Thread(target=self.setup_phidgets, daemon=True).start() def onDigitalInput0_StateChange(self, state): print(f"State [0]: {state}") self.count += 1 print(self.count / 2) # 用after方法在主线程更新UI(子线程不能直接操作Tk控件) self.root.after(0, lambda: self.parts_box.config(text=self.count)) def onDigitalInput1_StateChange(self, state): print(f"State [1]: {state}") self.count1 += 1 print(self.count1 / 2) self.root.after(0, lambda: self.broken_box.config(text=self.count1)) def setup_phidgets(self): self.digitalInput0 = DigitalInput() self.digitalInput1 = DigitalInput() self.digitalInput0.setChannel(0) self.digitalInput1.setChannel(1) self.digitalInput0.setOnStateChangeHandler(self.onDigitalInput0_StateChange) self.digitalInput1.setOnStateChangeHandler(self.onDigitalInput1_StateChange) # 等待设备连接 self.digitalInput0.openWaitForAttachment(5000) self.digitalInput1.openWaitForAttachment(5000) # 无需sleep,线程会持续运行直到设备关闭 def close_phidgets(self): # 关闭Phidget设备后销毁窗口 self.digitalInput0.close() self.digitalInput1.close() self.new_window.destroy() if __name__ == "__main__": Parts()
关键修改说明
- 替换Tk为Toplevel:次级窗口用
Toplevel基于主窗口创建,避免多主窗口的事件循环冲突。 - 线程分离Phidget逻辑:把Phidget的初始化和监听放到独立线程,让Tk主线程专注处理UI渲染和事件,不再被阻塞。
- 类属性替代全局变量:将计数变量设为类属性,代码更整洁,避免全局变量的副作用。
- UI实时更新:在Phidget的回调中用
root.after(0, ...)触发主线程更新标签文本,符合Tkinter控件只能在主线程操作的规则。 - 窗口关闭时清理资源:通过
protocol绑定窗口关闭事件,关闭Phidget设备后再销毁窗口,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Kingskiddles
相关产品推荐
相关产品推荐

