Python 带网格的tkinter画布滚动条?
Tkinter和Python相对较新。请容忍我 我试图显示以下GUI,希望在Frame2中有一个滚动条,一次只显示5x5个按钮。看起来Tkinter框架不支持滚动条,因此在父框架“FMas”中添加了一个画布(框架嵌入其中)和一个滚动条。但由于某些原因,滚动条会转到屏幕的右端,不会进行任何滚动 画布不应该在Frame2的边缘结束,滚动条不应该就在它旁边吗?此外,我还尝试rowspan增加滚动条的高度,以匹配5x5按钮的高度。那也不行 代码(使用Python3.2):Python 带网格的tkinter画布滚动条?,python,canvas,tkinter,grid,scrollbar,Python,Canvas,Tkinter,Grid,Scrollbar,Tkinter和Python相对较新。请容忍我 我试图显示以下GUI,希望在Frame2中有一个滚动条,一次只显示5x5个按钮。看起来Tkinter框架不支持滚动条,因此在父框架“FMas”中添加了一个画布(框架嵌入其中)和一个滚动条。但由于某些原因,滚动条会转到屏幕的右端,不会进行任何滚动 画布不应该在Frame2的边缘结束,滚动条不应该就在它旁边吗?此外,我还尝试rowspan增加滚动条的高度,以匹配5x5按钮的高度。那也不行 代码(使用Python3.2): 滚动条的高度与按钮框的高度不
滚动条的高度与按钮框的高度不匹配,因为您没有告诉滚动条粘贴南北
.grid(…,sticky='ns')
然后,您想要实现的滚动行为如下所述:
另请参见@的答案,了解更通用的2D滚动(水平和垂直)面向对象解决方案
虽然这是一个有点过时的问题,但这里有一个不同的答案,它不使用
tkinter
事件处理,从而避免了不必要的开销
虽然代码是从OP派生的,但我对代码格式做了一些更改,使其更好地符合规范,这导致许多变量名被更改。我还修改了架构,使应用程序成为roottkinter.Tk
windowwidget类的子类。我这样做的目的是希望结果更容易理解,并为编写类似的基于tkinter
的应用程序提供更好的模板
与@的答案类似,它将画布
和每个滚动条
小部件嵌套在另一个框架
中,这样就可以使用tkinter
的grid()
布局管理器轻松地将它们垂直和水平放置在一起
该代码已进一步扩展,因此网格也有一个水平滚动条,允许在该方向以及垂直方向滚动其内容
import tkinter as tk
LABEL_BG = "#ccc" # Light gray.
ROWS, COLS = 10, 6 # Size of grid.
ROWS_DISP = 3 # Number of rows to display.
COLS_DISP = 4 # Number of columns to display.
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.GROOVE)
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=2)
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)
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 buttons.
buttons_frame = tk.Frame(canvas, bg="Red", bd=2)
# Add the buttons to the frame.
for i in range(1, ROWS+1):
for j in range(1, COLS+1):
button = tk.Button(buttons_frame, padx=7, pady=7, relief=tk.RIDGE,
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.
#print('canvas.bbox(tk.ALL): {}'.format(bbox))
# 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.GROOVE)
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=2)
if __name__ == "__main__":
app = MyApp("Scrollable Canvas")
app.mainloop()
以下是跑步的感觉:
效果很好!谢谢。还有一个问题。为什么我必须绑定resize函数来配置frame_按钮的更改?为什么在用按钮填充框架后,我只写“Can1.configure(scrollregion=Can1.bbox(“all”)、width=235、height=190)”时它就不起作用了。PS:我试过了,但失败了。Josselin:经过一些实验,我发现你不需要定义一个函数,也不需要将它绑定到
框架按钮上。只需调用frame\u按钮。在调用Can1.configure(scrollregion=Can1.bbox(“全部”)、width=235、height=190)之前更新\u idletasks()
。您还可以从Can1.bbox(“all”)
返回的边界框中计算宽度和高度值,而不是像以前那样对它们进行硬编码。@martineau:没错,“
事件绑定是不必要的,对小部件大小的计算可以避免硬编码任何尺寸。我相应地更新了我的答案,并添加了一个指向您新的更一般答案的链接;)嗨,我正在尝试学习这些东西,在一个空闲的shell中使用最新的Python。当我运行您的代码时,我遇到了这个错误:buttons=[[tk.Button()代表xrange中的j(列)]代表xrange中的I(行)]NameError:name“xrange”没有定义,您能帮忙吗?Hi@Pedroski,xrange
在Python 3中被重命名。如果将xrange
替换为range
,它应该可以工作(我也更新了答案中的代码)
import tkinter as tk
root = tk.Tk()
root.grid_rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
frame_main = tk.Frame(root, bg="gray")
frame_main.grid(sticky='news')
label1 = tk.Label(frame_main, text="Label 1", fg="green")
label1.grid(row=0, column=0, pady=(5, 0), sticky='nw')
label2 = tk.Label(frame_main, text="Label 2", fg="blue")
label2.grid(row=1, column=0, pady=(5, 0), sticky='nw')
label3 = tk.Label(frame_main, text="Label 3", fg="red")
label3.grid(row=3, column=0, pady=5, sticky='nw')
# Create a frame for the canvas with non-zero row&column weights
frame_canvas = tk.Frame(frame_main)
frame_canvas.grid(row=2, column=0, pady=(5, 0), sticky='nw')
frame_canvas.grid_rowconfigure(0, weight=1)
frame_canvas.grid_columnconfigure(0, weight=1)
# Set grid_propagate to False to allow 5-by-5 buttons resizing later
frame_canvas.grid_propagate(False)
# Add a canvas in that frame
canvas = tk.Canvas(frame_canvas, bg="yellow")
canvas.grid(row=0, column=0, sticky="news")
# Link a scrollbar to the canvas
vsb = tk.Scrollbar(frame_canvas, orient="vertical", command=canvas.yview)
vsb.grid(row=0, column=1, sticky='ns')
canvas.configure(yscrollcommand=vsb.set)
# Create a frame to contain the buttons
frame_buttons = tk.Frame(canvas, bg="blue")
canvas.create_window((0, 0), window=frame_buttons, anchor='nw')
# Add 9-by-5 buttons to the frame
rows = 9
columns = 5
buttons = [[tk.Button() for j in range(columns)] for i in range(rows)]
for i in range(0, rows):
for j in range(0, columns):
buttons[i][j] = tk.Button(frame_buttons, text=("%d,%d" % (i+1, j+1)))
buttons[i][j].grid(row=i, column=j, sticky='news')
# Update buttons frames idle tasks to let tkinter calculate buttons sizes
frame_buttons.update_idletasks()
# Resize the canvas frame to show exactly 5-by-5 buttons and the scrollbar
first5columns_width = sum([buttons[0][j].winfo_width() for j in range(0, 5)])
first5rows_height = sum([buttons[i][0].winfo_height() for i in range(0, 5)])
frame_canvas.config(width=first5columns_width + vsb.winfo_width(),
height=first5rows_height)
# Set the canvas scrolling region
canvas.config(scrollregion=canvas.bbox("all"))
# Launch the GUI
root.mainloop()
import tkinter as tk
LABEL_BG = "#ccc" # Light gray.
ROWS, COLS = 10, 6 # Size of grid.
ROWS_DISP = 3 # Number of rows to display.
COLS_DISP = 4 # Number of columns to display.
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.GROOVE)
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=2)
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)
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 buttons.
buttons_frame = tk.Frame(canvas, bg="Red", bd=2)
# Add the buttons to the frame.
for i in range(1, ROWS+1):
for j in range(1, COLS+1):
button = tk.Button(buttons_frame, padx=7, pady=7, relief=tk.RIDGE,
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.
#print('canvas.bbox(tk.ALL): {}'.format(bbox))
# 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.GROOVE)
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=2)
if __name__ == "__main__":
app = MyApp("Scrollable Canvas")
app.mainloop()