Tkinter画布更新时NavigationToolbar功能失效问题求助
解决Tkinter中Matplotlib双面板绘图更新后导航工具栏失效及窗口关闭冻结问题
问题分析
你遇到的两个问题根源分别是:
- 导航工具栏失效:每次更新时用
fig.clf()完全清除了整个figure,导致工具栏保存的初始视图状态还是第一次绘图的信息,而且重新创建子图的方式没有和工具栏建立新的关联,所以home按钮无法恢复到更新后的初始状态。 - 窗口关闭冻结:Python 2.7下Tkinter和matplotlib的TkAgg后端结合时,直接关闭窗口会导致matplotlib的后台渲染线程没有正确终止,进而阻塞终端。
解决方案代码
下面是修改后的完整代码,关键改动会在注释里说明:
from Tkinter import * import Tkinter as tk import ttk import numpy as np # 加载matplotlib模块 import matplotlib matplotlib.use("TkAgg") import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg from matplotlib.figure import Figure top = tk.Tk() top.title("Intermolecular PDFs") top_frame = ttk.Frame(top, padding=(10, 10)) top_frame.pack() fig = Figure(figsize=(10, 6), dpi=100) # 提前创建子图对象,避免反复add_subplot导致的状态混乱 gs = fig.add_gridspec(2, 1, hspace=0.3) ax1 = fig.add_subplot(gs[0]) ax2 = fig.add_subplot(gs[1]) # 初始化绘图数据 x = np.linspace(0, 1) y = np.sin(x) z = np.cos(x) # 初始绘图函数,使用axes对象方法而非全局plt函数 def init_plot(): ax1.clear() ax1.set_title("Individual PDFs") ax1.set_xlabel(r"r (Å)", labelpad=3, fontsize=15) ax1.set_ylabel(r"PDF, G (Å$^{-2})$", labelpad=10, fontsize=15) ax1.plot(x, y, "r-", lw=2) ax1.tick_params(axis='both', labelsize=11) ax2.clear() ax2.set_title("Difference PDFs") ax2.set_xlabel(r"r (Å)", labelpad=3, fontsize=15) ax2.set_ylabel(r"PDF, G (Å$^{-2})$", labelpad=10, fontsize=15) ax2.plot(x, z, "g-", lw=2) ax2.tick_params(axis='both', labelsize=11) fig.tight_layout() canvas.draw() # 把当前视图设为工具栏的初始状态 toolbar.push_current() init_plot() canvas = FigureCanvasTkAgg(fig, master=top_frame) canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1) toolbar = NavigationToolbar2TkAgg(canvas, top_frame) toolbar.update() canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1) def update(): new_x = np.linspace(1, 100) new_y = new_x**2 new_z = new_x**3 # 仅清除子图内容,保留figure和工具栏的关联 ax1.clear() ax1.set_title("Individual PDFs") ax1.set_xlabel(r"r (Å)", labelpad=3, fontsize=15) ax1.set_ylabel(r"PDF, G (Å$^{-2})$", labelpad=10, fontsize=15) ax1.plot(new_x, new_y, "r-", lw=2) ax1.tick_params(axis='both', labelsize=11) ax2.clear() ax2.set_title("Difference PDFs") ax2.set_xlabel(r"r (Å)", labelpad=3, fontsize=15) ax2.set_ylabel(r"PDF, G (Å$^{-2})$", labelpad=10, fontsize=15) ax2.plot(new_x, new_z, "g-", lw=2) ax2.tick_params(axis='both', labelsize=11) fig.tight_layout() canvas.draw() # 更新后重置工具栏的初始视图,让home按钮指向新状态 toolbar.push_current() # 绑定窗口关闭事件,正确清理资源避免终端冻结 def on_closing(): plt.close('all') # 先关闭matplotlib后台 top.destroy() top.protocol("WM_DELETE_WINDOW", on_closing) ttk.Button(top_frame, text="update", command=update).pack() top.mainloop()
关键改动说明
保留子图对象,避免清除整个Figure:
- 提前创建
ax1和ax2子图对象,更新时仅调用ax.clear()清除子图内容,而非fig.clf()清除整个figure,维持了figure与工具栏的关联。 - 使用axes对象的方法(如
ax.set_title()、ax.plot())替代全局plt函数,避免全局绘图状态混乱。
- 提前创建
重置工具栏初始视图:
- 在初始化和更新绘图后,调用
toolbar.push_current()将当前绘图视图设为工具栏的初始状态,确保home按钮能正确恢复到最新的初始视图。
- 在初始化和更新绘图后,调用
解决窗口关闭冻结:
- 绑定窗口
WM_DELETE_WINDOW事件,关闭窗口时先调用plt.close('all')终止matplotlib后台线程,再销毁Tkinter窗口,避免终端阻塞。
- 绑定窗口
修改后,更新按钮点击后导航工具栏的所有功能(home、放大、缩小等)都能正常工作,直接关闭窗口也不会导致终端冻结了。
内容的提问来源于stack exchange,提问作者Chenyang
相关产品推荐
相关产品推荐

