如何在Tkinter框架中结合FigureCanvasTkAgg实现Blitting?
问题描述
我正在构建一个GUI程序,需要从CSV文件绘制多幅类实时图表。最初用FuncAnimation实现,但移植到树莓派后运行卡顿。改用Blitting更新绘图数组后效果不错,但没法把这个功能整合到Tkinter框架里:
- 用
fig.canvas会弹出新窗口 - 用
FigureCanvasTkAgg时Blitting功能失效
想问两个问题:
- 能不能让
FigureCanvasTkAgg支持Blitting? - 或者能不能给
fig.canvas指定master窗口?
我之前写过BlitManager类,现在简化代码尝试解决,核心代码如下:
class SeaofBTCapp(tk.Tk): def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) tk.Tk.wm_title(self, "Sea of BTC Client") container = tk.Frame(self, bg="whitesmoke") container.pack(side="top", fill="both", expand=True) container.grid_rowconfigure(0, weight=1) container.grid_columnconfigure(0, weight=1) self.frames = {} for F in ( StartPage, PageOne, ): frame = F(container, self) self.frames[F] = frame frame.grid(row=0, column=0, sticky="nsew") self.show_frame(StartPage) def show_frame(self, cont): frame = self.frames[cont] frame.tkraise() def qf(quickPrint): print(quickPrint) class StartPage(tk.Frame): def __init__(self, parent, controller): tk.Frame.__init__(self, parent, bg="aliceblue") label = tk.Label(self, text="Home Page", font=LARGE_FONT, bg="aliceblue") label.pack(padx=10, pady=10) button = tk.Button( self, text="ECG", command=lambda: controller.show_frame(PageOne), font=MED_FONT, bg="lightseagreen", activebackground="seagreen", ) button.pack(padx=10, pady=10) class PageOne(tk.Frame): def __init__(self, parent, controller): tk.Frame.__init__(self, parent) label = tk.Label(self, text="HR", font=LARGE_FONT) label.pack(pady=10, padx=10) label2 = tk.Label(self, text="HR = {}".format(round(HR)), font=LARGE_FONT) label2.pack() button1 = tk.Button( self, text="Back to Home", command=lambda: controller.show_frame(StartPage), font=MED_FONT, ) button1.pack() y=ecg_h[0:300] fig, ax=plt.subplots() (line1, )= ax.plot(y, linewidth="0.75", animated=True) plt.show(block=False) plt.pause(0.1) #self.canvas=FigureCanvasTkAgg(fig,self) canvas=fig.canvas canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True) bg = canvas.copy_from_bbox(fig.bbox) ax.draw_artist(line1) canvas.blit(fig.bbox) canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True) for i in range(301,len(ecg_h)-2): canvas.restore_region(bg) for j in range (300): y[300-(300-j)]=ecg_h[i+j] line1.set_ydata(y) ax.draw_artist(line1) canvas.blit(fig.bbox) canvas.flush_events() #toolbar = NavigationToolbar2Tk(canvas2, self) #toolbar.update()
解决方案
1. 让FigureCanvasTkAgg支持Blitting的正确姿势
FigureCanvasTkAgg本身完全支持Blitting,你的代码问题在于混用了Matplotlib弹窗逻辑,且没有正确绑定画布到Tk容器。以下是修正后的实现:
# 需要提前导入Figure from matplotlib.figure import Figure class PageOne(tk.Frame): def __init__(self, parent, controller): tk.Frame.__init__(self, parent) label = tk.Label(self, text="HR", font=LARGE_FONT) label.pack(pady=10, padx=10) self.label2 = tk.Label(self, text="HR = {}".format(round(HR)), font=LARGE_FONT) self.label2.pack() button1 = tk.Button( self, text="Back to Home", command=lambda: controller.show_frame(StartPage), font=MED_FONT, ) button1.pack() # 初始化数据与索引 self.y = ecg_h[0:300].copy() self.current_idx = 301 # 直接创建Figure并绑定到当前Tk Frame self.fig = Figure(figsize=(6, 4), dpi=100) self.ax = self.fig.add_subplot(111) self.canvas = FigureCanvasTkAgg(self.fig, master=self) self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True) # 初始化绘图元素并固定坐标轴范围(避免重绘轴) self.line1, = self.ax.plot(self.y, linewidth="0.75", animated=True) self.ax.set_ylim(min(ecg_h), max(ecg_h)) self.ax.set_xlim(0, 300) # 绘制背景并初始化Blitting self.canvas.draw() self.bg = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self.line1) self.canvas.blit(self.ax.bbox) # 启动异步更新循环 self.update_plot() def update_plot(self): if self.current_idx >= len(ecg_h) - 300: return # 更新数据 self.y = ecg_h[self.current_idx:self.current_idx+300].copy() self.line1.set_ydata(self.y) # Blitting更新仅变化区域 self.canvas.restore_region(self.bg) self.ax.draw_artist(self.line1) self.canvas.blit(self.ax.bbox) # 更新HR标签 self.label2.config(text="HR = {}".format(round(HR))) self.current_idx += 1 # 每隔10ms更新一次(可根据需求调整) self.after(10, self.update_plot)
核心修正点
- 删除Matplotlib弹窗代码:去掉
plt.show(block=False)和plt.pause(),这些会创建独立窗口,与Tkinter容器冲突 - 手动绑定画布到Tk容器:用
FigureCanvasTkAgg直接创建画布,明确指定master为当前Tk Frame - 固定坐标轴范围:提前设置
ax.set_ylim()和ax.set_xlim(),避免每次更新重绘坐标轴,大幅提升性能 - 用Tkinter
after()替代死循环:死循环会阻塞GUI事件循环导致界面卡死,after()是Tkinter官方推荐的异步更新方式 - 缩小Blitting范围:用
ax.bbox替代fig.bbox,只更新坐标轴区域,减少计算量
2. 给fig.canvas指定master的可行性
直接给默认fig.canvas指定master不可行,因为默认画布是Matplotlib后端自动创建的,已经绑定了独立的Tk窗口。正确做法还是用FigureCanvasTkAgg手动创建画布并绑定到目标Tk容器。
内容的提问来源于stack exchange,提问作者Summer Duffy
相关产品推荐
相关产品推荐

