我正在尝试创建一个可滚动的Python Tkinter小部件,它可以包含其他小部件.下面的虚拟代码(主要取决于这个SO问题的答案,然后根据我的风格进行调整)似乎几乎完全符合我的要求:
import Tkinter as tk
class ScrollFrame(tk.Frame):
def __init__(self, root, *args, **kwargs):
# Start up self
tk.Frame.__init__(self, root, *args, **kwargs)
# Put a canvas in the frame (self), along with scroll bars
self.canvas = tk.Canvas(self)
self.horizontal_scrollbar = tk.Scrollbar(
self, orient="horizontal", command=self.canvas.xview
)
self.vertical_scrollbar = tk.Scrollbar(
self, orient="vertical", command=self.canvas.yview
)
self.canvas.configure(
yscrollcommand=self.vertical_scrollbar.set,
xscrollcommand=self.horizontal_scrollbar.set
)
# Put a frame in the canvas, to hold all the widgets
self.inner_frame = tk.Frame(self.canvas)
# Pack the scroll bars and the canvas (in self)
self.horizontal_scrollbar.pack(side="bottom", fill="x")
self.vertical_scrollbar.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((0,0), window=self.inner_frame, anchor="nw")
self.inner_frame.bind("<Configure>", self.OnFrameConfigure)
def OnFrameConfigure(self, event):
'''Reset the scroll region to encompass the inner frame'''
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
root = tk.Tk()
frame = ScrollFrame(root, borderwidth=2, relief="sunken")
labels = []
for i in range(10):
labels.append(
tk.Label(
frame.inner_frame, text="Row {}".format(i) + "_"*20
) # Unfortunately, this widget's parent cannot just be frame but has to be frame.inner_frame
)
frame.place(x=20, y=20, width=150, height=150)
for i,label in enumerate(labels):
label.grid(row=i,column=0)
#label.place(x=0, y=20*i, width=100, height=20)
root.mainloop()
Run Code Online (Sandbox Code Playgroud)
(有10个标签,每个标签最后都有一些垃圾邮件,用于测试垂直和水平滚动是否都有效.)
但是,对于我的实际应用程序,我需要非常精细地控制每个小部件的结束位置,这迫使我使用place几何管理器而不是grid.(注意我place()是框架.)但是,如果我用grid()每行使用的行替换每个标签的行,place()标签就不再显示了.(在上面的代码中,通过从底部注释掉第三行来模拟它,并从底部取消注释第二行.)
为什么这不起作用?我该如何解决?
编辑:接受的答案让我得到了以下代码,它按预期工作,通过传入inner_width和inner_height初始化ScrollFrame类,然后作为width和的height参数传递inner_frame:
import Tkinter as tk
class ScrollFrame(tk.Frame):
def __init__(self, root, inner_width, inner_height, *args, **kwargs):
# Start up self
tk.Frame.__init__(self, root, *args, **kwargs)
# Put a canvas in the frame (self)
self.canvas = tk.Canvas(self)
# Put scrollbars in the frame (self)
self.horizontal_scrollbar = tk.Scrollbar(
self, orient="horizontal", command=self.canvas.xview
)
self.vertical_scrollbar = tk.Scrollbar(
self, orient="vertical", command=self.canvas.yview
)
self.canvas.configure(
yscrollcommand=self.vertical_scrollbar.set,
xscrollcommand=self.horizontal_scrollbar.set
)
# Put a frame in the canvas, to hold all the widgets
self.inner_frame = tk.Frame(
self.canvas, width=inner_width, height=inner_height
)
# Pack the scroll bars and the canvas (in self)
self.horizontal_scrollbar.pack(side="bottom", fill="x")
self.vertical_scrollbar.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
self.canvas.create_window((0,0), window=self.inner_frame, anchor="nw")
self.inner_frame.bind("<Configure>", self.on_frame_configure)
def on_frame_configure(self, event):
"""Reset the scroll region to encompass the inner frame"""
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
root = tk.Tk()
frame = ScrollFrame(root, 150, 200, borderwidth=2, relief="sunken")
labels = []
for i in range(10):
labels.append(
tk.Label(
frame.inner_frame, text="Row {}".format(i) + "_"*20, anchor="nw"
) # Unfortunately, this widget's parent cannot just be frame but has to be frame.inner_frame
)
frame.place(x=20, y=20, width=150, height=150)
for i,label in enumerate(labels):
#label.grid(row=i,column=0)
label.place(x=0, y=20*i, width=100, height=20)
root.mainloop()
Run Code Online (Sandbox Code Playgroud)
这是因为place不会调整父窗口的大小以容纳子窗口小部件.您需要在使用时显式设置父框架的大小place.您的标签在那里,但框架的默认大小为1x1像素,有效地使标签不可见.
从官方文件的地方:
与许多其他几何管理器(例如打包器)不同,布局器不会尝试操纵主窗口的几何形状或从属窗口的父级(即它不设置其请求的大小).要控制这些窗口的大小,请将它们设置为窗口,如框架和画布,以便为此目的提供配置选项.
如果您需要做的就是在具有绝对控制的可滚动画布上管理一组小部件,只需使用canvas方法create_window,这将允许您以精确的坐标将标签直接放在画布上.这比使用place将小部件放在框架中,然后将框架放在画布中要容易得多.