Tkinter嵌入Matplotlib时出现额外绘图窗口,如何解决?
问题
使用Tkinter开发GUI并绘制流式数据时,程序会额外弹出一个Matplotlib绘图窗口,相关代码如下:
应用代码
import tkinter as tk from tkinter import ttk from random import randint import matplotlib.pyplot as plt from matplotlib.patches import Rectangle from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg class App(tk.Tk): def __init__(self): super().__init__() self.button_run = tk.Button(master=self, text='run', bg='grey', command=self.run) self.button_run.pack() self.fig = plt.Figure() self.fig, self.axes_dict = plt.subplot_mosaic([['o', 'o'], ['_', '__']]) self.canvas = FigureCanvasTkAgg(figure=self.fig, master=self) self.canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True) self.canvas.draw_idle() self.fig.canvas.flush_events() def run(self): S = Streamer(parent=self) S.start()
流式数据绘制代码
class Streamer: def __init__(self, parent): self.parent = parent def start(self): # plot random data for x in range(6): self.parent.axes_dict['o'].add_patch(Rectangle((x, randint(x, x*2)), height=0.4, width=0.4)) self.parent.axes_dict['o'].relim() self.parent.axes_dict['o'].autoscale_view(True, True, True) self.parent.fig.canvas.draw_idle() self.parent.fig.canvas.flush_events() plt.pause(0.4)
启动代码
if __name__ == '__main__': A = App() A.mainloop()
请问这个额外的Matplotlib窗口为何会出现?如何关闭它?
额外窗口出现的原因
- 你先执行了
self.fig = plt.Figure(),这行代码创建了一个独立的Figure对象,由于没有将它绑定到Tkinter画布,Matplotlib的pyplot模块会默认给这个无主Figure生成独立绘图窗口。 - 流式绘制中的
plt.pause(0.4)会触发Matplotlib的全局事件循环,强制显示所有未关联到自定义画布的Figure窗口,直接导致多余窗口弹出。
解决方法
步骤1:移除多余的Figure创建语句
删除self.fig = plt.Figure()这一行,因为后续plt.subplot_mosaic已经会返回新的Figure对象,之前的创建完全多余,还会产生无主窗口。
步骤2:替换plt.pause为Tkinter原生延迟
plt.pause会触发Matplotlib全局状态,改用Tkinter的after()方法实现延迟,既符合Tkinter事件循环逻辑,又不会触发多余窗口。
修改后的完整代码
应用代码调整后
import tkinter as tk from random import randint import matplotlib.pyplot as plt from matplotlib.patches import Rectangle from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg class App(tk.Tk): def __init__(self): super().__init__() self.button_run = tk.Button(master=self, text='run', bg='grey', command=self.run) self.button_run.pack() # 移除多余的plt.Figure()创建 self.fig, self.axes_dict = plt.subplot_mosaic([['o', 'o'], ['_', '__']]) self.canvas = FigureCanvasTkAgg(figure=self.fig, master=self) self.canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True) self.canvas.draw_idle() def run(self): self.streamer = Streamer(parent=self) self.streamer.start()
流式数据绘制代码调整后
class Streamer: def __init__(self, parent): self.parent = parent self.x = 0 # 用实例变量记录循环次数 def start(self): if self.x < 6: self.parent.axes_dict['o'].add_patch(Rectangle((self.x, randint(self.x, self.x*2)), height=0.4, width=0.4)) self.parent.axes_dict['o'].relim() self.parent.axes_dict['o'].autoscale_view(True, True, True) self.parent.fig.canvas.draw_idle() self.x += 1 # 用Tkinter的after方法实现延迟,避免触发Matplotlib全局事件循环 self.parent.after(400, self.start)
启动代码不变
if __name__ == '__main__': A = App() A.mainloop()
这样修改后,多余的Matplotlib窗口就不会再出现,同时流式绘制逻辑符合Tkinter事件循环机制,避免了主进程阻塞。
内容的提问来源于stack exchange,提问作者jamesB
相关产品推荐
相关产品推荐

