求助:Tkinter中Canvas关联水平滚动条失效问题
Tkinter嵌入Matplotlib图表水平滚动失效问题解决
问题描述
我是Python及Tkinter新手,计划用Tkinter搭建仪表盘,通过Matplotlib将4个图表嵌入界面。但图表无法全部显示,添加水平滚动条后无法正常工作,希望实现滚动条正常查看所有图表,原代码如下:
import tkinter as tk from tkinter import ttk from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import matplotlib.pyplot as plt import numpy as np def calculate_gap(sum_of_demand, allocated, capacity): if sum_of_demand - allocated < 0: sum_of_not_demanded = abs(sum_of_demand - allocated) elif capacity - sum_of_demand > 0: sum_of_not_demanded = capacity - sum_of_demand else: sum_of_not_demanded = 0 if capacity - allocated > 0: spare_capacity = capacity - allocated else: spare_capacity = 0 if sum_of_demand - capacity > 0: skill_gap = sum_of_demand - capacity else: skill_gap = 0 return sum_of_not_demanded, spare_capacity, skill_gap def h_scroll(*args): print("In scroll") graph1_canvas.get_tk_widget().xview(*args) graph2_canvas.get_tk_widget().xview(*args) graph3_canvas.get_tk_widget().xview(*args) graph4_canvas.get_tk_widget().xview(*args) def test_gap(): sum_of_demand = 256 allocated = 73 capacity = 66 sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity) width = 0.35 below = np.array([sum_of_demand, allocated, capacity]) above = np.array([sum_of_not_demanded, spare_capacity, skill_gap]) weight_counts = { "Below": np.array([sum_of_demand, allocated, capacity]), "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap]) } title = ['Demand', 'Allocated', 'Capacity'] bottom = np.zeros(3) for boolean, weight_count in weight_counts.items(): p = graph4_ax.bar(title, weight_count, width, label=boolean, bottom=bottom) bottom += weight_count graph4_canvas.draw() graph4_canvas.get_tk_widget().pack(fill="both", expand=True, side="left") graph4_canvas.get_tk_widget().config(scrollregion=graph4_canvas.get_tk_widget().bbox("all")) graph4_canvas.get_tk_widget().config(xscrollcommand=horizontal_scrollbar.set, scrollregion=graph4_canvas.get_tk_widget().bbox("all")) def graph_three(): sum_of_demand = 256 allocated = 73 capacity = 66 sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity) if sum_of_demand - capacity > 0: skill_gap = sum_of_demand - capacity else: skill_gap = 0 width = 0.1 weight_counts = { "Below": np.array([sum_of_demand, allocated, capacity]), "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap]) } title = ['Demand', 'Allocated', 'Capacity'] bottom = np.zeros(3) for boolean, weight_count in weight_counts.items(): p = graph_ax.bar(title, weight_count, width, label=boolean, bottom=bottom) bottom += weight_count graph3_canvas.draw() graph3_canvas.get_tk_widget().pack(fill="both", expand=True, side="left") graph3_canvas.get_tk_widget().config(scrollregion=graph3_canvas.get_tk_widget().bbox("all")) graph3_canvas.get_tk_widget().config(xscrollcommand=horizontal_scrollbar.set, scrollregion=graph3_canvas.get_tk_widget().bbox("all")) def graph_two(): sum_of_demand = 830 allocated = 313 capacity = 339 sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity) width = 0.35 weight_counts = { "Below": np.array([sum_of_demand, allocated, capacity]), "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap]) } title = ['Demand', 'Allocated', 'Capacity'] bottom = np.zeros(3) for boolean, weight_count in weight_counts.items(): p = graph2_ax.bar(title, width, label=boolean, bottom=bottom) bottom += weight_count graph2_canvas.draw() graph2_canvas.get_tk_widget().pack(fill="both", expand=True, side="left") graph2_canvas.get_tk_widget().config(scrollregion=graph2_canvas.get_tk_widget().bbox("all")) graph2_canvas.get_tk_widget().config(xscrollcommand=horizontal_scrollbar.set, scrollregion=graph2_canvas.get_tk_widget().bbox("all")) def graph_one(): sum_of_demand = 1631 allocated = 778 capacity = 795 sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity) width = 0.35 weight_counts = { "Below": np.array([sum_of_demand, allocated, capacity]), "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap]) } title = ['Demand', 'Allocated', 'Capacity'] bottom = np.zeros(3) for boolean, weight_count in weight_counts.items(): p = graph1_ax.bar(title, weight_count, width, label=boolean, bottom=bottom) bottom += weight_count graph1_canvas.draw() graph1_canvas.get_tk_widget().config(scrollregion=graph1_canvas.get_tk_widget().bbox("all")) graph1_canvas.get_tk_widget().config(xscrollcommand=horizontal_scrollbar.set, scrollregion=graph1_canvas.get_tk_widget().bbox("all")) root = tk.Tk() # Main Frame frame = ttk.Frame(root) frame.pack() # Data Frame graph_frame = ttk.Labelframe(frame, text="Skill Gap") graph_frame.grid(side = tk.RIGHT, expand = True, fill = tk.BOTH) horizontal_scrollbar = ttk.Scrollbar(graph_frame, orient=tk.HORIZONTAL, command=h_scroll) horizontal_scrollbar.pack(side=tk.BOTTOM, fill=tk.X) # Canvas graph1_fig, graph1_ax = plt.subplots(dpi=100) graph1_canvas = FigureCanvasTkAgg(graph1_fig, graph_frame) horizontal_scrollbar.config(command=graph1_canvas.get_tk_widget().xview) graph1_canvas.get_tk_widget().config(yscrollcommand=horizontal_scrollbar.set) # Canvas graph2_fig, graph2_ax = plt.subplots(dpi=100) graph2_canvas = FigureCanvasTkAgg(graph2_fig, graph_frame) # Canvas graph3_fig, graph_ax = plt.subplots(dpi=100) graph3_canvas = FigureCanvasTkAgg(graph3_fig, graph_frame) # Canvas graph4_fig, graph4_ax = plt.subplots(dpi=100) graph4_canvas = FigureCanvasTkAgg(graph4_fig, graph_frame) graph_one() graph_two() graph_three() test_gap() horizontal_scrollbar.config(command=h_scroll) tk.mainloop()
问题分析
- 无统一滚动容器:直接将图表canvas打包到
graph_frame,滚动条无法同时控制多个图表的水平滚动 - 图表绘制错误:
graph_two函数中bar方法参数错误,将width作为数据传入,导致图表显示异常 - 滚动绑定混乱:滚动条的
command多次重复设置,未关联到统一的滚动容器 - 滚动范围设置时机错误:未在所有图表绘制完成后统一计算滚动区域
修正后的代码
import tkinter as tk from tkinter import ttk from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import matplotlib.pyplot as plt import numpy as np def calculate_gap(sum_of_demand, allocated, capacity): if sum_of_demand - allocated < 0: sum_of_not_demanded = abs(sum_of_demand - allocated) elif capacity - sum_of_demand > 0: sum_of_not_demanded = capacity - sum_of_demand else: sum_of_not_demanded = 0 if capacity - allocated > 0: spare_capacity = capacity - allocated else: spare_capacity = 0 if sum_of_demand - capacity > 0: skill_gap = sum_of_demand - capacity else: skill_gap = 0 return sum_of_not_demanded, spare_capacity, skill_gap def test_gap(): sum_of_demand = 256 allocated = 73 capacity = 66 sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity) width = 0.35 weight_counts = { "Below": np.array([sum_of_demand, allocated, capacity]), "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap]) } title = ['Demand', 'Allocated', 'Capacity'] bottom = np.zeros(3) for boolean, weight_count in weight_counts.items(): p = graph4_ax.bar(title, weight_count, width, label=boolean, bottom=bottom) bottom += weight_count graph4_canvas.draw() graph4_canvas.get_tk_widget().pack(fill="both", expand=True, side="left", padx=5, pady=5) def graph_three(): sum_of_demand = 256 allocated = 73 capacity = 66 sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity) width = 0.1 weight_counts = { "Below": np.array([sum_of_demand, allocated, capacity]), "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap]) } title = ['Demand', 'Allocated', 'Capacity'] bottom = np.zeros(3) for boolean, weight_count in weight_counts.items(): p = graph3_ax.bar(title, weight_count, width, label=boolean, bottom=bottom) bottom += weight_count graph3_canvas.draw() graph3_canvas.get_tk_widget().pack(fill="both", expand=True, side="left", padx=5, pady=5) def graph_two(): sum_of_demand = 830 allocated = 313 capacity = 339 sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity) width = 0.35 weight_counts = { "Below": np.array([sum_of_demand, allocated, capacity]), "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap]) } title = ['Demand', 'Allocated', 'Capacity'] bottom = np.zeros(3) # 修复bar方法参数错误:将width替换为weight_count for boolean, weight_count in weight_counts.items(): p = graph2_ax.bar(title, weight_count, width, label=boolean, bottom=bottom) bottom += weight_count graph2_canvas.draw() graph2_canvas.get_tk_widget().pack(fill="both", expand=True, side="left", padx=5, pady=5) def graph_one(): sum_of_demand = 1631 allocated = 778 capacity = 795 sum_of_not_demanded, spare_capacity, skill_gap = calculate_gap(sum_of_demand, allocated, capacity) width = 0.35 weight_counts = { "Below": np.array([sum_of_demand, allocated, capacity]), "Above": np.array([sum_of_not_demanded, spare_capacity, skill_gap]) } title = ['Demand', 'Allocated', 'Capacity'] bottom = np.zeros(3) for boolean, weight_count in weight_counts.items(): p = graph1_ax.bar(title, weight_count, width, label=boolean, bottom=bottom) bottom += weight_count graph1_canvas.draw() graph1_canvas.get_tk_widget().pack(fill="both", expand=True, side="left", padx=5, pady=5) root = tk.Tk() root.title("Skill Gap Dashboard") # Main Frame frame = ttk.Frame(root) frame.pack(fill=tk.BOTH, expand=True) # Data Frame graph_frame = ttk.Labelframe(frame, text="Skill Gap") graph_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 滚动容器:创建canvas和内部frame scroll_canvas = tk.Canvas(graph_frame) inner_frame = ttk.Frame(scroll_canvas) # 水平滚动条 horizontal_scrollbar = ttk.Scrollbar(graph_frame, orient=tk.HORIZONTAL, command=scroll_canvas.xview) scroll_canvas.configure(xscrollcommand=horizontal_scrollbar.set) # 布局滚动组件 horizontal_scrollbar.pack(side=tk.BOTTOM, fill=tk.X) scroll_canvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True) # 将内部frame嵌入canvas canvas_window = scroll_canvas.create_window((0, 0), window=inner_frame, anchor=tk.NW) # 绑定内部frame大小变化事件,更新canvas滚动范围 def update_scroll_region(event): scroll_canvas.configure(scrollregion=scroll_canvas.bbox("all")) inner_frame.bind("<Configure>", update_scroll_region) # 创建图表:将图表的父容器改为inner_frame graph1_fig, graph1_ax = plt.subplots(dpi=100) graph1_canvas = FigureCanvasTkAgg(graph1_fig, inner_frame) graph2_fig, graph2_ax = plt.subplots(dpi=100) graph2_canvas = FigureCanvasTkAgg(graph2_fig, inner_frame) graph3_fig, graph3_ax = plt.subplots(dpi=100) graph3_canvas = FigureCanvasTkAgg(graph3_fig, inner_frame) graph4_fig, graph4_ax = plt.subplots(dpi=100) graph4_canvas = FigureCanvasTkAgg(graph4_fig, inner_frame) # 绘制所有图表 graph_one() graph_two() graph_three() test_gap() tk.mainloop()
关键修改说明
- 统一滚动容器:新增
scroll_canvas作为滚动载体,所有图表放入inner_frame后嵌入该canvas,滚动条通过控制scroll_canvas.xview实现统一滚动 - 修复图表绘制错误:修正
graph_two中bar方法的参数,将错误传入的width替换为实际数据weight_count - 优化布局逻辑:所有图表统一打包到
inner_frame,横向排列并添加间距 - 自动更新滚动范围:绑定
inner_frame的大小变化事件,实时更新scroll_canvas的滚动区域,确保滚动范围覆盖所有图表
内容的提问来源于stack exchange,提问作者Chandrakanth Reddy
相关产品推荐
相关产品推荐

