Python - Tkinter 中的 Matplotlib - 重绘刚刚退出(或 Rasberry 中的段错误)

Python - Matplotlib in Tkinter - Redrawing just quits (or segfault in Rasberry)

美好的一天,

所以我有点反对在 Tkinter 中刷新 Matplotlib。总的来说,代码工作正常,但是一旦你按下“向上”键,程序就会在 figure.canvas.draw() 处崩溃——我也在 rasberry 上 运行 这段代码,它只是说内存访问失败。

我知道你可以通过 FuncAnimation 制作动画,但我遇到了一个大问题,它会随着时间的推移增加内存直到崩溃,所以我不想使用它(参见 Python3 - Matplotlib FuncAnimation continuous memoryleak

所以我想我可以用老式的方式重画它,但这似乎也行不通。

最小代码:

import tkinter as tk

from threading import Thread
from numpy import sin, cos, pi
from pynput.keyboard import Listener
import keyboard



reDraw = False


U1ausg = U2ausg = U3ausg = Counter_3 = 50
Counter_1 = 120
Counter_2 = 240


        

#--Mainwindow--
class Drehstromdemonstrator(tk.Tk):

    def __init__(self):
        #Predefine

        tk.Tk.__init__(self)
        tk.Tk.wm_title(self, "Minimal")
        
        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)  

        self.frame = plotSine(self,(tk.Tk.winfo_screenwidth(self)*0.7,tk.Tk.winfo_screenheight(self)*0.7))

    def reDrawFrame(self):
        self.frame.drawStuff()

      
import numpy,matplotlib
matplotlib.use('TkAgg')
from numpy import sin, cos, pi, deg2rad, linspace, arange
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib.ticker as tck
import time
import math


from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from math import ceil

#global U1ausg, U2ausg, U3ausg, Counter_1, Counter_2, Counter_3


class plotSine:

    def __init__(self,masterframe,size):
        self._running = True

        global U1ausg
        global U2ausg
        global U3ausg

        (w,h) = size
        inchsize = (w/25.5, h/25.4)
        fig = self.figure = Figure(inchsize)

        self.axes = fig.add_subplot(111)

        self.axes.xaxis.set_ticks(arange(0,390,30))
        self.axes.margins(x=0)
        self.axes.yaxis.set_ticks(arange(-120,130,20))
        self.axes.set_ylim(-120,120)

        #create canvas as matplotlib drawing area
        self.canvas = FigureCanvasTkAgg(fig, master=masterframe)
        self.canvas.get_tk_widget().pack()
        #self.canvas.mpl_connect('key_press_event',on_press)
        
        self.x = linspace(0,360,1000)
        self.axes.grid()

        self.ysin = int(ceil(U1ausg))*sin(50/Counter_3 * deg2rad(self.x))


        self.lineU1, = self.axes.plot(self.x, self.ysin, "-r",label="U1")
        
        #self.drawStuff()

    #Draw the plot
    def drawStuff(self,*args):

        ax = self.axes
        #ax.clear()
        ax.legend(["U1"])

        #Changed everything to degree instead of PI, better looking
        self.lineU1.set_ydata(int(ceil(U1ausg))*sin(50/Counter_3 * deg2rad(self.x)))

        print("Redrawing!")
#---------------------------------------------------------------------
#---------------------------------------------------------------------
#---------------------------------------------------------------------
        self.figure.canvas.draw()  #First run is fine, crashes here afterwards

        print("Flush!")
        self.figure.canvas.flush_events()
        
        print("ReDraw Done!")

#--Run Mainprog
app = Drehstromdemonstrator()


def on_press(event):
    global Counter_3
    Counter_3 = Counter_3 + 5
    print("up")
    app.reDrawFrame()
    
try:    
    keyboard.on_press_key("up", on_press)
    app.mainloop()

# Aufraeumarbeiten nachdem das Programm beendet wurde
except UnicodeDecodeError:
    print("Interrupt aswell!")
    lampensteuerung.terminate()

except KeyboardInterrupt:
    print("Interrupt")
    lampensteuerung.terminate()

except Exception as e:
    print(e)   

此代码只是将一个简单的正弦波 (matplotlib) 放入 Tkinter 叠加层(必须有一个)- 在按钮上按“UP”可以将频率提高 +5。

我正在使用 keyboard.on_press_key("up", on_press) 因为我必须在我使用 GPIO.add_event_detect(channel, GPIO.RISING) 的树莓上尽可能地模拟它 (使用 fig.canvas.mpl_connect('key_press_event', on_press) 它工作正常但我不能在 PI 上这样做)

谢谢和问候!

所以我自己得到了 - 解决方案:将 draw() 更改为 draw_idle()

对于任何需要 Tinker 中可更改的 Matplotlib 以响应 Keyinputs / 或 GPIO.INPUTS 的人,这里是代码:

# coding=utf-8
import tkinter as tk
import math as m
from numpy import sin

#-----------------------------------Ausgabe:-----------------------------------------
#Predefine
app = None

freq = 50

# Create Main Window
class Application(tk.Tk):

    def __init__(self):
        #Predefine
        tk.Tk.__init__(self)
        tk.Tk.wm_title(self, "Title")

        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        #Add the MatPlotLib 
        self.frame = plotSine(self,(tk.Tk.winfo_screenwidth(self)*0.7,tk.Tk.winfo_screenheight(self)*0.7))

    def reDrawFrame(self):
        self.frame.reDraw()
        self.frame.reDraw()  ## For whatever reason it can happen that the Draw Function has to be called twice for a good refresh?

#----------------------------------------------------------------------------------
#----------------------------------------------------------------------------------
#----------------------------------------------------------------------------------


import numpy,matplotlib
matplotlib.use('TkAgg')

from numpy import sin, deg2rad, linspace
import math

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from math import ceil

import keyboard

class plotSine:

    def __init__(self,masterframe,size):
        self._running = True

        (w,h) = size
        inchsize = (w/25.5, h/25.4)
        fig = self.figure = Figure(inchsize)

        self.axes = fig.add_subplot(111)
        
        #create canvas as matplotlib drawing area
        self.canvas = FigureCanvasTkAgg(fig, master=masterframe)
        self.canvas.draw_idle()
        self.canvas.get_tk_widget().pack()

        self.x = linspace(0,360,1000)
        self.axes.grid()

        self.ysin = int(ceil(120))*sin(50/freq * deg2rad(self.x))

        self.lineU1, = self.axes.plot(self.x, self.ysin, "-r",label="U1")

    #Draw the plot
    def reDraw(self,*args):
        ax = self.axes

        self.ysin = int(ceil(120))*sin(50/freq * deg2rad(self.x))

        self.lineU1.set_ydata(self.ysin)

        self.figure.canvas.draw_idle()
        self.figure.canvas.flush_events()


#------------------------------------------------------------------------------------
#Keyboard Click - Changeable to any other event i.e. GPIO event detect
def up_press(event):
    global freq
    freq = freq + 5
    app.reDrawFrame()

def down_press(event):
    global freq
    if(freq > 5):
        freq = freq - 5
    app.reDrawFrame()
#-------------------------------------------------------------------------------------
# Start Tkinter
try:
    app = Application()
    keyboard.on_press_key("up", up_press)
    keyboard.on_press_key("down", down_press)
    app.mainloop()

except Exception as e:
    print(e)