【Python】tkinter实现matplotlib绘制图形和多个组件一起的全窗口滚动并绑定鼠标滚轮进行滚动
当前情况是,因为一个窗口界面需要展示的东西很多,如果不加滚动所有组件数值就会挤在一起。现在需要添加整个窗口的滚动条来查看内容。注意是整个窗口实现滚动而不是一个组件的滚动。
首先在stack overflow [3] 找到 [1] 示例代码,即,将整个窗口添加滚动条而无需将所有内容都放入框架中
from tkinter import *
from tkinter import ttk
root = Tk()
root.title('Full Window Scrolling X Y Scrollbar Example')
root.geometry("1350x400")
# Create A Main frame
main_frame = Frame(root)
main_frame.pack(fill=BOTH,expand=1)
# Create Frame for X Scrollbar
sec = Frame(main_frame)
sec.pack(fill=X,side=BOTTOM)
# Create A Canvas
my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT,fill=BOTH,expand=1)
# Add A Scrollbars to Canvas
x_scrollbar = ttk.Scrollbar(sec,orient=HORIZONTAL,command=my_canvas.xview)
x_scrollbar.pack(side=BOTTOM,fill=X)
y_scrollbar = ttk.Scrollbar(main_frame,orient=VERTICAL,command=my_canvas.yview)
y_scrollbar.pack(side=RIGHT,fill=Y)
# Configure the canvas
my_canvas.configure(xscrollcommand=x_scrollbar.set)
my_canvas.configure(yscrollcommand=y_scrollbar.set)
my_canvas.bind("<Configure>",lambda e: my_canvas.config(scrollregion= my_canvas.bbox(ALL)))
# Create Another Frame INSIDE the Canvas
second_frame = Frame(my_canvas)
# Add that New Frame a Window In The Canvas
my_canvas.create_window((0,0),window= second_frame, anchor="nw")
for thing in range(100):
Button(second_frame ,text=f"Button {thing}").grid(row=5,column=thing,pady=10,padx=10)
for thing in range(100):
Button(second_frame ,text=f"Button {thing}").grid(row=thing,column=5,pady=10,padx=10)
root.mainloop()
执行代码后可以看到窗口长这样 ,通过水平竖直滚动条都能查看到后面的内容
根据示例代码可以明白需要加类似于这三类部分:创建滚动条并设置位置、组件关联滚动条使内容可以随着滚动条滚动、创建一个新框架用于放置自己的功能组件
# Add A Scrollbars to Canvas
x_scrollbar = ttk.Scrollbar(sec,orient=HORIZONTAL,command=my_canvas.xview)
x_scrollbar.pack(side=BOTTOM,fill=X)
y_scrollbar = ttk.Scrollbar(main_frame,orient=VERTICAL,command=my_canvas.yview)
y_scrollbar.pack(side=RIGHT,fill=Y)
# Configure the canvas
my_canvas.configure(xscrollcommand=x_scrollbar.set)
my_canvas.configure(yscrollcommand=y_scrollbar.set)
my_canvas.bind("<Configure>",lambda e: my_canvas.config(scrollregion= my_canvas.bbox(ALL)))
# Add that New Frame a Window In The Canvas
my_canvas.create_window((0,0),window= second_frame, anchor="nw")
接下来用另一个示例代码看是否复刻成功
import tkinter as tk
from tkinter import ttk
# 创建主窗口
result_window = tk.Tk()
result_window.title('Scrollable Lists and Histograms')
result_window.geometry("800x600")
# 创建一个Canvas作为主框架
canvas = tk.Canvas(result_window)
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
# 创建一个垂直滚动条
scrollbar = ttk.Scrollbar(result_window, orient=tk.VERTICAL, command=canvas.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 将Canvas的滚动区域设置为框架的边界
canvas.configure(yscrollcommand=scrollbar.set)
canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox('all')))
# 创建一个Frame来放置内容
frame_table = tk.Frame(canvas)
canvas.create_window((0, 0), window=frame_table, anchor='nw')
# 添加标题
label_title = tk.Label(frame_table, text="Scrollable Lists and Histograms", font=("Arial", 18))
label_title.grid(row=0, column=0, columnspan=3, pady=10)
# 创建左侧栏
left_frame = tk.Frame(frame_table)
left_frame.grid(row=1, column=0, padx=10, pady=10)
# 创建中间栏
middle_frame = tk.Frame(frame_table)
middle_frame.grid(row=1, column=1, padx=10, pady=10)
# 创建右侧栏
right_frame = tk.Frame(frame_table)
right_frame.grid(row=1, column=2, padx=10, pady=10)
# 创建列表和直方图
lists = []
histograms = []
for i in range(3):
listbox = tk.Listbox(left_frame, height=10)
lists.append(listbox)
for j in range(20):
listbox.insert(tk.END, f"List {i+1} - Item {j+1}")
listbox.pack(side=tk.TOP, padx=10, pady=10)
for i in range(2):
histogram = tk.Canvas(right_frame, bg="white", width=200, height=150)
histograms.append(histogram)
histogram.create_rectangle(20, 20, 180, 130, fill="blue")
histogram.pack(side=tk.TOP, padx=10, pady=10)
result_window.mainloop()
执行结果可知,可以通过鼠标按着滚动条拖动查看但无法用鼠标滚轮下滑,三个列表都可以用光标滚动翻看剩下的list,并且设置了两个直方图来做测试,看两个不同的组件能不能一起滚动而不是单个组件,现在全窗口滚动条可以拖动所有组件。这里只用了水平滚动条
接下来将标题、三个列表水平排列、两个图上下显示、三种组件上下排版再次进行测试
import tkinter as tk
from tkinter import ttk
# 创建主窗口
result_window = tk.Tk()
result_window.title('Scrollable Lists and Histograms')
result_window.geometry("800x600")
# 创建一个Canvas作为主框架
canvas = tk.Canvas(result_window)
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
# 创建一个垂直滚动条
scrollbar = ttk.Scrollbar(result_window, orient=tk.VERTICAL, command=canvas.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 将Canvas的滚动区域设置为框架的边界
canvas.configure(yscrollcommand=scrollbar.set)
canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox('all')))
# 创建一个Frame来放置内容
frame_table = tk.Frame(canvas)
canvas.create_window((0, 0), window=frame_table, anchor='nw')
# 添加标题
label_title = tk.Label(frame_table, text="Scrollable Lists and Histograms", font=("Arial", 18))
label_title.grid(row=0, column=0, columnspan=3, pady=10)
# 创建左侧栏
left_frame = tk.Frame(frame_table)
left_frame.grid(row=1, column=0, padx=10, pady=10)
# 创建中间栏
middle_frame = tk.Frame(frame_table)
middle_frame.grid(row=2, column=0, padx=10, pady=10)
# 创建右侧栏
right_frame = tk.Frame(frame_table)
right_frame.grid(row=3, column=0, padx=10, pady=10)
# 创建列表和直方图
lists = []
histograms = []
# 创建三个列表框水平排列
for i in range(3):
listbox = tk.Listbox(left_frame, height=10)
lists.append(listbox)
for j in range(20):
listbox.insert(tk.END, f"List {i+1} - Item {j+1}")
listbox.pack(side=tk.LEFT, padx=10, pady=10)
# 创建两个直方图并放在列表下方
for i in range(2):
histogram = tk.Canvas(middle_frame, bg="white", width=200, height=150)
histograms.append(histogram)
histogram.create_rectangle(20, 20, 180, 130, fill="blue")
histogram.pack(side=tk.TOP, padx=10, pady=10)
result_window.mainloop()
执行结果可以顺利用滚动条查看,但无法用鼠标滚轮
因为显示的图片设置很小,而我们需要做的是让他水平铺满尽可能多的面积,开始研究matplotlib绘制的图形怎么跟着全窗口来查看进行浏览。matplotlib直接嵌入窗口没什么难度,但是要实现其图形和其他组件排版并一起滚动就需要研究一下。
以下测试代码实现了让matplotlib绘制的图形嵌入在tkinter窗口并且可以实现拖动滚动条来进行浏览
import tkinter as tk
from tkinter import ttk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
def on_canvas_configure(event):
canvas.configure(scrollregion=canvas.bbox("all"))
# 创建Tkinter窗口
root = tk.Tk()
root.title("Matplotlib Histogram with Scrollbar in Tkinter")
# 创建带有滚动条的框架
frame = ttk.Frame(root)
frame.pack(fill=tk.BOTH, expand=True)
# 创建Canvas和垂直滚动条
canvas = tk.Canvas(frame)
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=canvas.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
canvas.configure(yscrollcommand=scrollbar.set)
canvas.bind("<Configure>", on_canvas_configure)
# 创建Matplotlib图形
fig = Figure(figsize=(6, 4))
ax = fig.add_subplot(111)
# 生成一些随机数据并绘制直方图
data = np.random.normal(loc=0, scale=1, size=1000)
ax.hist(data, bins=30, color='skyblue', alpha=0.7)
ax.set_title('Histogram')
ax.set_xlabel('Value')
ax.set_ylabel('Frequency')
# 将Matplotlib图形嵌入到Tkinter Canvas中
canvas_fig = FigureCanvasTkAgg(fig, master=canvas)
canvas_fig.draw()
canvas.create_window((0, 0), window=canvas_fig.get_tk_widget(), anchor='nw')
# 设置Canvas的滚动区域
canvas.configure(scrollregion=canvas.bbox("all"))
# 运行Tkinter事件循环
root.mainloop()
接下来为了将列表和图形嵌入到一起,通过这四个测试代码理一理结构,如下图所示
那么可以理出以下代码
# 创建新窗口
result_window = tk.Tk()
result_window.title("分析结果")
result_window.geometry("1280x720")
# 创建一个Canvas作为主框架
main_canvas = tk.Canvas(result_window)
main_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
# 创建一个垂直滚动条
scrollbar = ttk.Scrollbar(result_window, orient=tk.VERTICAL, command=main_canvas.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# 将Canvas的滚动区域设置为框架的边界
main_canvas.configure(yscrollcommand=scrollbar.set)
main_canvas.bind('<Configure>', lambda e: main_canvas.configure(scrollregion=main_canvas.bbox('all')))
# 创建一个Frame来放置内容
frame_table = tk.Frame(main_canvas)
frame1_window = main_canvas.create_window(0, 0, anchor=tk.NW, window=frame_table)
# 显示正在分析的文件名称
label_title = tk.Label(frame_table, text=f"正在分析文件:{folder_path}", font=("Helvetica", 12))
label_title.grid(row=0, column=0, columnspan=3, pady=10)
# 创建左侧栏
left_frame = tk.Frame(frame_table)
left_frame.grid(row=1, column=1, padx=10, pady=10)
# 创建中间栏
middle_frame = tk.Frame(frame_table)
middle_frame.grid(row=1, column=2, padx=10, pady=10)
# 创建右侧栏
right_frame = tk.Frame(frame_table)
right_frame.grid(row=1, column=3, padx=10, pady=10)
...
# 将绘制的图形嵌入到 Tkinter 窗口中
canvas_histogram = FigureCanvasTkAgg(fig, master=main_canvas)
canvas_histogram.draw()
canvas_histogram_widget = canvas_histogram.get_tk_widget()
canvas_histogram_widget.pack(side=tk.TOP, padx=10, pady=10, fill=tk.BOTH, expand=True)
frame2_window = main_canvas.create_window(0, 0, anchor=tk.NW, window=canvas_histogram_widget)
# 更新窗口
result_window.update()
# 进入 Tkinter 主事件循环
result_window.mainloop()
注意所有的组件和绘制的图形最后都要在canvas(代码中的main_canvas)进行显示(绑定),这样在canvas绑定的滚动条才能拖动所有内容
然后加入鼠标滚轮的功能,滚动canvas
def on_mouse_wheel(event):
delta = event.delta
speed = 3 # 滚动速度,您可以根据需要调整
for _ in range(abs(delta // 120)):
main_canvas.yview_scroll(int(-1 * (delta / abs(delta))), "units")
root.update_idletasks() # 更新窗口,确保平滑滚动
# 将鼠标滚轮事件绑定到Canvas上
main_canvas.bind_all("<MouseWheel>", on_mouse_wheel)
不通过直接规定组件大小来进行自适应上下排版,就再加入监听
def on_first_frame_resize(event):
# 获取第一个框架的实际高度
frame_height = event.height
# 将第二个框架放置在第一个框架的底部
main_canvas.coords(frame2_window, 0, frame_height)
# 监听第一个框架的大小变化
frame_table.bind("<Configure>", on_first_frame_resize)
上述的示意图中橘色框是一个框架,黄色框是第二个框架,要想让他们不拥挤就需要监听第一个框架的高度,这样就能合理进行窗口排版了。
【参考资料】
[1] 【python】Tkinter嵌入Matplotlib绘图:https://blog.csdn.net/west0425/article/details/134296273
[2] 整个窗口的 Python Tkinter 滚动条:https://www.coder.work/article/7904590
[3] Python Tkinter Scrollbar for entire window: https://stackoverflow.com/questions/19860047/python-tkinter-scrollbar-for-entire-window
[4] Python——Tkinter Scrollbar滚动条|窗口滑动条:https://blog.csdn.net/qq_60115503/article/details/124448995
[5] tkinter绘制组件(12)——表格:https://blog.csdn.net/tinga_kilin/article/details/119174198
[6] 在tkinter中如何为窗口添加滚动条:https://deepinout.com/tkinter/tkinter-questions/507_tkinter_how_to_add_a_scrollbar_to_a_window_with_tkinter.html#google_vignette
作者:hellenionia