英文:
How to update data individually on tkinter window?
问题
我正在创建一个GUI来显示来自IP地址的温度和湿度。这是我的主窗口,
import tkinter as tk
from tkinter import *
from PIL import ImageTk, Image
import requests
from tkinter import messagebox
class MainApplication(tk.Tk):
def __init__(self):
super().__init__()
global button_counter
global button_position
button_counter = 0
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.frame = tk.Frame(self, height=50, width=500)
self.frame.pack()
logo_image = Image.open('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=LEFT)
self.title = tk.Label(self.frame,
text="ESP32 Temperature & Humidity Measurement",
fg="DodgerBlue2",
font='Times 25 bold')
self.title.pack(side=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.status_var = tk.StringVar()
self.button1 = tk.Button(self.status_frame,
text="+",
bg="DodgerBlue2",
fg="white",
font='Helvetica 20 bold',
relief=FLAT,
command=self.add_device)
self.button1.pack(side=tk.RIGHT, padx=5)
每当我点击**+**符号时,它会添加带有ID和IP地址的设备。这是创建另一个窗口以添加设备的代码,
def add_device(self):
self.window = tk.Tk()
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=lambda:
[self.add_command(), self.get_ip_data(),
self.show_message()])
self.add_button.place(x=350, y=200)
每当我添加设备时,它从IP获取数据并将其更新到按钮中,该按钮将出现在主屏幕上。这是添加新设备时创建按钮的代码,
def add_command(self):
global button_counter
global button_position
if button_counter > len(button_position):
return
x, y = button_position[button_counter]
self.result_button = tk.Button(self,
text="",
font="Times 15",
bg="SkyBlue1",
fg="black",
highlightthickness=2,
command=lambda:
[self.get_ip_data(),
self.update_data(),
self.show_details()])
self.result_button.place(x=x, y=y)
button_counter += 1
从IP地址获取数据的代码如下,
def get_ip_data(self):
id = self.id_entry.get()
ip = self.ip_entry.get()
print(f"The id number is {id}")
print(f"The ip address is {ip}")
response = requests.get(f"http://{ip}/")
data = response.json()
print(data)
temp = round(data['temperature'], 2)
humi = round(data['humidity'], 2)
id_json = data['id']
print(f"temperature for id {id} is {temp}")
print(f"humidity for id {id} is {humi}")
if int(id_json) == int(id):
self.result_button.config(text=f"Device {id}\nTemperature: {temp}°C\nHumidity: {humi} %")
else:
tk.messagebox.showerror("Error!", "Invalid ID")
这是每隔2秒自动更新数据的代码,
def update_data(self):
self.after(2000, self.get_ip_data)
最后,这是创建新窗口以显示结果的温度和湿度的代码,
def show_details(self):
self.new_window = tk.Tk()
self.new_window.geometry("500x500")
self.new_window.config(bg="LightSkyBlue1")
self.new_window.resizable(0,0)
my_text = self.result_button['text']
self.label = tk.Label(self.new_window,
text=my_text,
bg="LightSkyBlue1",
fg="black",
font="Times 20 bold")
self.label.place(relx=0.5, rely=0.5, anchor=CENTER)
print(my_text)
我的需求是每当我点击result_button
时,新窗口应该打开并显示第一个设备的详细信息。如果我添加两个设备,那么每当点击第二个设备的result_button
时,它应该显示第二个设备的详细信息。我的问题是,如果我点击第一个设备的result_button
,
英文:
I am creating GUI to show Temperature & Humidity from ip address. This is my mainwindow,
import tkinter as tk
from tkinter import *
from PIL import ImageTk, Image
import requests
from tkinter import messagebox
class MainApplication(tk.Tk):
def __init__(self):
super().__init__()
global button_counter
global button_position
button_counter = 0
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.frame = tk.Frame(self, height=50, width=500)
self.frame.pack()
logo_image = Image.open('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=LEFT)
self.title = tk.Label(self.frame,
text="ESP32 Temperature & Humidity Measurement",
fg="DodgerBlue2",
font='Times 25 bold')
self.title.pack(side=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.status_var = tk.StringVar()
self.button1 = tk.Button(self.status_frame,
text="+",
bg="DodgerBlue2",
fg="white",
font='Helvetica 20 bold',
relief=FLAT,
command=self.add_device)
self.button1.pack(side=tk.RIGHT, padx=5)
Here whenever I click + symbol it adds devices with id and ip address. This is the code to create another window to add device,
def add_device(self):
self.window = tk.Tk()
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=lambda:
[self.add_command(), self.get_ip_data(),
self.show_message()])
self.add_button.place(x=350, y=200)
and whenever I add devices it gets the data from ip and update into a button which will be appeared on the mainscreen. This is the code to create buttons when I add new device,
def add_command(self):
global button_counter
global button_position
if button_counter > len(button_position):
return
x, y = button_position[button_counter]
self.result_button = tk.Button(self,
text="",
font="Times 15",
bg="SkyBlue1",
fg="black",
highlightthickness=2,
command=lambda:
[self.get_ip_data(),
self.update_data(),
self.show_details()])
self.result_button.place(x=x, y=y)
button_counter += 1
The code to get data from ip address is,
def get_ip_data(self):
id = self.id_entry.get()
ip = self.ip_entry.get()
print(f"The id number is {id}")
print(f"The ip address is {ip}")
response = requests.get(f"http://{ip}/")
data = response.json()
print(data)
temp = round(data['temperature'], 2)
humi = round(data['humidity'], 2)
id_json = data['id']
print(f"temperature for id {id} is {temp}")
print(f"humidity for id {id} is {humi}")
if int(id_json) == int(id):
self.result_button.config(text=f"Device
{id}\nTemperature: {temp°C\n
Humidity: {humi} %")
else:
tk.messagebox.showerror("Error!", "Invalid
ID")
This is the function to update data after 2 seconds,
def update_data(self):
self.after(2000, self.get_ip_data)
and finally this is the code that creates new window to display the temperature and humidity of result.
def show_details(self):
self.new_window = tk.Tk()
self.new_window.geometry("500x500")
self.new_window.config(bg="LightSkyBlue1")
self.new_window.resizable(0,0)
my_text = self.result_button['text']
self.label = tk.Label(self.new_window,
text=my_text,
bg="LightSkyBlue1",
fg="black",
font="Times 20 bold")
self.label.place(relx=0.5, rely=0.5, anchor=CENTER)
print(my_text)
My need is whenever I click result_button then new window should be open and show the details of first device. If I add two device means whenever click result_button of second device means that shows the details the second device. My requirement is for example I add 10 devices means if I click device 1 button then it shows the details. Like that each device shows the details of temperature and humidity individually.
My problem is If I click result_button for first device means it shows details of first device but if i click result_button for second device means both device buttons shows same details. And also the data from ip does not update after 2 seconds automatically.
Can anyone suggest me to solve this problem?
答案1
得分: 1
问题是由于在所有设备按钮中都使用相同的实例变量self.result_button
引起的,因此它将始终引用最后添加的按钮。
对于您当前的设计,要克服这个问题有点困难,所以我建议创建一个自定义按钮来将所有相关的功能(如get_ip_data()
,show_details()
等)组合到该类中。
此外,您的代码中还有一些应该避免的不良实践:
- 使用通配符导入
- 多次实例化
Tk()
,应使用Toplevel()
用于子窗口 - 在lambda内部调用多个函数
- 使用不必要的全局变量,可以使用实例变量代替的
button_counter
和button_position
以下是自定义按钮类:
# 您可以使用任何您想要的名称
class DeviceButton(tk.Button):
def __init__(self, master, id, ip):
super().__init__(master, font="Times 15", bg="SkyBlue1", fg="black",
highlightthickness=2, command=self.show_details)
self.id = id
self.ip = ip
self.get_ip_data()
# 其他代码...
以下是修改后的MainApplication
类,使用新的自定义按钮类:
class MainApplication(tk.Tk):
# 其他代码...
def add_device(self):
# use tk.Toplevel instead of tk.Tk
self.window = tk.Toplevel()
self.window.title("Add new device")
self.window.geometry("700x400")
self.window.resizable(0, 0)
# 其他代码...
def add_command(self):
if self.button_counter > len(self.button_position):
return
x, y = self.button_position[self.button_counter]
# use the new custom button class
result_button = DeviceButton(self, self.id_entry.get(), self.ip_entry.get())
result_button.place(x=x, y=y)
self.button_counter += 1
# 其他代码...
请注意,您可能需要修改上述类以适应您当前的代码。
英文:
The issue is caused by using same instance variable self.result_button
for all the device buttons, so it will always reference the last added button.
It is a bit hard to overcome the issue for your current design, so I would suggest to create a custom button to group all the related functions, like get_ip_data()
, show_details()
, etc., into that class.
Also there are few bad practices you should avoid in your code:
- using wildcard import
- multiple instances of
Tk()
, useToplevel()
for child windows - call multiple functions inside lambda
- using not necessary global variables,
button_counter
andbutton_position
which can use instance variables instead
Below is the custom button class:
# you can use whatever name you want
class DeviceButton(tk.Button):
def __init__(self, master, id, ip):
super().__init__(master, font="Times 15", bg="SkyBlue1", fg="black",
highlightthickness=2, command=self.show_details)
self.id = id
self.ip = ip
self.get_ip_data()
def get_ip_data(self):
print(f"The id number is {self.id}")
print(f"The ip address is {self.ip}")
response = requests.get(f"http://{self.ip}/")
data = response.json()
print(data)
temp = round(data['temperature'], 2)
humi = round(data['humidity'], 2)
id_json = data['id']
print(f"temperature for id {self.id} is {temp}")
print(f"humidity for id {self.id} is {humi}")
if int(id_json) == int(self.id):
self.config(text=f"Device {self.id}\nTemperature: {temp}°C\nHumidity: {humi}%")
else:
tk.messagebox.showerror("Error!", f"Invalid ID")
def show_details(self):
# use Toplevel instead of Tk
self.new_window = tk.Toplevel()
self.new_window.geometry("500x500")
self.new_window.config(bg="LightSkyBlue1")
self.new_window.resizable(0,0)
self.label = tk.Label(self.new_window,
text=self["text"],
bg="LightSkyBlue1",
fg="black",
font="Times 20 bold")
self.label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
print(self["text"])
Below is the modified MainApplication
class to use the new custom button class:
class MainApplication(tk.Tk):
def __init__(self):
super().__init__()
# change to use instance variables instead
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.frame = tk.Frame(self, height=50, width=500)
self.frame.pack()
logo_image = Image.open('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)
# rename self.title to self.title_label because Tk.title is a function inside the class Tk
self.title_label = tk.Label(self.frame,
text="ESP32 Temperature & Humidity Measurement",
fg="DodgerBlue2",
font='Times 25 bold')
self.title_label.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):
# use tk.Toplevel instead of tk.Tk
self.window = tk.Toplevel()
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_command)
self.add_button.place(x=350, y=200)
def add_command(self):
if self.button_counter > len(self.button_position):
return
x, y = self.button_position[self.button_counter]
# use the new custom button class
result_button = DeviceButton(self, self.id_entry.get(), self.ip_entry.get())
result_button.place(x=x, y=y)
self.button_counter += 1
Note that you may need to modify the above classes to cope with your current code.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论