框架的 Tkinter 滚动条
Posted
技术标签:
【中文标题】框架的 Tkinter 滚动条【英文标题】:Tkinter scrollbar for frame 【发布时间】:2013-04-17 19:02:30 【问题描述】:我的目标是在其中包含多个标签的框架中添加一个垂直滚动条。只要框架内的标签超过框架的高度,滚动条就会自动启用。经过搜索,我发现this 有用的帖子。根据那篇文章,我知道为了实现我想要的,(如果我错了,请纠正我,我是初学者)我必须先创建一个 Frame
,然后在该框架内创建一个 Canvas
并粘贴滚动条到该框架。之后,创建另一个框架并将其作为窗口对象放在画布内。所以,我终于想出了这个:
from Tkinter import *
def data():
for i in range(50):
Label(frame,text=i).grid(row=i,column=0)
Label(frame,text="my text"+str(i)).grid(row=i,column=1)
Label(frame,text="..........").grid(row=i,column=2)
def myfunction(event):
canvas.configure(scrollregion=canvas.bbox("all"),width=200,height=200)
root=Tk()
sizex = 800
sizey = 600
posx = 100
posy = 100
root.wm_geometry("%dx%d+%d+%d" % (sizex, sizey, posx, posy))
myframe=Frame(root,relief=GROOVE,width=50,height=100,bd=1)
myframe.place(x=10,y=10)
canvas=Canvas(myframe)
frame=Frame(canvas)
myscrollbar=Scrollbar(myframe,orient="vertical",command=canvas.yview)
canvas.configure(yscrollcommand=myscrollbar.set)
myscrollbar.pack(side="right",fill="y")
canvas.pack(side="left")
canvas.create_window((0,0),window=frame,anchor='nw')
frame.bind("<Configure>",myfunction)
data()
root.mainloop()
-
我做得对吗?有没有更好/更智能的方法来实现这段代码给我的输出?
为什么必须使用网格法? (我尝试了 place 方法,但画布上没有出现任何标签。)
在画布上创建窗口时使用
anchor='nw'
有什么特别之处?
由于我是初学者,请保持简单的回答。
【问题讨论】:
尽管代码乍一看似乎是正确的,但您的问题却倒退了。您必须创建一个框架,将其嵌入到画布中,然后将滚动条附加到画布上。 Adding a scrollbar to a grid of widgets in Tkinter的可能重复 @TrevorBoydSmith 有很多东西可能是重复的,但我投票决定将其关闭为另一个似乎有最佳答案的重复:***.com/questions/1873575/… 我迟到了,但非常感谢您!这是仅使用纯 Tkinter(我的项目的一个限制)创建可滚动框架的唯一全功能(和完整)示例。我知道这不是你的本意,但谢谢! 当我将这个框架原样放在另一个框架内并使用网格来绘制它时,它会以某种方式变得比允许的大得多。如何确保画布保持在其父框架的边框内? 【参考方案1】:请注意,建议的代码仅适用于 Python 2
这是一个例子:
from Tkinter import * # from x import * is bad practice
from ttk import *
# http://tkinter.unpythonic.net/wiki/VerticalScrolledFrame
class VerticalScrolledFrame(Frame):
"""A pure Tkinter scrollable frame that actually works!
* Use the 'interior' attribute to place widgets inside the scrollable frame
* Construct and pack/place/grid normally
* This frame only allows vertical scrolling
"""
def __init__(self, parent, *args, **kw):
Frame.__init__(self, parent, *args, **kw)
# create a canvas object and a vertical scrollbar for scrolling it
vscrollbar = Scrollbar(self, orient=VERTICAL)
vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
canvas = Canvas(self, bd=0, highlightthickness=0,
yscrollcommand=vscrollbar.set)
canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
vscrollbar.config(command=canvas.yview)
# reset the view
canvas.xview_moveto(0)
canvas.yview_moveto(0)
# create a frame inside the canvas which will be scrolled with it
self.interior = interior = Frame(canvas)
interior_id = canvas.create_window(0, 0, window=interior,
anchor=NW)
# track changes to the canvas and frame width and sync them,
# also updating the scrollbar
def _configure_interior(event):
# update the scrollbars to match the size of the inner frame
size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
canvas.config(scrollregion="0 0 %s %s" % size)
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the canvas's width to fit the inner frame
canvas.config(width=interior.winfo_reqwidth())
interior.bind('<Configure>', _configure_interior)
def _configure_canvas(event):
if interior.winfo_reqwidth() != canvas.winfo_width():
# update the inner frame's width to fill the canvas
canvas.itemconfigure(interior_id, width=canvas.winfo_width())
canvas.bind('<Configure>', _configure_canvas)
if __name__ == "__main__":
class SampleApp(Tk):
def __init__(self, *args, **kwargs):
root = Tk.__init__(self, *args, **kwargs)
self.frame = VerticalScrolledFrame(root)
self.frame.pack()
self.label = Label(text="Shrink the window to activate the scrollbar.")
self.label.pack()
buttons = []
for i in range(10):
buttons.append(Button(self.frame.interior, text="Button " + str(i)))
buttons[-1].pack()
app = SampleApp()
app.mainloop()
它还没有将鼠标滚轮绑定到滚动条,但这是可能的。不过,使用滚轮滚动可能会有些颠簸。
编辑:
到 1) 恕我直言,滚动框架在 Tkinter 中有些棘手,似乎并没有做很多。似乎没有优雅的方式来做到这一点。 您的代码的一个问题是您必须手动设置画布大小 - 这就是我发布的示例代码所解决的问题。
到 2) 你说的是数据功能?地方也适合我。 (总的来说,我更喜欢网格)。
到 3) 好吧,它将窗口定位在画布上。
我注意到的一件事是,您的示例默认处理鼠标滚轮滚动,而我发布的示例没有。有时间会去看看的。
【讨论】:
即使你没有回答我的问题,谢谢你的例子。但是当我的整个脚本比你的例子短时,我不愿意只为带有滚动条的框架编写那么多行代码。事实上,我的示例代码可以解决问题,尽管我不知道为什么我不能使用 place 方法。 @ChrisAung:这个解决方案的好处是它有一个可重用的类VerticalScrolledFrame
,你可以用它来替代任何Frame
。使用解决方案中的代码,您需要为您希望能够滚动的每个 Frame
重写所有代码。使用此代码,它几乎可以替代Frame
。所以不要因为行数而不愿意使用它。如果您只需要一个可滚动的框架,他的解决方案是更多的代码。如果你需要两个,两种技术都需要相同数量的代码,而且他更易于维护(冗余更少)。
从上面继续:如果您需要超过 2 个可滚动的框架,他的解决方案需要更少的代码,并且更容易维护。说了这么多,我不会使用这个解决方案,因为它有一些警告。另外,我不喜欢使用.interior
的要求——我想要一个接口,使.interior
成为实现细节,而不是让它成为使用它的人必须知道的东西。【参考方案2】:
“我做得对吗?有更好/更智能的方法来实现这段代码给我的输出吗?”
一般来说,是的,你做得对。除了画布之外,Tkinter 没有本机可滚动容器。如您所见,设置起来并不难。如您的示例所示,只需 5 或 6 行代码即可使其工作 - 取决于您如何计算行数。
“为什么我必须使用网格方法?(我尝试了放置方法,但画布上没有出现任何标签?)”
您问为什么必须使用网格。不需要使用网格。 Place, grid 和 pack 都可以使用。只是有些更自然地适合特定类型的问题。在这种情况下,看起来您正在创建一个实际的网格——标签的行和列——所以网格是自然的选择。
“在画布上创建窗口时使用 anchor='nw' 有什么特别之处?”
锚告诉您窗口的哪个部分位于您提供的坐标处。默认情况下,窗口的中心将放置在坐标处。对于上面的代码,您希望左上角(“西北”)位于坐标处。
【讨论】:
包含画布的框架和包含框架。没有其他选择吗?示例:1 帧带滚动条? @ChrisP:框架本身是不可滚动的,这就是你需要使用画布的原因。【参考方案3】:即使不使用 Canvas,我们也可以添加滚动条。我在许多其他帖子中读过它,我们不能直接在框架中添加垂直滚动条等。但是在做了很多实验之后,找到了添加垂直和水平滚动条的方法:)。请在下面找到用于在 treeView 和 frame 中创建滚动条的代码。
f = Tkinter.Frame(self.master,width=3)
f.grid(row=2, column=0, columnspan=8, rowspan=10, pady=30, padx=30)
f.config(width=5)
self.tree = ttk.Treeview(f, selectmode="extended")
scbHDirSel =tk.Scrollbar(f, orient=Tkinter.HORIZONTAL, command=self.tree.xview)
scbVDirSel =tk.Scrollbar(f, orient=Tkinter.VERTICAL, command=self.tree.yview)
self.tree.configure(yscrollcommand=scbVDirSel.set, xscrollcommand=scbHDirSel.set)
self.tree["columns"] = (self.columnListOutput)
self.tree.column("#0", width=40)
self.tree.heading("#0", text='SrNo', anchor='w')
self.tree.grid(row=2, column=0, sticky=Tkinter.NSEW,in_=f, columnspan=10, rowspan=10)
scbVDirSel.grid(row=2, column=10, rowspan=10, sticky=Tkinter.NS, in_=f)
scbHDirSel.grid(row=14, column=0, rowspan=2, sticky=Tkinter.EW,in_=f)
f.rowconfigure(0, weight=1)
f.columnconfigure(0, weight=1)
【讨论】:
我不明白这些答案对“Tkinter”、“tk”和“ttk”的使用。我希望每个示例中只有一个这样的前缀,但第三个具有全部三个。什么给了? 他必须以import Tkinter
、import Tkinter as tk
和import ttk
开头这段代码。前两个是多余的,所以他所有的地方Tkinter.NSEW
,例如,他可以把tk.NSEW
.ttk
是它自己的模块,而Treeview是一个只存在于该模块中的类,所以声明应该是这样的。
@4dummies :“tk”是“Tkinter”的同义词,在导入时使用“as”关键字创建。通常,大多数人在教程中使用“Tkinter”,在他们自己的代码中使用“tk”。他可能将不同的项目复制并粘贴在一起。无论如何, ttk 是一个单独的模块,是各种更高级别的小部件和样式的实现。导入是正确的,因为它是它自己的模块。
如何运行此代码解决方案?我收到一条错误消息“NameError: name 'self' is not defined” 请提供一个完整的工作示例来创建树视图和框架。谢谢。【参考方案4】:
请看我的类是一个可滚动的框架。它的垂直滚动条也绑定到<Mousewheel>
事件。所以,你所要做的就是创建一个框架,用你喜欢的方式填充它,然后让这个框架成为我的ScrolledWindow.scrollwindow
的子框架。如果有不清楚的地方,请随时询问。
使用了很多来自@Brayan Oakley 的答案来接近这个问题
class ScrolledWindow(tk.Frame):
"""
1. Master widget gets scrollbars and a canvas. Scrollbars are connected
to canvas scrollregion.
2. self.scrollwindow is created and inserted into canvas
Usage Guideline:
Assign any widgets as children of <ScrolledWindow instance>.scrollwindow
to get them inserted into canvas
__init__(self, parent, canv_w = 400, canv_h = 400, *args, **kwargs)
docstring:
Parent = master of scrolled window
canv_w - width of canvas
canv_h - height of canvas
"""
def __init__(self, parent, canv_w = 400, canv_h = 400, *args, **kwargs):
"""Parent = master of scrolled window
canv_w - width of canvas
canv_h - height of canvas
"""
super().__init__(parent, *args, **kwargs)
self.parent = parent
# creating a scrollbars
self.xscrlbr = ttk.Scrollbar(self.parent, orient = 'horizontal')
self.xscrlbr.grid(column = 0, row = 1, sticky = 'ew', columnspan = 2)
self.yscrlbr = ttk.Scrollbar(self.parent)
self.yscrlbr.grid(column = 1, row = 0, sticky = 'ns')
# creating a canvas
self.canv = tk.Canvas(self.parent)
self.canv.config(relief = 'flat',
width = 10,
heigh = 10, bd = 2)
# placing a canvas into frame
self.canv.grid(column = 0, row = 0, sticky = 'nsew')
# accociating scrollbar comands to canvas scroling
self.xscrlbr.config(command = self.canv.xview)
self.yscrlbr.config(command = self.canv.yview)
# creating a frame to inserto to canvas
self.scrollwindow = ttk.Frame(self.parent)
self.canv.create_window(0, 0, window = self.scrollwindow, anchor = 'nw')
self.canv.config(xscrollcommand = self.xscrlbr.set,
yscrollcommand = self.yscrlbr.set,
scrollregion = (0, 0, 100, 100))
self.yscrlbr.lift(self.scrollwindow)
self.xscrlbr.lift(self.scrollwindow)
self.scrollwindow.bind('<Configure>', self._configure_window)
self.scrollwindow.bind('<Enter>', self._bound_to_mousewheel)
self.scrollwindow.bind('<Leave>', self._unbound_to_mousewheel)
return
def _bound_to_mousewheel(self, event):
self.canv.bind_all("<MouseWheel>", self._on_mousewheel)
def _unbound_to_mousewheel(self, event):
self.canv.unbind_all("<MouseWheel>")
def _on_mousewheel(self, event):
self.canv.yview_scroll(int(-1*(event.delta/120)), "units")
def _configure_window(self, event):
# update the scrollbars to match the size of the inner frame
size = (self.scrollwindow.winfo_reqwidth(), self.scrollwindow.winfo_reqheight())
self.canv.config(scrollregion='0 0 %s %s' % size)
if self.scrollwindow.winfo_reqwidth() != self.canv.winfo_width():
# update the canvas's width to fit the inner frame
self.canv.config(width = self.scrollwindow.winfo_reqwidth())
if self.scrollwindow.winfo_reqheight() != self.canv.winfo_height():
# update the canvas's width to fit the inner frame
self.canv.config(height = self.scrollwindow.winfo_reqheight())
【讨论】:
小错误,你的最后一行应该是self.canv.config(height= ...)
你是什么意思,“让这个框架成为我的 ScrolledWindow.scrollwindow 的子框架”。能举个例子吗?
@Jacob 的用法基本上是分配滚动窗口:self.sw = ScrolledWindow(self.root)
然后您可以将任何子项分配为self.child(self.sw.scrollwindow, any of the child's options
,因此添加一个按钮将是self.btn = tk.Button(self.sw.scrollwindow, text = 'Button', command = lambda: print("Button Pressed"))
@MikhailT。我对它进行了一些修改以满足我的需要,但这对替换 Pmw 中的滚动框架很有帮助,因为显然 Pmw 奇怪的导入加载与 pyinstaller 不兼容,谢谢。
截至 2021 年 10 月为止的最佳答案。完美解决了我的问题,而当前投票最高的答案无法处理请求的框架大小不断变化的情况。非常感谢!【参考方案5】:
对于任何偶然发现这一点的人(就像在寻找我自己的 gist 时所做的那样),我在 https://gist.github.com/mp035/9f2027c3ef9172264532fcd6262f3b01 上维护了一个完全用于此目的的 gist。文件中的演示。
【讨论】:
以上是关于框架的 Tkinter 滚动条的主要内容,如果未能解决你的问题,请参考以下文章