You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Tkinter框架中结合FigureCanvasTkAgg实现Blitting?

问题描述

我正在构建一个GUI程序,需要从CSV文件绘制多幅类实时图表。最初用FuncAnimation实现,但移植到树莓派后运行卡顿。改用Blitting更新绘图数组后效果不错,但没法把这个功能整合到Tkinter框架里:

  • 用fig.canvas会弹出新窗口
  • 用FigureCanvasTkAgg时Blitting功能失效

想问两个问题:

  1. 能不能让FigureCanvasTkAgg支持Blitting?
  2. 或者能不能给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(),避免每次更新重绘坐标轴,大幅提升性能
  • 用Tkinterafter()替代死循环:死循环会阻塞GUI事件循环导致界面卡死,after()是Tkinter官方推荐的异步更新方式
  • 缩小Blitting范围:用ax.bbox替代fig.bbox,只更新坐标轴区域,减少计算量

2. 给fig.canvas指定master的可行性

直接给默认fig.canvas指定master不可行,因为默认画布是Matplotlib后端自动创建的,已经绑定了独立的Tk窗口。正确做法还是用FigureCanvasTkAgg手动创建画布并绑定到目标Tk容器。

内容的提问来源于stack exchange,提问作者Summer Duffy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 20:50:56