如何在tkinter窗口上单独更新数据?

huangapple go评论63阅读模式
英文:

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_counterbutton_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(), use Toplevel() for child windows
  • call multiple functions inside lambda
  • using not necessary global variables, button_counter and button_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.

huangapple
  • 本文由 发表于 2023年7月20日 17:18:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/76728385.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定