【问题标题】:Python Tkinter, Display Live DataPython Tkinter,显示实时数据
【发布时间】:2019-05-23 12:16:55
【问题描述】:

我想在 tkinter 的 GUI 中显示实时数据。我得到的数据包含两个整数[current, voltage]list。我每秒都在获取新数据。

我设法创建了一个 GUI,现在我想知道如何在 GUI Label 小部件(python tkinter)中显示数据并动态更新标签。大家有什么建议

到目前为止,这是我的代码:

#data getting is a list eg. [10, 12]
from tkinter import *
import tkinter.font


#main Window using Tk
win = Tk()

win.title("v1.0")
win.geometry('800x480')
win.configure(background='#CD5C5C')

#Labels
voltage = Label(win, text = "voltage")
voltage.place(x=15, y=100)

current = Label(win, text = "current")
current.place(x=15, y=200)

#display measured values
#how to display here !!!
currentValues = Label(win, text = "want to display somewhere like this")
currentValues.place(x=200, y=100)

voltageValues = Label(win, text = "want to display somewhere like this")
voltageValues.place(x=200, y=200)
mainloop()

【问题讨论】:

    标签: python python-3.x user-interface tkinter


    【解决方案1】:

    如果您想绘制实时数据并避免使用其他库为您执行此操作,您可能会发现以下内容是创建您自己的图表的启发性起点。该示例在评估标准库中的math.sin 函数时绘制了一整圈值。该代码会根据需要考虑自动采样、调整大小和更新,并且应该具有相当的响应能力。

    #! /usr/bin/env python3
    import math
    import threading
    import time
    import tkinter.ttk
    import uuid
    from tkinter.constants import EW, NSEW, SE
    
    
    class Application(tkinter.ttk.Frame):
        FPS = 10  # frames per second used to update the graph
        MARGINS = 10, 10, 10, 10  # internal spacing around the graph
    
        @classmethod
        def main(cls):
            tkinter.NoDefaultRoot()
            root = tkinter.Tk()
            root.title('Tkinter Graphing')
            # noinspection SpellCheckingInspection
            root.minsize(640, 480)  # VGA (NTSC)
            cls(root).grid(sticky=NSEW)
            root.grid_rowconfigure(0, weight=1)
            root.grid_columnconfigure(0, weight=1)
            root.mainloop()
    
        def __init__(self, master=None, **kw):
            super().__init__(master, **kw)
            self.display = tkinter.Canvas(self, background='white')
            self.display.bind('<Configure>', self.draw)
            self.start = StatefulButton(self, 'Start Graphing', self.start_graph)
            self.grip = tkinter.ttk.Sizegrip(self)
            self.grid_widgets(padx=5, pady=5)
            self.data_source = DataSource()
            self.after_idle(self.update_graph, round(1000 / self.FPS))
            self.run_graph = None
    
        def grid_widgets(self, **kw):
            self.display.grid(row=0, column=0, columnspan=2, sticky=NSEW, **kw)
            self.start.grid(row=1, column=0, sticky=EW, **kw)
            self.grip.grid(row=1, column=1, sticky=SE)
            self.grid_rowconfigure(0, weight=1)
            self.grid_columnconfigure(0, weight=1)
    
        def start_graph(self):
            self.run_graph = True
            threading.Thread(target=self.__simulate, daemon=True).start()
            return 'Stop Graphing', self.stop_graph
    
        def stop_graph(self):
            self.run_graph = False
            return 'Clear Graph', self.clear_graph
    
        def clear_graph(self):
            self.data_source.clear()
            self.reset_display()
            return 'Start Graphing', self.start_graph
    
        # def __simulate(self):
        #     # simulate changing populations
        #     for population in itertools.count():
        #         if not self.run_graph:
        #             break
        #         self.data_source.append(population, get_max_age(population, 200))
    
        # def __simulate(self):
        #     # simulate changing ages
        #     for age in itertools.count(1):
        #         if not self.run_graph:
        #             break
        #         self.data_source.append(age, get_max_age(250_000_000, age))
    
        def __simulate(self):
            # draw a sine curve
            for x in range(800):
                time.sleep(0.01)
                if not self.run_graph:
                    break
                self.data_source.append(x, math.sin(x * math.pi / 400))
    
        def update_graph(self, rate, previous_version=None):
            if previous_version is None:
                self.reset_display()
            current_version = self.data_source.version
            if current_version != previous_version:
                data_source = self.data_source.copy()
                self.draw(data_source)
            self.after(rate, self.update_graph, rate, current_version)
    
        def reset_display(self):
            self.display.delete('data')
            self.display.create_line((0, 0, 0, 0), tag='data', fill='black')
    
        def draw(self, data_source):
            if not isinstance(data_source, DataSource):
                data_source = self.data_source.copy()
            if data_source:
                self.display.coords('data', *data_source.frame(
                    self.MARGINS,
                    self.display.winfo_width(),
                    self.display.winfo_height(),
                    True
                ))
    
    
    class StatefulButton(tkinter.ttk.Button):
        def __init__(self, master, text, command, **kw):
            kw.update(text=text, command=self.__do_command)
            super().__init__(master, **kw)
            self.__command = command
    
        def __do_command(self):
            self['text'], self.__command = self.__command()
    
    
    def new(obj):
        kind = type(obj)
        return kind.__new__(kind)
    
    
    def interpolate(x, y, z):
        return x * (1 - z) + y * z
    
    
    def interpolate_array(array, z):
        if z <= 0:
            return array[0]
        if z >= 1:
            return array[-1]
        share = 1 / (len(array) - 1)
        index = int(z / share)
        x, y = array[index:index + 2]
        return interpolate(x, y, z % share / share)
    
    
    def sample(array, count):
        scale = count - 1
        return tuple(interpolate_array(array, z / scale) for z in range(count))
    
    
    class DataSource:
        EMPTY = uuid.uuid4()
    
        def __init__(self):
            self.__x = []
            self.__y = []
            self.__version = self.EMPTY
            self.__mutex = threading.Lock()
    
        @property
        def version(self):
            return self.__version
    
        def copy(self):
            instance = new(self)
            with self.__mutex:
                instance.__x = self.__x.copy()
                instance.__y = self.__y.copy()
                instance.__version = self.__version
            instance.__mutex = threading.Lock()
            return instance
    
        def __bool__(self):
            return bool(self.__x or self.__y)
    
        def frame(self, margins, width, height, auto_sample=False, timing=False):
            if timing:
                start = time.perf_counter()
            x1, y1, x2, y2 = margins
            drawing_width = width - x1 - x2
            drawing_height = height - y1 - y2
            with self.__mutex:
                x_tuple = tuple(self.__x)
                y_tuple = tuple(self.__y)
            if auto_sample and len(x_tuple) > drawing_width:
                x_tuple = sample(x_tuple, drawing_width)
                y_tuple = sample(y_tuple, drawing_width)
            max_y = max(y_tuple)
            x_scaling_factor = max(x_tuple) - min(x_tuple)
            y_scaling_factor = max_y - min(y_tuple)
            coords = tuple(
                coord
                for x, y in zip(x_tuple, y_tuple)
                for coord in (
                    round(x1 + drawing_width * x / x_scaling_factor),
                    round(y1 + drawing_height * (max_y - y) / y_scaling_factor)))
            if timing:
                # noinspection PyUnboundLocalVariable
                print(f'len = {len(coords) >> 1}; '
                      f'sec = {time.perf_counter() - start:.6f}')
            return coords
    
        def append(self, x, y):
            with self.__mutex:
                self.__x.append(x)
                self.__y.append(y)
                self.__version = uuid.uuid4()
    
        def clear(self):
            with self.__mutex:
                self.__x.clear()
                self.__y.clear()
                self.__version = self.EMPTY
    
        def extend(self, iterable):
            with self.__mutex:
                for x, y in iterable:
                    self.__x.append(x)
                    self.__y.append(y)
                self.__version = uuid.uuid4()
    
    
    if __name__ == '__main__':
        Application.main()
    

    【讨论】:

    【解决方案2】:

    您可以动态更改标签文本:

    这是一种将textvariable 选项与StringVar.set() 方法一起使用的方法

    str_var = tk.StringVar(value="Default")
    
    currentValues= Label(win, textvariable=my_string_var)
    currentValues.place(x=200, y=100)
    
    str_var.set("New value")
    

    另一种使用简单的.configure()方法

    currentValues = Label(win, text = "default")
    currentValues.configure(text="New value")
    

    最后,让 UI 更新而不等待循环的其余部分进行更新

    win.update()
    

    【讨论】:

    • @dayDreamer 在您配置或更改您的小部件后。以便您的窗口使用新设置进行更新
    【解决方案3】:

    我想在 GUI 中显示一些实时数据。

    我认为你想要做的是使用.after() 方法。 .after() 方法将tkinter 排队,以便在设定的时间后运行一些代码。

    例如:

    currentValues = Label(win, text = "want to display somewhere like this")
    currentValues.place(x=200, y=100)
    
    voltageValues = Label(win, text = "want to display somewhere like this")
    voltageValues.place(x=200, y=200)
    
    
    def live_update():
        currentValues['text'] = updated_value
        voltageValues['text'] = updated_value
        win.after(1000, live_update) # 1000 is equivalent to 1 second (closest you'll get)
    
    live_update() # to start the update loop
    

    after 方法中的 1000 个单位是最接近 1 秒的单位。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-11-20
      • 1970-01-01
      • 1970-01-01
      • 2017-08-23
      • 2019-10-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多