tkinter 滚动条只向下滚动并切断内容
tkinter scrollbar only scrolls downwards and cuts off content
编辑[已解决]
基于下面的 ,该方法只是确保您生成的元素被分配以正确 parents。
值得创建一个框架来容纳每个可滚动元素,因为使用 pack/place
时您无法在 parents 之间移动小部件。因此,所有 movable
元素之间的公共 parents 将使您可以自由移动它们,同样只需创建一个支架框架。有关详细信息,请参阅下面的答案和讨论。
编辑2
有非常好的信息和仅使用 canvas 的替代方法。核心概念仍然相同。
原题从这里开始
情况
所以基于 Bryan 的 ,我使用这种方法在 scrollable frame
上生成小部件(这基本上是一个 Canvas
在内部包裹一个框架,因为我们知道框架不'有滚动属性)。
本例中的小部件只是 tk.Button
,我使用 lambda 在循环中生成这些小部件以保存状态。那方面完全没问题。
问题
现在一切正常,除了当我生成更多元素(再次只是按钮)时,它们似乎被切断了。而且我似乎无法向下滚动以查看其余的。我只能向上滚动只能看到空的 space.
请观看下面的视频了解我的意思(请原谅我糟糕的颜色选择,这是为了快速演示)
在视频中,template47
下面还有更多元素,但我无法滚动到它们。我可以向上滚动,但那里很寂寞。所有名称实际上都是带有零 bd
和 HLthickness
.
的按钮
我试过的
首先,我的第一直觉是将 ttk.scrollbar
附加到 canvas+frame
,我这样做了,但观察到完全相同的行为。
然后我尝试查看是否可以使用 .moveTo('1.0')
向下滚动到最后一个条目,然后由于向上滚动已经工作,应该没有问题。但这也没有做任何事情。元素仍然被切断,显然向上滚动也搞砸了。
我认为我不能使用 pack/grid
geoManagers,因为正如我在上面链接的 bryan 的回答所建议的那样,使用 place
及其 in_
arg 是首选方式。如果可能的话,请告诉我。
我的用例简述
如上面链接的答案中所述,我也有两个框架,我正在使用 on_click 回调函数来切换 parents(很像答案中的示例代码)。事实证明,地方是实现这一目标的最佳选择。
确实,所有这些方面都运作良好。只是滚动的东西不起作用。
一些代码(我敢说 MCVE)
我如何绑定到鼠标滚轮
def _bound_to_mousewheel(self, event):
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
def _unbound_to_mousewheel(self, event):
self.canvas.unbind_all("<MouseWheel>")
def _on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
创建 window
self.canvas.create_window((5, 6), window=self.scrolled_frame, anchor="w")
当然还有里面的小部件
for f in self.fav_templates:
btn = tk.Button(self.root, text='text', command=lambda te=f: self._on_click(te))
btn.place(in_=self.ft_frame, x=20, y=p)
所以
我相信上面的片段会让我了解我所做的事情。如果有人需要我创建一个完整的 MCVE,他们可以 运行,很乐意这样做,让我知道。
我在设置中遗漏了什么?欢迎任何想法或建议。
谢谢你:)
主要问题是您正在使用 place
并且在 place 中您将成为小部件的全能者,这意味着将没有 requested size 到 master 或 tkinter 中提供的任何其他魔法的背景。所以我确实建议使用另一个几何管理器,它可以像 pack 一样为您带来魔力。另请注意,我将锚点设置为 nw
.
此外,您似乎只能在普通母版中使用可选参数 in_
。所以这个概念的关键是有一个用作主参数的支架框架,并将 in_
用于 children 能够容纳小部件。
import tkinter as tk
class Example:
def __init__(self):
self.root = tk.Tk()
test_frame = tk.Frame(self.root,height=200,width=400,bg='orange')
test_frame.pack()
self.DISPLAY_WIDTH = 200
self.DISPLAY_HEIGHT= 200
self.create_holder()
self.create_displays()
self.add_scrollbars()
self.populate_holder()
def create_holder(self):
'''creates a canvas for the displays and is needed
to have a common border/window'''
self.holder = tk.Canvas(self.root,width=self.DISPLAY_WIDTH*2,height=self.DISPLAY_HEIGHT)
self.holder_frame = tk.Frame(self.holder)
self.holder.create_window((5, 6), window=self.holder_frame, anchor="nw")
self.holder.pack()
def create_displays(self):
'''creates 2 displays to have seperate scrollregions'''
self.cnvs1 = tk.Canvas(self.holder_frame,
width=self.DISPLAY_WIDTH,
height=self.DISPLAY_HEIGHT)
self.cnvs2 = tk.Canvas(self.holder_frame,
width=self.DISPLAY_WIDTH,
height=self.DISPLAY_HEIGHT)
self.lf1 = tk.Frame(self.cnvs1);self.cnvs1.create_window((5, 6), window=self.lf1, anchor="nw")
self.lf2 = tk.Frame(self.cnvs2);self.cnvs2.create_window((5, 6), window=self.lf2, anchor="nw")
def add_scrollbars(self):
self.vsb1 = tk.Scrollbar(self.holder_frame, orient="vertical", command=self.cnvs1.yview)
self.vsb2 = tk.Scrollbar(self.holder_frame, orient="vertical", command=self.cnvs2.yview)
self.cnvs1.configure(yscrollcommand=self.vsb1.set)
self.lf1.bind("<Configure>", self.onFrameConfigure)
self.cnvs2.configure(yscrollcommand=self.vsb2.set)
self.lf2.bind("<Configure>", self.onFrameConfigure)
def populate_holder(self):
self.cnvs1.pack(side="left", fill="both", expand=True)
self.vsb1.pack(side="left", fill="y")
self.cnvs2.pack(side="right", fill="both", expand=True)
self.vsb2.pack(side="right", fill="y")
for i in range(20):
button = tk.Button(self.holder_frame,text="Click me")
button.config(command=lambda b=button:self.on_click(b))
button.pack(in_=self.lf1)
def start(self):
self.root.mainloop()
def onFrameConfigure(self, event):
self.cnvs1.configure(scrollregion=self.cnvs1.bbox("all"))
self.cnvs2.configure(scrollregion=self.cnvs2.bbox("all"))
def on_click(self,button):
current_frame = button.pack_info().get("in")
new_frame = self.lf1 if current_frame == self.lf2 else self.lf2
button.pack(in_=new_frame)
Example().start()
如果您正在创建一个垂直堆栈的小部件,那么在 canvas 中嵌入一个框架会使解决方案过于复杂。相反,您可以直接在 canvas 中创建按钮。嵌入式框架唯一有用的时候是当你想利用 pack
或 grid
的力量将小部件排列成复杂的布局时。在这种情况下,您正在创建一个垂直堆栈,因此框架是不必要的。
以下代码需要注意几点:
这些按钮是 canvas 的子项,而不是 root 的子项。我不知道你为什么要让按钮成为根 window 的子按钮,因为它们在 canvas 中。它们需要是 canvas 的子元素,这样它们就不会在 canvas.
的边界之外绘制
一个名为 lasty
的 属性 将 return 底部的 y 坐标。每个新按钮都将添加到此坐标下方。
为简洁起见,此代码未使用 self.fav_templates
。
import tkinter as tk
class ScrolledButtons(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.canvas = tk.Canvas(self)
self.vsb = tk.Scrollbar(self, command=self.canvas.yview, orient="vertical")
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
for n in range(20):
x = 2
y = self.lasty + 2
btn = tk.Button(self.canvas, text=f'Button #{n}', command=lambda te=n: self._on_click(te))
self.canvas.create_window(20, y, anchor="nw", window=btn)
bbox = self.canvas.bbox("all")
self.canvas.configure(scrollregion=(0, 0, bbox[2], bbox[3]))
@property
def lasty(self):
bbox = self.canvas.bbox("all")
lasty = bbox[3] if bbox else 0
return lasty
root = tk.Tk()
w = ScrolledButtons(root)
w.pack(fill="both", expand=True)
root.mainloop()
编辑[已解决]
基于下面的
值得创建一个框架来容纳每个可滚动元素,因为使用 pack/place
时您无法在 parents 之间移动小部件。因此,所有 movable
元素之间的公共 parents 将使您可以自由移动它们,同样只需创建一个支架框架。有关详细信息,请参阅下面的答案和讨论。
编辑2
原题从这里开始
情况
所以基于 Bryan 的 scrollable frame
上生成小部件(这基本上是一个 Canvas
在内部包裹一个框架,因为我们知道框架不'有滚动属性)。
本例中的小部件只是 tk.Button
,我使用 lambda 在循环中生成这些小部件以保存状态。那方面完全没问题。
问题
现在一切正常,除了当我生成更多元素(再次只是按钮)时,它们似乎被切断了。而且我似乎无法向下滚动以查看其余的。我只能向上滚动只能看到空的 space.
请观看下面的视频了解我的意思(请原谅我糟糕的颜色选择,这是为了快速演示)
在视频中,template47
下面还有更多元素,但我无法滚动到它们。我可以向上滚动,但那里很寂寞。所有名称实际上都是带有零 bd
和 HLthickness
.
我试过的
首先,我的第一直觉是将 ttk.scrollbar
附加到 canvas+frame
,我这样做了,但观察到完全相同的行为。
然后我尝试查看是否可以使用 .moveTo('1.0')
向下滚动到最后一个条目,然后由于向上滚动已经工作,应该没有问题。但这也没有做任何事情。元素仍然被切断,显然向上滚动也搞砸了。
我认为我不能使用 pack/grid
geoManagers,因为正如我在上面链接的 bryan 的回答所建议的那样,使用 place
及其 in_
arg 是首选方式。如果可能的话,请告诉我。
我的用例简述
如上面链接的答案中所述,我也有两个框架,我正在使用 on_click 回调函数来切换 parents(很像答案中的示例代码)。事实证明,地方是实现这一目标的最佳选择。 确实,所有这些方面都运作良好。只是滚动的东西不起作用。
一些代码(我敢说 MCVE)
我如何绑定到鼠标滚轮
def _bound_to_mousewheel(self, event):
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
def _unbound_to_mousewheel(self, event):
self.canvas.unbind_all("<MouseWheel>")
def _on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
创建 window
self.canvas.create_window((5, 6), window=self.scrolled_frame, anchor="w")
当然还有里面的小部件
for f in self.fav_templates:
btn = tk.Button(self.root, text='text', command=lambda te=f: self._on_click(te))
btn.place(in_=self.ft_frame, x=20, y=p)
所以
我相信上面的片段会让我了解我所做的事情。如果有人需要我创建一个完整的 MCVE,他们可以 运行,很乐意这样做,让我知道。
我在设置中遗漏了什么?欢迎任何想法或建议。
谢谢你:)
主要问题是您正在使用 place
并且在 place 中您将成为小部件的全能者,这意味着将没有 requested size 到 master 或 tkinter 中提供的任何其他魔法的背景。所以我确实建议使用另一个几何管理器,它可以像 pack 一样为您带来魔力。另请注意,我将锚点设置为 nw
.
此外,您似乎只能在普通母版中使用可选参数 in_
。所以这个概念的关键是有一个用作主参数的支架框架,并将 in_
用于 children 能够容纳小部件。
import tkinter as tk
class Example:
def __init__(self):
self.root = tk.Tk()
test_frame = tk.Frame(self.root,height=200,width=400,bg='orange')
test_frame.pack()
self.DISPLAY_WIDTH = 200
self.DISPLAY_HEIGHT= 200
self.create_holder()
self.create_displays()
self.add_scrollbars()
self.populate_holder()
def create_holder(self):
'''creates a canvas for the displays and is needed
to have a common border/window'''
self.holder = tk.Canvas(self.root,width=self.DISPLAY_WIDTH*2,height=self.DISPLAY_HEIGHT)
self.holder_frame = tk.Frame(self.holder)
self.holder.create_window((5, 6), window=self.holder_frame, anchor="nw")
self.holder.pack()
def create_displays(self):
'''creates 2 displays to have seperate scrollregions'''
self.cnvs1 = tk.Canvas(self.holder_frame,
width=self.DISPLAY_WIDTH,
height=self.DISPLAY_HEIGHT)
self.cnvs2 = tk.Canvas(self.holder_frame,
width=self.DISPLAY_WIDTH,
height=self.DISPLAY_HEIGHT)
self.lf1 = tk.Frame(self.cnvs1);self.cnvs1.create_window((5, 6), window=self.lf1, anchor="nw")
self.lf2 = tk.Frame(self.cnvs2);self.cnvs2.create_window((5, 6), window=self.lf2, anchor="nw")
def add_scrollbars(self):
self.vsb1 = tk.Scrollbar(self.holder_frame, orient="vertical", command=self.cnvs1.yview)
self.vsb2 = tk.Scrollbar(self.holder_frame, orient="vertical", command=self.cnvs2.yview)
self.cnvs1.configure(yscrollcommand=self.vsb1.set)
self.lf1.bind("<Configure>", self.onFrameConfigure)
self.cnvs2.configure(yscrollcommand=self.vsb2.set)
self.lf2.bind("<Configure>", self.onFrameConfigure)
def populate_holder(self):
self.cnvs1.pack(side="left", fill="both", expand=True)
self.vsb1.pack(side="left", fill="y")
self.cnvs2.pack(side="right", fill="both", expand=True)
self.vsb2.pack(side="right", fill="y")
for i in range(20):
button = tk.Button(self.holder_frame,text="Click me")
button.config(command=lambda b=button:self.on_click(b))
button.pack(in_=self.lf1)
def start(self):
self.root.mainloop()
def onFrameConfigure(self, event):
self.cnvs1.configure(scrollregion=self.cnvs1.bbox("all"))
self.cnvs2.configure(scrollregion=self.cnvs2.bbox("all"))
def on_click(self,button):
current_frame = button.pack_info().get("in")
new_frame = self.lf1 if current_frame == self.lf2 else self.lf2
button.pack(in_=new_frame)
Example().start()
如果您正在创建一个垂直堆栈的小部件,那么在 canvas 中嵌入一个框架会使解决方案过于复杂。相反,您可以直接在 canvas 中创建按钮。嵌入式框架唯一有用的时候是当你想利用 pack
或 grid
的力量将小部件排列成复杂的布局时。在这种情况下,您正在创建一个垂直堆栈,因此框架是不必要的。
以下代码需要注意几点:
这些按钮是 canvas 的子项,而不是 root 的子项。我不知道你为什么要让按钮成为根 window 的子按钮,因为它们在 canvas 中。它们需要是 canvas 的子元素,这样它们就不会在 canvas.
的边界之外绘制一个名为
lasty
的 属性 将 return 底部的 y 坐标。每个新按钮都将添加到此坐标下方。为简洁起见,此代码未使用
self.fav_templates
。
import tkinter as tk
class ScrolledButtons(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.canvas = tk.Canvas(self)
self.vsb = tk.Scrollbar(self, command=self.canvas.yview, orient="vertical")
self.canvas.configure(yscrollcommand=self.vsb.set)
self.vsb.pack(side="right", fill="y")
self.canvas.pack(side="left", fill="both", expand=True)
for n in range(20):
x = 2
y = self.lasty + 2
btn = tk.Button(self.canvas, text=f'Button #{n}', command=lambda te=n: self._on_click(te))
self.canvas.create_window(20, y, anchor="nw", window=btn)
bbox = self.canvas.bbox("all")
self.canvas.configure(scrollregion=(0, 0, bbox[2], bbox[3]))
@property
def lasty(self):
bbox = self.canvas.bbox("all")
lasty = bbox[3] if bbox else 0
return lasty
root = tk.Tk()
w = ScrolledButtons(root)
w.pack(fill="both", expand=True)
root.mainloop()