Tkinter单设备温湿度数据独立展示与自动更新问题求解
ESP32温湿度监控Tkinter GUI修复方案
现存问题
- 点击不同设备的按钮时,所有按钮显示相同数据
- 设备数据无法按预期每2秒自动刷新
问题分析
- 全局变量覆盖:用
self.result_button存储设备按钮,新添加的设备会覆盖这个变量,导致所有操作都指向最后一个设备。 - 设备信息未绑定:获取数据的函数直接从添加窗口输入框读取内容,没有将ID、IP与对应设备按钮关联,无法区分不同设备。
- 刷新逻辑无针对性:自动刷新函数没有绑定具体设备信息,无法单独更新每个设备的数据。
- 多窗口错误:添加设备和详情窗口使用
tk.Tk()创建新主窗口,违反Tkinter单主窗口原则,可能引发异常。
修复后的完整代码
import tkinter as tk from tkinter import messagebox, Toplevel from PIL import ImageTk, Image import requests class MainApplication(tk.Tk): def __init__(self): super().__init__() self.button_counter = 0 # 替换全局变量为实例变量 self.button_position = [ (100, 100), (300, 100), (500, 100), (700, 100), (900, 100), (1100, 100), (1300, 100), (100, 200), (300, 200), (500, 200), (700, 200), (900, 200), (1100, 200), (1300, 200), (100, 300), (300, 300), (500, 300), (700, 300), (900, 300), (1100, 300), (1300, 300), (100, 400), (300, 400), (500, 400), (700, 400), (900, 400), (1100, 400), (1300, 400), (100, 500), (300, 500), (500, 500), (700, 500), (900, 500), (1100, 500), (1300, 500), (100, 600), (300, 600), (500, 600), (700, 600), (900, 600), (1100, 600), (1300, 600) ] self.devices = [] # 存储所有设备信息:{id, ip, button, refresh_id, last_data} self.frame = tk.Frame(self, height=50, width=500) self.frame.pack() # 加载logo(注意路径转义或用原始字符串) logo_image = Image.open(r'C:\Users\Kuralmozhi.R\Downloads\icons8-temperature-65.png') test = ImageTk.PhotoImage(logo_image) self.logo = tk.Label(self.frame, image=test) self.logo.image = test self.logo.pack(side=tk.LEFT) self.title = tk.Label(self.frame, text="ESP32 Temperature & Humidity Measurement", fg="DodgerBlue2", font='Times 25 bold') self.title.pack(side=tk.LEFT) self.status_frame = tk.Frame(self, bd=1, bg="DodgerBlue2", relief=tk.SUNKEN) self.status_frame.pack(side=tk.BOTTOM, fill=tk.X) self.button1 = tk.Button(self.status_frame, text="+", bg="DodgerBlue2", fg="white", font='Helvetica 20 bold', relief=tk.FLAT, command=self.add_device) self.button1.pack(side=tk.RIGHT, padx=5) def add_device(self): # 使用Toplevel创建子窗口,而非新主窗口 self.window = Toplevel(self) self.window.title("Add new device") self.window.geometry("700x400") self.window.resizable(0, 0) self.id_label = tk.Label(self.window, text="Enter ID Number: ", font='Helvetica 12 bold') self.id_label.place(x=200, y=100) self.id_entry = tk.Entry(self.window) self.id_entry.place(x=350, y=100) self.ip_label = tk.Label(self.window, text="Enter IP Address: ", font='Helvetica 12 bold') self.ip_label.place(x=200, y=150) self.ip_entry = tk.Entry(self.window) self.ip_entry.place(x=350, y=150) self.add_button = tk.Button(self.window, text="Add", bg="DodgerBlue2", fg="white", font='Helvetica 12 bold', command=self.add_device_confirm) self.add_button.place(x=350, y=200) def add_device_confirm(self): device_id = self.id_entry.get().strip() device_ip = self.ip_entry.get().strip() if not device_id or not device_ip: messagebox.showerror("Error", "ID和IP不能为空") return # 创建设备按钮 if self.button_counter >= len(self.button_position): messagebox.showwarning("Warning", "设备数量已达上限") self.window.destroy() return x, y = self.button_position[self.button_counter] self.button_counter += 1 result_button = tk.Button(self, text=f"Device {device_id}\n加载中...", font="Times 15", bg="SkyBlue1", fg="black", highlightthickness=2) result_button.place(x=x, y=y) # 存储设备信息,绑定按钮与ID、IP device_info = { 'id': device_id, 'ip': device_ip, 'button': result_button, 'refresh_id': None, 'last_data': f"Device {device_id}\n加载中..." } self.devices.append(device_info) # 设置按钮点击事件,绑定当前设备信息 result_button.config(command=lambda d=device_info: [self.get_ip_data(d), self.show_details(d)]) # 首次获取数据并启动自动刷新 self.get_ip_data(device_info) self.start_auto_refresh(device_info) self.window.destroy() messagebox.showinfo("Success", "设备添加成功") def get_ip_data(self, device_info): device_id = device_info['id'] device_ip = device_info['ip'] button = device_info['button'] try: response = requests.get(f"http://{device_ip}/", timeout=5) response.raise_for_status() data = response.json() temp = round(data['temperature'], 2) humi = round(data['humidity'], 2) id_json = str(data['id']) if id_json == device_id: button_text = f"Device {device_id}\nTemperature: {temp}°C\nHumidity: {humi} %" button.config(text=button_text) device_info['last_data'] = button_text # 存储最新数据用于详情展示 else: messagebox.showerror("Error!", f"设备{device_id}返回ID不匹配") except Exception as e: error_text = f"Device {device_id}\n连接失败" button.config(text=error_text) device_info['last_data'] = error_text messagebox.showerror("Error!", f"获取设备{device_id}数据失败: {str(e)}") def start_auto_refresh(self, device_info): # 先取消之前的刷新任务(如果有) if device_info['refresh_id']: self.after_cancel(device_info['refresh_id']) # 定时刷新当前设备数据 refresh_id = self.after(2000, lambda: [self.get_ip_data(device_info), self.start_auto_refresh(device_info)]) device_info['refresh_id'] = refresh_id def show_details(self, device_info): # 使用Toplevel创建详情窗口 new_window = Toplevel(self) new_window.geometry("500x500") new_window.config(bg="LightSkyBlue1") new_window.resizable(0, 0) new_window.title(f"设备{device_info['id']}详情") display_text = device_info.get('last_data', f"Device {device_info['id']}\n暂无数据") self.label = tk.Label(new_window, text=display_text, bg="LightSkyBlue1", fg="black", font="Times 20 bold") self.label.place(relx=0.5, rely=0.5, anchor=tk.CENTER) if __name__ == "__main__": app = MainApplication() app.geometry("1400x700") app.mainloop()
关键修改说明
- 设备信息管理:新增
self.devices列表存储每个设备的ID、IP、按钮组件、刷新任务ID和最新数据,避免全局变量覆盖问题。 - 绑定设备数据:创建按钮时将设备信息通过lambda绑定到点击事件,确保每个按钮操作对应自身设备。
- 修复多窗口问题:所有子窗口改用
Toplevel创建,遵循Tkinter单主窗口设计原则。 - 针对性自动刷新:每个设备独立维护定时刷新任务,调用
after_cancel避免重复刷新,确保每2秒更新自身数据。 - 异常处理:添加网络请求异常捕获,避免单个设备连接失败导致整个GUI崩溃。
内容的提问来源于stack exchange,提问作者Kuralmozhi
相关产品推荐
相关产品推荐

