Tkinter回调中的异常:AttributeError:'NoneType'对象没有属性'update'

时间:2019-05-09 15:28:33

标签: python tkinter

我正在尝试使用tkinter进行绘图,但出现此错误,并且无法追溯:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Python37\lib\tkinter\__init__.py", line 1705, in __call__
    return self.func(*args)
  File "C:\Python37\lib\tkinter\__init__.py", line 749, in callit
    func(*args)
  File "C:\Python37\lib\site-packages\matplotlib\backends\_backend_tk.py", line 346, in idle_draw
    self.draw()
  File "C:\Python37\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 9, in draw
    super(FigureCanvasTkAgg, self).draw()
  File "C:\Python37\lib\site-packages\matplotlib\backends\backend_agg.py", line 402, in draw
    self.figure.draw(self.renderer)
  File "C:\Python37\lib\site-packages\matplotlib\artist.py", line 50, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "C:\Python37\lib\site-packages\matplotlib\figure.py", line 1649, in draw
    renderer, self, artists, self.suppressComposite)
  File "C:\Python37\lib\site-packages\matplotlib\image.py", line 138, in _draw_list_compositing_images
    a.draw(renderer)
  File "C:\Python37\lib\site-packages\matplotlib\artist.py", line 50, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "C:\Python37\lib\site-packages\matplotlib\axes\_base.py", line 2628, in draw
    mimage._draw_list_compositing_images(renderer, self, artists)
  File "C:\Python37\lib\site-packages\matplotlib\image.py", line 138, in _draw_list_compositing_images
    a.draw(renderer)
  File "C:\Python37\lib\site-packages\matplotlib\artist.py", line 50, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "C:\Python37\lib\site-packages\matplotlib\text.py", line 709, in draw
    bbox, info, descent = textobj._get_layout(renderer)
  File "C:\Python37\lib\site-packages\matplotlib\text.py", line 286, in _get_layout
    key = self.get_prop_tup(renderer=renderer)
  File "C:\Python37\lib\site-packages\matplotlib\text.py", line 871, in get_prop_tup
    x, y = self.get_unitless_position()
  File "C:\Python37\lib\site-packages\matplotlib\text.py", line 854, in get_unitless_position
    y = float(self.convert_yunits(self._y))
  File "C:\Python37\lib\site-packages\matplotlib\artist.py", line 195, in convert_yunits
    return ax.yaxis.convert_units(y)
  File "C:\Python37\lib\site-packages\matplotlib\axis.py", line 1530, in convert_units
    ret = self.converter.convert(x, self.units, self)
  File "C:\Python37\lib\site-packages\matplotlib\category.py", line 53, in convert
    unit.update(values)
AttributeError: 'NoneType' object has no attribute 'update'

这是我的代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Graphic user interface that can be used with the experiment to write values and give live graphical feedback to the user
"""
import time
from collections import deque
try:
    # for Python2
    import Tkinter as tk
    import ttk
except ImportError:
    # Python 3
    import tkinter as tk
    from tkinter import ttk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import pyplot as plt
import matplotlib.animation as animation
from matplotlib.gridspec import GridSpec
from crio.communication_handler import Handler

bitfile = "../LabVIEW/FPGA Bitfiles/Live_bitfile.lvbitx"
compactRIO = "rio://169.254.47.210/RIO0"
steps = "../Examples/Step_Setter/Steps.yml"
test = "../Examples/test.yml"
config = "../Examples/YAML_Config.yml"
cm = Handler(config, test, steps, bitfile, compact_rio=None)
cm.verbose = False
cm.start()


# Setting constants
HISTORY_LEN = 200  # Number of data points to show in the plot
GRAPH_INTERVAL = 100  # milliseconds between graph updates
XLABEL = "Time (s)"
# For real time graphing
PAUSE = "Pause Graph"
UNPAUSE = "Unpause Graph"
START = "Start Graphing"
CLEAR = "Reset Graphs"
# GUI Colors
background_color = "#ffffff"
button_color = "cadet blue"
button_txt_color = "snow"

button_opts = dict(  # general button settings
    width=30,
    font=("MS Serif", 12),
    borderwidth=3.5,
    relief="raised",
    bg=button_color,
    fg=button_txt_color)

# Container for the plots so graphing is simplified
gs = GridSpec(3, 6)  # (x,y)
PLOTS = [
    # (ylabel, title, YLIM_HIGH, YLIM_LOW,  plot position, register)
    ('Phi', 'McKenna: Actual Equivalence Ratio (Phi)', 6, 0, gs[0, 0:-4],
     'Actual_Phi'),
    ('Velocity (cm/s)', 'McKenna: Actual Mixture Velocity', 500, 0,
     gs[0, 2:-2], 'Actual_Velocity'),
    ('Air, LPM', 'McKenna Air Flow rate (LPM)', 600, 0, gs[0, 4:], 'MFC_Air'),
    ('Hydrogen, LPM', 'Hydrogen Fuel Flow Rate (LPM)', 500, 0, gs[2, 0:-4],
     'MFC_Fuel'),
    ('Air SCCM', 'Tube Air Flow Rate (SCCM)', 75, 0, gs[1, 2:-2],
     'MFC_Tube_Air'),
    ('N₂, SCCM', 'Tube N₂ Flow Rate (SCCM)', 50, 0, gs[1, 4:], 'MFC_Tube_N2'),
    ('Temperature', 'Temperature Large', 1700, 0, gs[2, 4:],
     'Temperature_Large'),
    ('Amplitude', 'Microphone Readings', 15, -15, gs[2, 2:-2], 'Mic_Voltage'),
    ('Pressure (PSI)', 'Pressure Reading', 500, 0, gs[1, 0:-4],
     'Pressure_Reading')
]
COLUMNS = 2  # Determines entries in a row
# Fields will correspond with the Dynamic User Input entries i.e. the set points
FIELDS = (("Dynamic User Inputs", (
    cm.set_point
)), )


class GUI(tk.Tk):
    """GUI created from Tkinter

    Note:
        The __init__ method creates the GUI window with using a
        notebook tabbed style and initializes each tab.

        The input fields tab is created to allow for input values
        for the experiment.

        The graph window tab is where the live plotting of the
        results are to be displayed
    """

    def __init__(self):
        try:
            super(tk.Tk, self).__init__(self)
        except TypeError:
            super().__init__()
        # GUI window
        self.title("Python Graphic User Interface")

        # Maximizes window when exiting fullscreen mode
        # self.state('zoomed') # <-- not supported in Linux
        self.attributes("-fullscreen", True)
        self.configure(background=background_color)
        self.bind("<Escape>", self.end_fullscreen)

        # Title
        title = tk.Label(
            self,
            text="Microcombustion Project",
            bg=background_color,
            font=("Bodoni bold", 45))
        title.pack(fill=tk.BOTH)

        style = ttk.Style()
        style.theme_create(
            "MyStyle",
            parent="alt",
            settings={
                "TNotebook": {
                    "configure": {
                        "tabmargins": [0, 5, 0, 0],
                        "background": "#ffffff"
                    }
                },
                "TNotebook.Tab": {
                    "configure": {
                        "padding": [100, 8],
                        "font": ("MS Serif, bold", 14)
                    }
                }
            })

        style.theme_use("MyStyle")

        nb = ttk.Notebook(self)
        nb.pack(expand=True, fill=tk.BOTH)

        # Input fields
        input_fields = EntryWindow(self, background=background_color)
        nb.add(input_fields, text='Values')

        # Graph
        graph_window = GraphWindow(self)
        nb.add(graph_window, text='Graphs')

    def end_fullscreen(self, event=None):
        """Toggle full screen"""
        self.state = False
        self.attributes("-fullscreen", False)
        return "break"


# Creating labels and entries out of a tuple
class Entry:
    """Creating labels and entries out of a tuple

    Note:
        The Entry class is created to store/set dynamic inputs on the GUI

    Args:
            master: The main GUI
            text: Input field names
            value: Initial values
            idx: Index

    """
    instances = []

    def __init__(self, master, text, value, idx):
        self.text = text
        row, column = divmod(idx, COLUMNS)
        lbl = tk.Label(
            master,
            text=text.replace("_", " ") + ":",
            bg=background_color,
            font=("Bodoni bold", 15))
        lbl.grid(row=row, column=column * 2, sticky='e')
        self.ent = ttk.Entry(master, font="Bodoni 14", justify="right")
        self.ent.insert(0, '{}'.format(value))
        self.ent.grid(row=row, column=column * 2 + 1)
        self.instances.append(self)

    def get(self):
        """Retrieves the values

        Returns: The text formatted properly and entries

        """
        return self.text, self.ent.get()


class EntryWindow(tk.Frame):
    """The entry window for the GUI

    Args:
            master: The main GUI
            **kwargs: Tkinter's Frame method takes keyword arguments
    """

    def __init__(self, master=None, **kwargs):
        self.frame = tk.Frame.__init__(self, master, **kwargs)
        master.bind("<Return>", self.fetch_values)

        # Input fields
        for name, properties in FIELDS:
            namelbl = tk.Label(
                self,
                text=name,
                bg=background_color,
                font=("Bodoni bold", 30),
                pady=25)
            namelbl.pack()
            input_fields = tk.Frame(self, background=background_color)
            for idx, (field, initVal) in enumerate(properties.items()):
                Entry(input_fields, field, initVal, idx)
                input_fields.pack()

        # Buttons
        self.set_button = tk.Button(
            self, text='Set Values', command=self.fetch_values, **button_opts)
        self.set_button.pack()

    def fetch_values(self, event=None):
        """Fetching input entries

        Args:
            event: None

        Returns: Sets the TCP data to the inputted parameters
        """
        # Dictionary that will act like a JSON file
        data = dict(elem.get() for elem in Entry.instances)
        print('Values have been set.')


class GraphWindow(tk.Frame):
    """Frame containing graphed data"""

    def __init__(self, master=None, **kwargs):
        """

        Args:
            master: The main GUI
            **kwargs: Tkinter's Frame method takes keyword arguments
        """
        self.frame = tk.Frame.__init__(self, master, **kwargs)
        self.start_time = time.time()
        self.running = False
        self.ani = None

        # Graphing plots from predefined PLOTS
        self.fig = plt.Figure()
        self.plots = []  # a container for the plots
        for ylabel, title, ylim_high, ylim_low, position, register in PLOTS:
            ax = self.fig.add_subplot(position)
            line, = ax.plot([], [], lw=2)
            ax.set_ylim(ylim_low, ylim_high)
            ax.set_xlabel(XLABEL)
            ax.set_ylabel(ylabel)
            ax.set_title(title)
            bbox_props = dict(
                boxstyle="round,pad=0.3", fc="cyan", ec="b", lw=2)
            t = ax.text(
                0, 0, "0", ha="right", va="center",
                bbox=bbox_props)  # Indicator
            self.plots.append((ax, line, register, t))

        # Adjusting subplot placement
        self.fig.subplots_adjust(
            bottom=.075, left=.05, right=.99, top=.9, hspace=.35, wspace=0.35)
        self.canvas = FigureCanvasTkAgg(self.fig, master=self)
        self.canvas.draw()
        self.canvas.get_tk_widget().pack(expand=True, fill=tk.BOTH)

        # Start, pause, unpause button that is called from on_click()
        btn_frame = tk.Frame(self)
        self.btn = tk.Button(
            btn_frame, text=START, command=self.on_click, **button_opts)
        self.btn.pack(side=tk.LEFT)
        btn = tk.Button(btn_frame, text=CLEAR, command=cm.clear(), **button_opts)
        btn.pack(side=tk.LEFT)
        btn_frame.place(relx=0.5, rely=0, anchor='n')

    def on_click(self):
        """How buttons in the GraphWindow respond to being pressed"""
        if self.ani is None:
            return self.start()  # animation is not running; start it

        if self.running:
            self.ani.event_source.stop  # animation is running; pause it
            self.btn.config(text=UNPAUSE)
        else:
            self.ani.event_source.start()  # animation is paused; unpause it
            self.btn.config(text=PAUSE)
        self.running = not self.running

    def start(self):
        """Start graphing"""
        self.ani = animation.FuncAnimation(self.fig, self.update_graph, interval=GRAPH_INTERVAL)
        self.running = True
        self.btn.config(text=PAUSE)
        self.ani._start()


    def update_graph(self, i):
        """Function to constantly plot new data"""
        # If statement to check that data is being read
        if cm.set_point:
            dt = 10
            if self.start_time < dt:
                second_time = '00:00:00'
            else:
                hours, rem = divmod(time.time() - self.start_time - dt, 3600)
                minutes, seconds = divmod(rem, 60)
                second_time = '{:0>2}:{:0>2}:{:02.0f}'.format(
                    int(hours), int(minutes), seconds)

            """
                Run time and data needs to be set from cm
            """
            x_lim = second_time, max(cm.fpga.run_time)
            last_x = cm.fpga.run_time[-1]
            for ax, line, register, text in self.plots:
                line.set_data(cm.fpga.run_time, cm.fpga.result[register])
                ax.set_xlim(*x_lim)  # rescale X
                last_y = cm.fpga.run_time[-1]
                text.set_text(str(last_y))
                text.set_position((last_x, last_y))


class Plotting:
    """Plotting the data"""

    def __init__(self):
        self.output_data = {}  # initial data value
        self.xdata = deque([], maxlen=HISTORY_LEN)
        self.ydata = {}  # container for the ydata
        self.indicators = []  # container for the indicator functions
        for ylabel, title, ylim_high, ylim_low, position, register in PLOTS:
            self.ydata[register] = deque([], maxlen=HISTORY_LEN)

    def clear(self):
        """Clears the data read"""
        self.xdata.clear()
        for ydata in self.ydata.values():
            ydata.clear()
        self.start_time = time.time()


def run_gui():
    root = GUI()
    root.iconbitmap(r'../docs/lsu_tiger.ico')
    root.mainloop()

我只是调用run_gui函数来获取AttributeError: 'NoneType' object has no attribute 'update',但我不确定是什么导致了错误,或者如何追溯到错误。我已经坚持了很长时间了,真的可以使用一些帮助

0 个答案:

没有答案