在Tkinter界面中使用Matplotlib绘图的问题求助
解决方案
方案1:独立弹出绘图窗口(符合首选需求)
核心思路是用Tkinter的Toplevel窗口承载Matplotlib图表,替代原生plt.show()窗口,实现:
- 主窗口关闭时绘图窗口同步关闭
- 按钮在绘图窗口打开时禁用,关闭后自动恢复
- 避免重复打开多个绘图窗口
修改后的完整代码示例:
import tkinter as tk from tkinter import ttk import pandas as pd import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from functools import partial class YourApp: def __init__(self, root): self.root = root self.root.title("Main Window") # 初始化Pandastable(假设已完成相关配置) # self.pt = Table(self.root, ...) # self.pt.pack(fill=tk.BOTH, expand=True) # 底部按钮容器 self.button_frame = ttk.Frame(self.root) self.button_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=5, pady=5) self.button1 = ttk.Button(self.button_frame, width=25, text="Plot 0") self.button1.pack(side="left", padx=2) # 记录绘图窗口实例,防止重复打开 self.plot_window = None def func1(self, dictionary): # 关闭已存在的绘图窗口 if self.plot_window is not None and self.plot_window.winfo_exists(): self.plot_window.destroy() # 禁用按钮 self.button1['state'] = 'disabled' # 创建独立绘图窗口 self.plot_window = tk.Toplevel(self.root) self.plot_window.title("Antenna A Ch 1 RSSI") # 绘制箱线图 df = pd.DataFrame.from_dict(dictionary) fig, ax = plt.subplots(figsize=(8, 5)) df.boxplot(column='RSSI', by='SenderID', ax=ax) ax.set_title('Antenna A Ch 1 RSSI') ax.set_xlabel('SenderID') ax.set_ylabel('RSSI') plt.suptitle('') # 移除默认的分组标题 # 将图表嵌入Toplevel窗口 canvas = FigureCanvasTkAgg(fig, master=self.plot_window) canvas.draw() canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 绑定绘图窗口关闭事件,恢复按钮状态 def on_plot_close(): self.button1['state'] = 'normal' self.plot_window.destroy() self.plot_window = None self.plot_window.protocol("WM_DELETE_WINDOW", on_plot_close) # 主窗口关闭时同步关闭绘图窗口 self.root.protocol("WM_DELETE_WINDOW", self.on_main_close) def on_main_close(self): if self.plot_window is not None and self.plot_window.winfo_exists(): self.plot_window.destroy() self.root.destroy() def clicked(self, event): try: rclicked = self.pt.get_row_clicked(event) cclicked = self.pt.get_col_clicked(event) clicks = (rclicked, cclicked) except: print('Error getting clicked row/column') if clicks: try: ROW = self.pt.model.getRecordAtRow(clicks[0]) # 用partial绑定参数,避免lambda的变量捕获问题 self.button1.configure(command=partial( self.func1, self.unfiltered_df.loc[self.unfiltered_df['ReceiverID'] == ROW[0]] )) except: print('No record found at:', clicks) if __name__ == "__main__": root = tk.Tk() app = YourApp(root) root.mainloop()
关键改进点:
- 用
Toplevel替代原生Matplotlib窗口,实现窗口生命周期与主窗口绑定 - 新增
self.plot_window跟踪窗口实例,防止重复打开 - 绘图窗口关闭时触发回调恢复按钮状态
- 主窗口关闭前先关闭绘图窗口,避免残留进程
- 使用
functools.partial绑定按钮命令参数,解决lambda的变量捕获BUG
方案2:主窗口内嵌入图表(布局整洁版)
若不需要独立窗口,可在主窗口中划分专门的图表区域,避免布局混乱:
import tkinter as tk from tkinter import ttk import pandas as pd import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from functools import partial class YourApp: def __init__(self, root): self.root = root self.root.title("Main Window") # 顶部容器:Pandastable + 图表区域 self.top_container = ttk.Frame(self.root) self.top_container.pack(fill=tk.BOTH, expand=True) # Pandastable放置区域 # self.pt_frame = ttk.Frame(self.top_container) # self.pt_frame.pack(fill=tk.BOTH, expand=True) # self.pt = Table(self.pt_frame, ...) # self.pt.pack(fill=tk.BOTH, expand=True) # 图表容器(初始为空) self.chart_container = ttk.Frame(self.top_container) self.chart_container.pack(fill=tk.BOTH, expand=True) # 底部按钮容器 self.button_frame = ttk.Frame(self.root) self.button_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=5, pady=5) self.button1 = ttk.Button(self.button_frame, width=25, text="Plot 0") self.button1.pack(side="left", padx=2) # 记录当前图表实例 self.current_chart = None def func2(self, dictionary): # 禁用按钮 self.button1['state'] = 'disabled' # 清除旧图表 if self.current_chart is not None: self.current_chart.get_tk_widget().destroy() # 绘制新图表 df = pd.DataFrame.from_dict(dictionary) fig, ax = plt.subplots(figsize=(8, 4)) df.boxplot(column='RSSI', by='SenderID', ax=ax) ax.set_title('Antenna A Ch 1 RSSI') ax.set_xlabel('SenderID') ax.set_ylabel('RSSI') plt.suptitle('') # 嵌入图表到容器 canvas = FigureCanvasTkAgg(fig, master=self.chart_container) canvas.draw() canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self.current_chart = canvas # 添加关闭图表按钮 close_btn = ttk.Button(self.chart_container, text="关闭图表", command=self.close_chart) close_btn.pack(side=tk.BOTTOM, pady=2) def close_chart(self): if self.current_chart is not None: self.current_chart.get_tk_widget().destroy() self.current_chart = None self.button1['state'] = 'normal' def clicked(self, event): try: rclicked = self.pt.get_row_clicked(event) cclicked = self.pt.get_col_clicked(event) clicks = (rclicked, cclicked) except: print('Error getting clicked row/column') if clicks: try: ROW = self.pt.model.getRecordAtRow(clicks[0]) self.button1.configure(command=partial( self.func2, self.unfiltered_df.loc[self.unfiltered_df['ReceiverID'] == ROW[0]] )) except: print('No record found at:', clicks) if __name__ == "__main__": root = tk.Tk() app = YourApp(root) root.mainloop()
关键改进点:
- 用
top_container和button_frame分离布局,图表独立放置在专门容器中 - 每次绘图前清除旧图表,保持界面整洁
- 新增"关闭图表"按钮,点击后恢复按钮状态
- 布局逻辑清晰,主窗口关闭时图表自动销毁
内容的提问来源于stack exchange,提问作者Oshua
相关产品推荐
相关产品推荐

