【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

物联沃分享整理
物联沃-IOTWORD物联网 » 【Python】tkinter实现matplotlib绘制图形和多个组件一起的全窗口滚动并绑定鼠标滚轮进行滚动

发表回复