代码之家  ›  专栏  ›  技术社区  ›  Moshe S.

如何根据来自单独线程的特定值更新小部件?

  •  -2
  • Moshe S.  · 技术社区  · 6 年前

    我的GUI应用程序有多个屏幕(3),而每个屏幕都包含用户可以更改的特定文本小部件(我根据一个众所周知的stackoverflow解决方案基于这个多屏幕GUI)。填充某个屏幕的字段后,用户可以将这些值“烧录”到某个硬件。为了能够“烧录”到硬件,我在应用程序运行后立即启动到硬件的Telnet会话(IP是硬编码的)。

    每个帧都显示了状态栏,我想用到硬件的Telnet连接的当前状态来更新它。为了维护Telnet连接,我使用了一个单独的线程。我还使用了一个队列来更新当前状态。

    我的问题是我无法用当前状态更新状态栏(状态标签)。在下面的代码中,您可以看到我试图在状态更改时生成一个事件。但是,它没有起作用。如何用实际状态更新状态栏?

    编辑后

    from tkinter import font, ttk
    import tkinter as tk
    from re import match
    
    import telnetlib
    import threading
    import queue
    
    import time
    
    LARGE_FONT= ("Verdana", 12)
    Current_PN = '123456789'    # This global ver is used for the purpose of automatic PN fill
    
    HOST = '10.0.1.235'
    PORT = 23
    
    telnet_session = None       # After I create the Telnet session I will keep track with this variable
    connected = False
    
    class BurningApp(tk.Tk):
        def __init__(self, *args, **kwargs):
    
            tk.Tk.__init__(self, *args, **kwargs)
            container = tk.Frame(self)
    
            container.pack(side="top", fill="both", expand = True)
    
            container.grid_rowconfigure(0, weight=1)
            container.grid_columnconfigure(0, weight=1)
    
            self.title('Burning App')  #handling the application's Window title
    
            w = 1000    # Windows width
            h = 600     # Windows height
    
            ws = self.winfo_screenwidth()       # Screen resolution width
            hs = self.winfo_screenheight()     # Screen resolution height
    
            # w = ws * 0.8    # Fit the GUI width to 80% percent of the screen
            # h = hs * 0.8    # Fit the GUI height to 80% percent of the screen
    
            x = (ws/2) - (w/2)      # X coordinate for the purpose of GUI placement
            y = (hs/2) - (h/2)      # X coordinate for the purpose of GUI placement
    
    
    
            self.resizable(width=False, height=False)
    
            self.geometry('%dx%d+%d+%d'%(w,h,x,y))
    
            self.frames = {}
    
            for F in (MainScreen, FirstScreen, SecondScreen):
    
                frame = F(container, self)
    
                self.frames[F] = frame
    
                frame.grid(row=0, column=0, sticky="nsew")
    
            self.show_frame(MainScreen)
    
            # Start the Telnet session
            self.connect_to_uut()
    
            # Create the queue that will hold the status
            self.status_queue = queue.Queue()
    
            # Set up the thread to do asynchronous I/O
            self.running = 1    # True
            self.thread = threading.Thread(target=self.workerThread)
            self.thread.start()
    
            # Start the periodic call in the GUI to check if the queue contains
            # anything
            self.periodicCall()
    
        def show_frame(self, cont):
            '''
            This function is being used in order to raise a frame on demand
            '''
            frame = self.frames[cont]
            frame.tkraise()
    
    
        def connect_to_uut(self, Retry=5):
            '''
            This functions is used for the purpose of connecting to the UUT
            '''
            global telnet_session
            global connected
    
            for _ in range(Retry):
                try:
                    telnet_session = telnetlib.Telnet(HOST, PORT, timeout=5)
                    connected = True
                    self.event_generate("<<StatusChange>>")
                    break
                except:
                    connected = False
                    self.event_generate("<<StatusChange>>")
                    continue
    
    
        def periodicCall(self):
            """
            Check every 10 sec if there is something new in the queue.
            This is actually Telnet connection status check
            """
            self.processIncoming()
            if not self.running:
                # This is the brutal stop of the system. You may want to do
                # some cleanup before actually shutting it down.
                import sys
                sys.exit(1)
            self.after(10000, self.periodicCall)
    
    
        def processIncoming(self):
            """
            Handle all the messages currently in the queue (if any).
            """
    #         global connected
    
            while self.status_queue.qsize():
                try:
                    msg = self.status_queue.get(0)
                    # Check contents of message and do what it says
                    # As a test, I simply print it
                    print(msg)
    #                 if not connected:
    #                     self.connect_to_uut()
                except queue.Empty:
                    pass
    
        def workerThread(self):
            """
            This is where we handle the asynchronous I/O.
            """
            global telnet_session
            global connected
    
            while self.running:
                time.sleep(5)
                try:
                    telnet_session.sock.send(telnetlib.IAC + telnetlib.NOP)
                    connected = True
                    msg = 'Connected'
                except:
                    connected = False
                    msg = 'Disconnected'       #The Actual Status of the Telnet session
                    self.event_generate("<<StatusChange>>")
                    if not connected:
                        self.connect_to_uut()
    
                self.status_queue.put(msg)
    
    
    
    class MainScreen(tk.Frame):
        def __init__(self, parent, controller):
            tk.Frame.__init__(self,parent)
    
            self.Button_Font_Style1 = font.Font(family='Helvetica', size=30, weight='bold')
            self.Status_BasicStyle = font.Font(family='Helvetica', size=10, weight='bold')
    
            self.my_string_var = tk.StringVar()
    
            self.button1 = tk.Button(self, text="PROGRAM 1",
                                command=lambda: controller.show_frame(FirstScreen),
                                 width=30, font=self.Button_Font_Style1, bd=5)
            self.button1.pack(pady=8)
    
            self.button2 = tk.Button(self, text="PROGRAM 2",
                                command=lambda: controller.show_frame(FirstScreen),
                                 width=30, font=self.Button_Font_Style1, bd=5)
            self.button2.pack(pady=8)
    
            self.button3 = tk.Button(self, text="PROGRAM 3",
                                command=lambda: controller.show_frame(FirstScreen),
                                 width=30, font=self.Button_Font_Style1, bd=5)
            self.button3.pack(pady=8)
    
            self.button4 = tk.Button(self, text="PROGRAM 4",
                                command=lambda: controller.show_frame(SecondScreen),
                                width=30, font=self.Button_Font_Style1, bd=5) 
            self.button4.pack(pady=8)
    
            self.button5 = tk.Button(self, text="PROGRAM FAN ",
                                command=lambda: controller.show_frame(FirstScreen),
                                width=30, font=self.Button_Font_Style1, bd=5) 
            self.button5.pack(pady=8)
    
            self.status = tk.Label(self, textvariable=self.my_string_var, bd=2, relief=tk.SUNKEN, anchor=tk.W, font=self.Status_BasicStyle, fg="black")        
            self.my_string_var.set('Connecting...')
            self.status.pack(side="bottom" , fill="x")
    
    class FirstScreen(tk.Frame):
        def __init__(self, parent, controller):
            tk.Frame.__init__(self,parent)
    
            self.valid_string_color = "springgreen3"
            self.invalid_string_color = "red2"
    
            self.main_frame = tk.Frame(self)
    
            self.Button_Font_Style1 = font.Font(family='Helvetica', size=20, weight='bold')
            self.Lable_Font_Style1 = font.Font(family='Helvetica', size=20, weight='bold')
            self.Status_BasicStyle = font.Font(family='Helvetica', size=10, weight='bold')
    
            self.SN_Label = tk.Label(self.main_frame, text="Serial Number", font=self.Lable_Font_Style1)
            self.SN_Label.grid(row=0, column=0, pady=10)     # Y axis padding was added only to the label. This padding effects the whole line
    
            self.SN_field = tk.Text(self.main_frame, height=1, width=30, font=self.Lable_Font_Style1)
            self.SN_field.grid(row=0, column=1)
    
            self.PN_Label = tk.Label(self.main_frame, text="Part Number", font=self.Lable_Font_Style1)
            self.PN_Label.grid(row=1, column=0, pady=10)     # Y axis padding was added only to the label. This padding effects the whole line
    
            self.PN_field = tk.Text(self.main_frame, height=1, width=30, font=self.Lable_Font_Style1)
            self.PN_field.grid(row=1, column=1)
    
            self.HwVer_Label = tk.Label(self.main_frame, text="HW Version", font=self.Lable_Font_Style1)
            self.HwVer_Label.grid(row=2, column=0, pady=10)     # Y axis padding was added only to the label. This padding effects the whole line
    
            self.HwVer_field = tk.Text(self.main_frame, height=1, width=30, font=self.Lable_Font_Style1)
            self.HwVer_field.grid(row=2, column=1)
    
            self.button2 = tk.Button(self.main_frame, text="Burn",
                                font=self.Button_Font_Style1, bd=5)
            self.button2.grid(row=3, columnspan=2, pady=(20,0))
    
            self.main_frame.pack()
    
            self.my_string_var = tk.StringVar()
    
            self.status = tk.Label(self, textvariable=self.my_string_var, bd=2, relief=tk.SUNKEN, anchor=tk.W, font=self.Status_BasicStyle, fg='black')        
            self.my_string_var.set('Connecting...')
            self.status.pack(side="bottom" , fill="x")
            self.status.bind("<<StatusChange>>", self.statuschange)     # React to the status change event and change the status label accordingly
    
            self.button1 = tk.Button(self, text="Main Menu",
                                command=lambda: controller.show_frame(MainScreen),
                                font=self.Button_Font_Style1, bd=5)
            self.button1.pack(side="bottom", pady=(0,20))
    
    
        def statuschange(self):
            global connected
    
            if connected:
                self.my_string_var.set('Connected')
                self.status.config(font=self.Status_ConnectedStyle, fg='springgreen3')
            else:
                self.my_string_var.set('Disonnected')
                self.status.config(font=self.Status_DisconnectedStyle, fg='red2')
    
    class SecondScreen(tk.Frame):    
        def __init__(self, parent, controller):
            tk.Frame.__init__(self,parent)
    
            self.valid_string_color = "springgreen3"
            self.invalid_string_color = "red2"
    
            self.main_frame = tk.Frame(self)
    
            self.Button_Font_Style1 = font.Font(family='Helvetica', size=20, weight='bold')
            self.Lable_Font_Style1 = font.Font(family='Helvetica', size=20, weight='bold')
            self.Status_BasicStyle = font.Font(family='Helvetica', size=5, weight='bold')
    
            self.SN_Label = tk.Label(self.main_frame, text="Serial Number", font=self.Lable_Font_Style1)
            self.SN_Label.grid(row=0, column=0, pady=10)     # Y axis padding was added only to the label. This padding effects the whole line
    
            self.SN_field = tk.Text(self.main_frame, height=1, width=30, font=self.Lable_Font_Style1)
            self.SN_field.grid(row=0, column=1)
    
            self.PN_Label = tk.Label(self.main_frame, text="Part Number", font=self.Lable_Font_Style1)
            self.PN_Label.grid(row=1, column=0, pady=10)     # Y axis padding was added only to the label. This padding effects the whole line
    
            self.PN_field = tk.Text(self.main_frame, height=1, width=30, font=self.Lable_Font_Style1)
            self.PN_field.grid(row=1, column=1)
    
            self.HwVer_Label = tk.Label(self.main_frame, text="HW Version", font=self.Lable_Font_Style1)
            self.HwVer_Label.grid(row=2, column=0, pady=10)     # Y axis padding was added only to the label. This padding effects the whole line
    
            self.HwVer_field = tk.Text(self.main_frame, height=1, width=30, font=self.Lable_Font_Style1)
            self.HwVer_field.grid(row=2, column=1)
    
            self.button2 = tk.Button(self.main_frame, text="Burn",
                                font=self.Button_Font_Style1, bd=5)
            self.button2.grid(row=3, columnspan=2, pady=(20,0))
    
            self.main_frame.pack()
    
            self.my_string_var = tk.StringVar()
    
            self.status = tk.Label(self, textvariable=self.my_string_var, bd=2, relief=tk.SUNKEN, anchor=tk.W, font=self.Status_BasicStyle, fg="black")        
            self.my_string_var.set('Connecting...')
            self.status.pack(side="bottom" , fill="x")
            self.status.bind("<<StatusChange>>", self.statuschange)     # React to the status change event and change the status label accordingly
    
            self.button1 = tk.Button(self, text="Main Menu",
                                command=lambda: controller.show_frame(MainScreen),
                                font=self.Button_Font_Style1, bd=5)
            self.button1.pack(side="bottom", pady=(0,20))
    
    
        def statuschange(self):
            global connected
    
            if connected:
                self.my_string_var.set('Connected')
                self.status.config(font=self.Status_ConnectedStyle, fg='springgreen3')
            else:
                self.my_string_var.set('Disonnected')
                self.status.config(font=self.Status_DisconnectedStyle, fg='red2')
    
    
    def main():
        app = BurningApp()        
        app.mainloop()
    
    if __name__ == '__main__':
        main()
    

    顺便说一句,我知道我缺少在MainScreen类中更新状态栏的方法

    正如我在这里承诺的是最精简的代码。我留下了一些“框架”,只是为了能够看到每个框架显示正确的状态,我删除了不相关的字段

    from tkinter import font, ttk
    import tkinter as tk
    from re import match
    
    import telnetlib
    import threading
    import queue
    
    import time
    
    LARGE_FONT= ("Verdana", 12)
    Current_PN = '123456789'    # This global ver is used for the purpose of automatic PN fill
    
    HOST = '10.0.1.235'
    PORT = 23
    
    telnet_session = None       # After I create the Telnet session I will keep track with this variable
    connected = False
    
    class BurningApp(tk.Tk):
        def __init__(self, *args, **kwargs):
    
            tk.Tk.__init__(self, *args, **kwargs)
            container = tk.Frame(self)
    
            container.pack(side="top", fill="both", expand = True)
    
            container.grid_rowconfigure(0, weight=1)
            container.grid_columnconfigure(0, weight=1)
    
            self.title('Burning App')  #handling the application's Window title
    
            w = 1000    # Windows width
            h = 600     # Windows height
    
            ws = self.winfo_screenwidth()       # Screen resolution width
            hs = self.winfo_screenheight()     # Screen resolution height
    
            # w = ws * 0.8    # Fit the GUI width to 80% percent of the screen
            # h = hs * 0.8    # Fit the GUI height to 80% percent of the screen
    
            x = (ws/2) - (w/2)      # X coordinate for the purpose of GUI placement
            y = (hs/2) - (h/2)      # X coordinate for the purpose of GUI placement
    
    
    
            self.resizable(width=False, height=False)
    
            self.geometry('%dx%d+%d+%d'%(w,h,x,y))
    
            self.frames = {}
    
            for F in (MainScreen, FirstScreen, SecondScreen):
    
                frame = F(container, self)
    
                self.frames[F] = frame
    
                frame.grid(row=0, column=0, sticky="nsew")
    
            self.show_frame(MainScreen)
    
            # Start the Telnet session
            self.connect_to_uut()
    
            # Create the queue that will hold the status
            self.status_queue = queue.Queue()
    
            # Set up the thread to do asynchronous I/O
            self.running = 1    # True
            self.thread = threading.Thread(target=self.workerThread)
            self.thread.start()
    
            # Start the periodic call in the GUI to check if the queue contains
            # anything
            self.periodicCall()
    
        def show_frame(self, cont):
            '''
            This function is being used in order to raise a frame on demand
            '''
            frame = self.frames[cont]
            frame.tkraise()
    
    
        def connect_to_uut(self, Retry=5):
            '''
            This functions is used for the purpose of connecting to the UUT
            '''
            global telnet_session
            global connected
    
            for _ in range(Retry):
                try:
                    telnet_session = telnetlib.Telnet(HOST, PORT, timeout=5)
                    connected = True
                    self.event_generate("<<StatusChange>>")
                    break
                except:
                    connected = False
                    self.event_generate("<<StatusChange>>")
                    continue
    
    
        def periodicCall(self):
            """
            Check every 10 sec if there is something new in the queue.
            This is actually Telnet connection status check
            """
            self.processIncoming()
            if not self.running:
                # This is the brutal stop of the system. You may want to do
                # some cleanup before actually shutting it down.
                import sys
                sys.exit(1)
            self.after(10000, self.periodicCall)
    
    
        def processIncoming(self):
            """
            Handle all the messages currently in the queue (if any).
            """
    #         global connected
    
            while self.status_queue.qsize():
                try:
                    msg = self.status_queue.get(0)
                    # Check contents of message and do what it says
                    # As a test, I simply print it
                    print(msg)
    #                 if not connected:
    #                     self.connect_to_uut()
                except queue.Empty:
                    pass
    
        def workerThread(self):
            """
            This is where we handle the asynchronous I/O.
            """
            global telnet_session
            global connected
    
            while self.running:
                time.sleep(5)
                try:
                    telnet_session.sock.send(telnetlib.IAC + telnetlib.NOP)
                    connected = True
                    msg = 'Connected'
                except:
                    connected = False
                    msg = 'Disconnected'       #The Actual Status of the Telnet session
                    self.event_generate("<<StatusChange>>")
                    if not connected:
                        self.connect_to_uut()
    
                self.status_queue.put(msg)
    
    
    
    class MainScreen(tk.Frame):
        def __init__(self, parent, controller):
            tk.Frame.__init__(self,parent)
    
            self.Button_Font_Style1 = font.Font(family='Helvetica', size=30, weight='bold')
            self.Status_BasicStyle = font.Font(family='Helvetica', size=10, weight='bold')
    
            self.my_string_var = tk.StringVar()
    
            self.button1 = tk.Button(self, text="PROGRAM 1",
                                command=lambda: controller.show_frame(FirstScreen),
                                 width=30, font=self.Button_Font_Style1, bd=5)
            self.button1.pack(pady=8)
    
            self.button2 = tk.Button(self, text="PROGRAM 2",
                                command=lambda: controller.show_frame(FirstScreen),
                                 width=30, font=self.Button_Font_Style1, bd=5)
            self.button2.pack(pady=8)
    
            self.status = tk.Label(self, textvariable=self.my_string_var, bd=2, relief=tk.SUNKEN, anchor=tk.W, font=self.Status_BasicStyle, fg="black")        
            self.my_string_var.set('Connecting...')
            self.status.pack(side="bottom" , fill="x")
    
    class FirstScreen(tk.Frame):
        def __init__(self, parent, controller):
            tk.Frame.__init__(self,parent)
    
            self.Button_Font_Style1 = font.Font(family='Helvetica', size=20, weight='bold')
            self.Status_BasicStyle = font.Font(family='Helvetica', size=10, weight='bold')
            self.my_string_var = tk.StringVar()
    
            self.status = tk.Label(self, textvariable=self.my_string_var, bd=2, relief=tk.SUNKEN, anchor=tk.W, font=self.Status_BasicStyle, fg='black')        
            self.my_string_var.set('Connecting...')
            self.status.pack(side="bottom" , fill="x")
            self.status.bind("<<StatusChange>>", self.statuschange)     # React to the status change event and change the status label accordingly
    
            self.button1 = tk.Button(self, text="Main Menu",
                                command=lambda: controller.show_frame(MainScreen),
                                font=self.Button_Font_Style1, bd=5)
            self.button1.pack(side="bottom", pady=(0,20))
    
    
        def statuschange(self):
            global connected
    
            if connected:
                self.my_string_var.set('Connected')
                self.status.config(font=self.Status_ConnectedStyle, fg='springgreen3')
            else:
                self.my_string_var.set('Disonnected')
                self.status.config(font=self.Status_DisconnectedStyle, fg='red2')
    
    class SecondScreen(tk.Frame):    
        def __init__(self, parent, controller):
            tk.Frame.__init__(self,parent)
    
            self.Button_Font_Style1 = font.Font(family='Helvetica', size=20, weight='bold')
            self.Status_BasicStyle = font.Font(family='Helvetica', size=5, weight='bold')
            self.my_string_var = tk.StringVar()
    
            self.status = tk.Label(self, textvariable=self.my_string_var, bd=2, relief=tk.SUNKEN, anchor=tk.W, font=self.Status_BasicStyle, fg="black")        
            self.my_string_var.set('Connecting...')
            self.status.pack(side="bottom" , fill="x")
            self.status.bind("<<StatusChange>>", self.statuschange)     # React to the status change event and change the status label accordingly
    
            self.button1 = tk.Button(self, text="Main Menu",
                                command=lambda: controller.show_frame(MainScreen),
                                font=self.Button_Font_Style1, bd=5)
            self.button1.pack(side="bottom", pady=(0,20))
    
    
        def statuschange(self):
            global connected
    
            if connected:
                self.my_string_var.set('Connected')
                self.status.config(font=self.Status_ConnectedStyle, fg='springgreen3')
            else:
                self.my_string_var.set('Disonnected')
                self.status.config(font=self.Status_DisconnectedStyle, fg='red2')
    
    
    def main():
        app = BurningApp()        
        app.mainloop()
    
    if __name__ == '__main__':
        main()
    
    2 回复  |  直到 6 年前
        1
  •  2
  •   Mike    6 年前

    这些调整可以解决您的问题:

    self.status.config(font=self.Status_ConnectedStyle,
    text=self.my_string_var,fg='springgreen3')
    
        2
  •  0
  •   Moshe S.    6 年前

    我在每个帧中添加了以下方法。此方法的目的是定期检查保存Telnet会话状态(connect)的标志。所以我仍然使用相同的线程:主GUI线程和另一个维护Telnet会话的线程。为了“激活”这个方法,我在某个帧被提升后启动它(“frame\u was\u raised”方法)

    def update_statusbar(self):
            '''Poll the Telnet Session Status for the purpose of update'''
            global connected
    
            if connected:
                self.my_string_var.set('Connected')
                self.status.config(fg='springgreen3')
            else:
                self.my_string_var.set('Disconnected')
                self.status.config(fg='red2')
    
            self.after(2000, self.update_statusbar)