tkinter滚动框仅使用鼠标滚轮滚动笔记本中最后添加的页面



我正在尝试制作一个带有选项卡的应用程序。我会在选项卡上有很多按钮,所以我需要在每个选项卡上都有一个可滚动的框架。下面是我从这里获得的代码:

import tkinter as tk
from tkinter import ttk
# ************************
# Scrollable Frame Class
# ************************
class ScrollFrame(tk.Frame):
def __init__(self, parent):
super().__init__(parent) # create a frame (self)
self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff")          #place canvas on self
self.viewPort = tk.Frame(self.canvas, background="#ffffff")                    #place a frame on the canvas, this frame will hold the child widgets 
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) #place a scrollbar on self 
self.canvas.configure(yscrollcommand=self.vsb.set)                          #attach scrollbar action to scroll of canvas
self.vsb.pack(side="right", fill="y")                                       #pack scrollbar to right of self
self.canvas.pack(side="left", fill="both", expand=True)                     #pack canvas to left of self and expand to fil
self.canvas_window = self.canvas.create_window((4,4), window=self.viewPort, anchor="nw",            #add view port frame to canvas
tags="self.viewPort")
self.viewPort.bind("<Configure>", self.onFrameConfigure)                       #bind an event whenever the size of the viewPort frame changes.
self.canvas.bind("<Configure>", self.onCanvasConfigure)                       #bind an event whenever the size of the viewPort frame changes.
self.canvas.bind_all("<MouseWheel>", self.onScroll)                       #bind an event whenever the size of the viewPort frame changes.
self.onFrameConfigure(None)                                                 #perform an initial stretch on render, otherwise the scroll region has a tiny border until the first resize

def onFrameConfigure(self, event):                                              
'''Reset the scroll region to encompass the inner frame'''
self.canvas.configure(scrollregion=self.canvas.bbox("all"))                 #whenever the size of the frame changes, alter the scroll region respectively.
def onCanvasConfigure(self, event):
'''Reset the canvas window to encompass inner frame when required'''
canvas_width = event.width
self.canvas.itemconfig(self.canvas_window, width = canvas_width)            #whenever the size of the canvas changes alter the window region respectively.
def onScroll(self,event):
if event.state == 0:
self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
return "break"
elif event.state == 1:
self.canvas.xview_scroll(int(-1*(event.delta/120)), "units")
return "break"

# ********************************
# Example usage of the above class
# ********************************
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.scrollFrame = ScrollFrame(self) # add a new scrollable frame.

# Now add some controls to the scrollframe. 
# NOTE: the child controls are added to the view port (scrollFrame.viewPort, NOT scrollframe itself)
for row in range(100):
a = row
tk.Label(self.scrollFrame.viewPort, text="%s" % row, width=3, borderwidth="1", 
relief="solid").grid(row=row, column=0)
t="this is the second column for row %s" %row
tk.Button(self.scrollFrame.viewPort, text=t, command=lambda x=a: self.printMsg("Hello " + str(x))).grid(row=row, column=1)
# when packing the scrollframe, we pack scrollFrame itself (NOT the viewPort)
self.scrollFrame.pack(side="top", fill="both", expand=True)

def printMsg(self, msg):
print(msg)
if __name__ == "__main__":
root=tk.Tk()
main_frame = tk.Frame(root)
nb = ttk.Notebook(main_frame)
frame = Example(nb) 
#frame = tk.Frame(main_frame)
frame.pack()
nb.add(frame,text = "frame1")
frame1 = Example(nb) 
frame1.pack()
nb.add(frame1,text = "frame2")
nb.pack(side = "left")
main_frame.pack()
root.mainloop()

问题是我只能用鼠标滚轮滚动笔记本中最后添加的选项卡。对于单个选项卡,滚动框可以正常工作。但当添加多个选项卡时,我只能通过鼠标滚轮滚动最后添加的选项卡。

看起来画布小部件上的bind_all调用有问题,每次在ScrollableFrame中调用bind_all时,它都会替换回调。我为每个选项卡使用了bindtags和bind_class来分隔回调。以下是更新的代码:

import tkinter as tk
from tkinter import ttk
# ************************
# Scrollable Frame Class
# ************************
class ScrollFrame(tk.Frame):
def __init__(self, parent,frame_name):
super().__init__(parent) # create a frame (self)
self.canvas = tk.Canvas(self, borderwidth=0, background="#ffffff")          #place canvas on self
self.viewPort = tk.Frame(self.canvas, background="#ffffff")                    #place a frame on the canvas, this frame will hold the child widgets 
self.vsb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) #place a scrollbar on self 
self.canvas.configure(yscrollcommand=self.vsb.set)                          #attach scrollbar action to scroll of canvas
self.vsb.pack(side="right", fill="y")                                       #pack scrollbar to right of self
self.canvas.pack(side="left", fill="both", expand=True)                     #pack canvas to left of self and expand to fil
self.canvas_window = self.canvas.create_window((4,4), window=self.viewPort, anchor="nw",            #add view port frame to canvas
tags="self.viewPort")
self.viewPort.bind("<Configure>", self.onFrameConfigure)                       #bind an event whenever the size of the viewPort frame changes.
self.canvas.bind("<Configure>", self.onCanvasConfigure)                       #bind an event whenever the size of the viewPort frame changes.
self.viewPort.bindtags((frame_name,)+(self.viewPort.bindtags()))
self.viewPort.bind_class(frame_name,"<MouseWheel>", self.onScroll)                       #bind an event whenever the size of the viewPort frame changes.
self.onFrameConfigure(None)                                                 #perform an initial stretch on render, otherwise the scroll region has a tiny border until the first resize

def onFrameConfigure(self, event):                                              
'''Reset the scroll region to encompass the inner frame'''
self.canvas.configure(scrollregion=self.canvas.bbox("all"))                 #whenever the size of the frame changes, alter the scroll region respectively.
def onCanvasConfigure(self, event):
'''Reset the canvas window to encompass inner frame when required'''
canvas_width = event.width
self.canvas.itemconfig(self.canvas_window, width = canvas_width)            #whenever the size of the canvas changes alter the window region respectively.
def onScroll(self,event):
print(event)
if event.state == 0:
self.canvas.yview_scroll(int(-1*(event.delta/120)), "units")
return "break"
elif event.state == 1:
self.canvas.xview_scroll(int(-1*(event.delta/120)), "units")
return "break"

# ********************************
# Example usage of the above class
# ********************************
class Example(tk.Frame):
def __init__(self, root,frame):
tk.Frame.__init__(self, root)
self.scrollFrame = ScrollFrame(self,frame) # add a new scrollable frame.

# Now add some controls to the scrollframe. 
# NOTE: the child controls are added to the view port (scrollFrame.viewPort, NOT scrollframe itself)
for row in range(100):
a = row
label = tk.Label(self.scrollFrame.viewPort, text="%s" % row, width=3, borderwidth="1", 
relief="solid")
label.grid(row=row, column=0)
label.bindtags((frame,)+(label.bindtags()))
t="this is the second column for row %s" %row

button = tk.Button(self.scrollFrame.viewPort, text=t, command=lambda x=a: self.scrollFrame.canvas.yview_scroll(1,"units"))
button.grid(row=row, column=1)
button.bindtags((frame,)+(button.bindtags()))
# when packing the scrollframe, we pack scrollFrame itself (NOT the viewPort)
self.scrollFrame.pack(side="top", fill="both", expand=True)

def printMsg(self, msg):
print(msg)
if __name__ == "__main__":
root=tk.Tk()
main_frame = tk.Frame(root)
nb = ttk.Notebook(main_frame)
frame1 = Example(nb,"frame1")
frame1.pack()
nb.add(frame1,text = "frame1")
frame2 = Example(nb,"frame2") 
frame2.pack()
nb.add(frame2,text = "frame2")
nb.pack(side = "left")
main_frame.pack()
root.bind_all("<FocusIn>", lambda e : print(root.focus_get(),flush=True))
root.mainloop()

最新更新