虽然这是一个有点过时的问题,但这里有一个不同的答案,它不使用 tkinter 事件处理,从而避免了它所需的不必要的开销。
虽然代码是从 OP 派生的,但我已经进行了一些代码格式更改,因此它更符合 PEP 8 - Style Guide for Python Code,这导致许多变量名称被更改。我还修改了体系结构,因此应用程序是根 tkinter.Tk 窗口小部件类的子类。我做这些事情是希望结果更容易理解,并为编写类似的基于tkinter 的应用程序提供更好的模板。
就像@Josselin 的答案一样,它将Canvas 和每个Scrollbar 小部件嵌套在另一个Frame 中,这使得它们可以使用tkinter 轻松地在垂直和水平方向上并排放置的grid() 布局管理器。
代码已进一步扩展,因此网格还有一个水平滚动条,允许在该方向和垂直方向滚动其内容。
import tkinter as tk
LABEL_BG = 'light grey'
ROWS, COLS = 10, 6 # Size of grid.
ROWS_DISP = 3 # Number of rows to display.
COLS_DISP = 4 # Number of columns to display.
class HoverButton(tk.Button):
""" Button that changes color to activebackground when mouse is over it. """
def __init__(self, master, **kw):
super().__init__(master=master, **kw)
self.default_Background = self.cget('background')
self.hover_Background = self.cget('activebackground')
self.bind('<Enter>', self.on_enter)
self.bind('<Leave>', self.on_leave)
def on_enter(self, e):
self.config(background=self.hover_Background)
def on_leave(self, e):
self.config(background=self.default_Background)
class MyApp(tk.Tk):
def __init__(self, title='Sample App', *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title(title)
self.configure(background='Gray')
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
master_frame = tk.Frame(self, bg='Light Blue', bd=3, relief=tk.RIDGE)
master_frame.grid(sticky=tk.NSEW)
master_frame.columnconfigure(0, weight=1)
label1 = tk.Label(master_frame, text='Frame1 Contents', bg=LABEL_BG)
label1.grid(row=0, column=0, pady=5, sticky=tk.NW)
frame1 = tk.Frame(master_frame, bg='Green', bd=2, relief=tk.FLAT)
frame1.grid(row=1, column=0, sticky=tk.NW)
cb_var1 = tk.IntVar()
checkbutton1 = tk.Checkbutton(frame1, text='StartCheckBox', variable=cb_var1)
checkbutton1.grid(row=0, column=0, padx=0, pady=0)
label2 = tk.Label(master_frame, text='Frame2 Contents', bg=LABEL_BG)
label2.grid(row=2, column=0, pady=5, sticky=tk.NW)
# Create a frame for the canvas and scrollbar(s).
frame2 = tk.Frame(master_frame, bg='Red', bd=2, relief=tk.FLAT)
frame2.grid(row=3, column=0, sticky=tk.NW)
# Add a canvas in that frame.
canvas = tk.Canvas(frame2, bg='Yellow')
canvas.grid(row=0, column=0)
# Create a vertical scrollbar linked to the canvas.
vsbar = tk.Scrollbar(frame2, orient=tk.VERTICAL, command=canvas.yview)
vsbar.grid(row=0, column=1, sticky=tk.NS)
canvas.configure(yscrollcommand=vsbar.set)
# Create a horizontal scrollbar linked to the canvas.
hsbar = tk.Scrollbar(frame2, orient=tk.HORIZONTAL, command=canvas.xview)
hsbar.grid(row=1, column=0, sticky=tk.EW)
canvas.configure(xscrollcommand=hsbar.set)
# Create a frame on the canvas to contain the grid of buttons.
buttons_frame = tk.Frame(canvas)
# Add the buttons to the frame.
for i in range(1, ROWS+1):
for j in range(1, COLS+1):
button = HoverButton(buttons_frame, padx=7, pady=7, relief=tk.RIDGE,
activebackground= 'orange', text='[%d, %d]' % (i, j))
button.grid(row=i, column=j, sticky='news')
# Create canvas window to hold the buttons_frame.
canvas.create_window((0,0), window=buttons_frame, anchor=tk.NW)
buttons_frame.update_idletasks() # Needed to make bbox info available.
bbox = canvas.bbox(tk.ALL) # Get bounding box of canvas with Buttons.
# Define the scrollable region as entire canvas with only the desired
# number of rows and columns displayed.
w, h = bbox[2]-bbox[1], bbox[3]-bbox[1]
dw, dh = int((w/COLS) * COLS_DISP), int((h/ROWS) * ROWS_DISP)
canvas.configure(scrollregion=bbox, width=dw, height=dh)
label3 = tk.Label(master_frame, text='Frame3 Contents', bg=LABEL_BG)
label3.grid(row=4, column=0, pady=5, sticky=tk.NW)
frame3 = tk.Frame(master_frame, bg='Blue', bd=2, relief=tk.FLAT)
frame3.grid(row=5, column=0, sticky=tk.NW)
cb_var2 = tk.IntVar()
checkbutton2 = tk.Checkbutton(frame3, text='EndCheckBox', variable=cb_var2)
checkbutton2.grid(row=0, column=0, padx=0, pady=0)
if __name__ == '__main__':
app = MyApp('Scrollable Canvas')
app.mainloop()
这是跑步的样子(略微放大):