You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tkinter次级窗口需等脚本运行完毕才打开的解决方案咨询

问题原因与解决方法

你的问题核心是Tkinter的事件循环被阻塞,加上错误使用多主窗口导致的。下面是具体问题和修复方案:

核心问题点

  1. 多主窗口冲突:Tkinter设计上只允许一个Tk()主实例,你创建第二个tk.Tk()会导致事件循环混乱,窗口无法及时渲染。
  2. 主线程阻塞:Phidget的初始化、time.sleep(1)都在Tk的主线程执行,阻塞了窗口的绘制和事件处理,所以次级窗口要等这些代码跑完才显示。
  3. UI未实时更新:标签文本只在初始化时设置,count变化后没有同步更新到界面。
  4. 监听时长过短: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 18:57:03