顶级 window 关闭后在主 window 中滚动时出错
Error scrolling in main window after top level window was closed
我正在使用 Tkinter 构建应用程序,我需要可滚动 windows。我使用 Canvas 创建了一个可滚动容器: ScrollContainer 。在此之后,我将我的程序的主要逻辑合并到这个容器中,我在其中放置了一个按钮来打开另一个单独的 TopLevel window。这个单独的 window 也必须是可滚动的。因此我也将它包含在同一个容器中 class
现在,问题是:当我 运行 程序时,我的主程序 window 可以正常滚动。单击按钮后,我打开了 TopLevel window。辅助 window 滚动正常。在我 关闭 次要 window 并将鼠标再次悬停在主要 window 上之后,现在它不再滚动并且我在控制台中收到错误消息:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\mirel.voicu\Anaconda3\envs\gis385\lib\tkinter\__init__.py", line 1883, in __call__
return self.func(*args)
File "C:/Users/mirel.voicu/Desktop/python_projects/TKINTER/standalone_program/test.py", line 45, in _on_mousewheel
self.my_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
File "C:\Users\mirel.voicu\Anaconda3\envs\gis385\lib\tkinter\__init__.py", line 1929, in yview_scroll
self.tk.call(self._w, 'yview', 'scroll', number, what)
_tkinter.TclError: invalid command name ".!toplevel.!frame.!canvas"
注:
我也尝试使用 self.my_canvas.bind("<MouseWheel>", self._on_mousewheel)
而不是 self.my_canvas.bind_all("<MouseWheel>", self._on_mousewheel)
,现在没有错误。但是,滚动发生了变化。如果将鼠标悬停在标签上,则无法再滚动。只有放大 window 并将鼠标悬停在右侧一点才能滚动。我想这是因为您必须将鼠标移到 canvas 上,因为它是唯一可滚动的实体
滚动容器 class:
from tkinter import *
from tkinter import ttk
class ScrollContainer (ttk.Frame):
def __init__(self, container,w,h,*args, **kwargs):
super().__init__(container, *args, **kwargs)
# Create a main frame
self.main_frame = Frame(container, width=w, height=h)
self.main_frame.pack(side=TOP,fill=BOTH, expand=1) # expand frame to the size of the container
# create a canvas
self.my_canvas = Canvas(self.main_frame)
self.my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
self.my_canvas.bind_all("<MouseWheel>", self._on_mousewheel)
# add h and v scrollbar to canvas
self.my_vscrollbar = ttk.Scrollbar(self.main_frame, orient=VERTICAL, command=self.my_canvas.yview)
self.my_vscrollbar.pack(side=RIGHT, fill=Y)
self.my_hscrollbar = ttk.Scrollbar(container, orient=HORIZONTAL, command=self.my_canvas.xview)
self.my_hscrollbar.pack(side=BOTTOM, fill=X)
# configure canvas
self.my_canvas.configure(yscrollcommand=self.my_vscrollbar.set, xscrollcommand=self.my_hscrollbar.set)
self.my_canvas.bind('<Configure>', lambda e: self.my_canvas.configure(scrollregion=self.my_canvas.bbox('all')))
# create another frame inside the canvas
self.second_frame = Frame(self.my_canvas)
# add that new frame to a window in the canvas
self.my_canvas.create_window((0, 0), window=self.second_frame, anchor='nw')
def _on_mousewheel(self, event):
self.my_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
主要程序逻辑:
def open():
w=Toplevel()
SecondContainer=ScrollContainer(w,1000,768)
for thing in range(40):
Label(SecondContainer.second_frame, text=f"It's Friday {thing} ").grid(row=thing, column=0)
root=Tk()
MainContainer=ScrollContainer(root,1000,768)
btn=Button(MainContainer.second_frame, text="New Window",bg='yellow',command=open)
btn.grid(row=0,column=0)
for thing in range(1,30):
Label(MainContainer.second_frame,text=f"It's Friday {thing} ").grid(row=thing,column=0)
# frame design
root.mainloop()
这是因为您使用了 bind_all()
,这是应用程序智能绑定。因此 Toplevel()
中的绑定将覆盖 root
中的绑定。当顶层被销毁时,绑定函数仍然引用顶层中的 canvas,因此异常。
您应该使用 window 智能绑定:
class ScrollContainer(ttk.Frame):
def __init__(self, container, w, h, *args, **kwargs):
super().__init__(container, *args, **kwargs)
container.bind("<MouseWheel>", self._on_mousewheel) # bind on the parent window
# Create a main frame
self.main_frame = Frame(container, width=w, height=h)
self.main_frame.pack(side=TOP, fill=BOTH, expand=1) # expand frame to the size of the container
# create a canvas
self.my_canvas = Canvas(self.main_frame)
self.my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
#self.my_canvas.bind_all("<MouseWheel>", self._on_mousewheel)
# add h and v scrollbar to canvas
self.my_vscrollbar = ttk.Scrollbar(self.main_frame, orient=VERTICAL, command=self.my_canvas.yview)
self.my_vscrollbar.pack(side=RIGHT, fill=Y)
self.my_hscrollbar = ttk.Scrollbar(container, orient=HORIZONTAL, command=self.my_canvas.xview)
self.my_hscrollbar.pack(side=BOTTOM, fill=X)
# configure canvas
self.my_canvas.configure(yscrollcommand=self.my_vscrollbar.set, xscrollcommand=self.my_hscrollbar.set)
self.my_canvas.bind('<Configure>', lambda e: self.my_canvas.configure(scrollregion=self.my_canvas.bbox('all')))
# create another frame inside the canvas
self.second_frame = Frame(self.my_canvas)
# add that new frame to a window in the canvas
self.my_canvas.create_window((0, 0), window=self.second_frame, anchor='nw')
def _on_mousewheel(self, event):
self.my_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
我注意到你已经解决了这个问题,但 @acw1668 给你的解决方案,就我而言,部分有效。所以,我决定 post 在这里我的代码,以防有人像我一样不幸。
我使用了@acw1668 class 但稍微修改了一下。
将 'bind_all' 替换为 'bind' 的唯一效果是在滚动条区域以外的任何地方都禁用鼠标滚动,因此我必须定义小部件的行为(self.canvas) 当事件 ('MouseWheel') 发生时。因此,我进行了一些搜索,最后找到了另一个问题 answered by @Saad,该问题解释了如何通过一种新方法 (set_mousewheel) 在光标悬停时更改小部件的行为。然后,我把所有东西放在一起,这就是我得到的:
class ScrollableFrame(Frame):
"""
Defines a ScrollbarFrame class
"""
def __init__(self, container, width=800, height=700, bg = "white", *args, **kwargs):
super().__init__(container, *args, **kwargs)
# to hide the border
highlightthickness = 0
# Create a main frame
self.main_frame = Frame(container, width=width, height=height, bg=bg)
self.main_frame.pack(side=TOP, fill=BOTH, expand=1) # expand frame to the size of the container
#create a canvas
#highlightthickness = 0 to hide the border
self.canvas = Canvas(self.main_frame, width=width, height=height, bg=bg, highlightthickness=highlightthickness)
self.canvas.pack(side=LEFT, fill=BOTH, expand=1)
self.scrollbar = Scrollbar(self.main_frame, orient=VERTICAL, command=self.canvas.yview)
self.scrollbar.pack(side=RIGHT, fill=Y)
self.scrollbar_x = Scrollbar(container, orient=HORIZONTAL, command=self.canvas.xview)
self.scrollbar_x.pack(side=BOTTOM, fill=X)
self.canvas.configure(yscrollcommand=self.scrollbar.set, xscrollcommand=self.scrollbar_x.set)
self.canvas.bind(
"<Configure>",
lambda e: self.canvas.configure (
scrollregion=self.canvas.bbox("all")
)
)
self.scrollable_frame = Frame(self.canvas, bg=bg)
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
#DO NOT PUT bind_all!!!
self.canvas.bind("<MouseWheel>", self._on_mousewheel)
self.canvas.bind("<MouseWheel>", self.set_mousewheel(self.canvas, self._on_mousewheel))
def _on_mousewheel(self, event):
"""
Allowes to scroll the mouse
"""
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def set_mousewheel(self, widget, command):
"""Activate / deactivate mousewheel scrolling when
cursor is over / not over the widget respectively."""
widget.bind("<Enter>", lambda _: widget.bind_all('<MouseWheel>', command))
widget.bind("<Leave>", lambda _: widget.unbind_all('<MouseWheel>'))
我正在使用 Tkinter 构建应用程序,我需要可滚动 windows。我使用 Canvas 创建了一个可滚动容器: ScrollContainer 。在此之后,我将我的程序的主要逻辑合并到这个容器中,我在其中放置了一个按钮来打开另一个单独的 TopLevel window。这个单独的 window 也必须是可滚动的。因此我也将它包含在同一个容器中 class
现在,问题是:当我 运行 程序时,我的主程序 window 可以正常滚动。单击按钮后,我打开了 TopLevel window。辅助 window 滚动正常。在我 关闭 次要 window 并将鼠标再次悬停在主要 window 上之后,现在它不再滚动并且我在控制台中收到错误消息:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\mirel.voicu\Anaconda3\envs\gis385\lib\tkinter\__init__.py", line 1883, in __call__
return self.func(*args)
File "C:/Users/mirel.voicu/Desktop/python_projects/TKINTER/standalone_program/test.py", line 45, in _on_mousewheel
self.my_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
File "C:\Users\mirel.voicu\Anaconda3\envs\gis385\lib\tkinter\__init__.py", line 1929, in yview_scroll
self.tk.call(self._w, 'yview', 'scroll', number, what)
_tkinter.TclError: invalid command name ".!toplevel.!frame.!canvas"
注:
我也尝试使用 self.my_canvas.bind("<MouseWheel>", self._on_mousewheel)
而不是 self.my_canvas.bind_all("<MouseWheel>", self._on_mousewheel)
,现在没有错误。但是,滚动发生了变化。如果将鼠标悬停在标签上,则无法再滚动。只有放大 window 并将鼠标悬停在右侧一点才能滚动。我想这是因为您必须将鼠标移到 canvas 上,因为它是唯一可滚动的实体
滚动容器 class:
from tkinter import *
from tkinter import ttk
class ScrollContainer (ttk.Frame):
def __init__(self, container,w,h,*args, **kwargs):
super().__init__(container, *args, **kwargs)
# Create a main frame
self.main_frame = Frame(container, width=w, height=h)
self.main_frame.pack(side=TOP,fill=BOTH, expand=1) # expand frame to the size of the container
# create a canvas
self.my_canvas = Canvas(self.main_frame)
self.my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
self.my_canvas.bind_all("<MouseWheel>", self._on_mousewheel)
# add h and v scrollbar to canvas
self.my_vscrollbar = ttk.Scrollbar(self.main_frame, orient=VERTICAL, command=self.my_canvas.yview)
self.my_vscrollbar.pack(side=RIGHT, fill=Y)
self.my_hscrollbar = ttk.Scrollbar(container, orient=HORIZONTAL, command=self.my_canvas.xview)
self.my_hscrollbar.pack(side=BOTTOM, fill=X)
# configure canvas
self.my_canvas.configure(yscrollcommand=self.my_vscrollbar.set, xscrollcommand=self.my_hscrollbar.set)
self.my_canvas.bind('<Configure>', lambda e: self.my_canvas.configure(scrollregion=self.my_canvas.bbox('all')))
# create another frame inside the canvas
self.second_frame = Frame(self.my_canvas)
# add that new frame to a window in the canvas
self.my_canvas.create_window((0, 0), window=self.second_frame, anchor='nw')
def _on_mousewheel(self, event):
self.my_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
主要程序逻辑:
def open():
w=Toplevel()
SecondContainer=ScrollContainer(w,1000,768)
for thing in range(40):
Label(SecondContainer.second_frame, text=f"It's Friday {thing} ").grid(row=thing, column=0)
root=Tk()
MainContainer=ScrollContainer(root,1000,768)
btn=Button(MainContainer.second_frame, text="New Window",bg='yellow',command=open)
btn.grid(row=0,column=0)
for thing in range(1,30):
Label(MainContainer.second_frame,text=f"It's Friday {thing} ").grid(row=thing,column=0)
# frame design
root.mainloop()
这是因为您使用了 bind_all()
,这是应用程序智能绑定。因此 Toplevel()
中的绑定将覆盖 root
中的绑定。当顶层被销毁时,绑定函数仍然引用顶层中的 canvas,因此异常。
您应该使用 window 智能绑定:
class ScrollContainer(ttk.Frame):
def __init__(self, container, w, h, *args, **kwargs):
super().__init__(container, *args, **kwargs)
container.bind("<MouseWheel>", self._on_mousewheel) # bind on the parent window
# Create a main frame
self.main_frame = Frame(container, width=w, height=h)
self.main_frame.pack(side=TOP, fill=BOTH, expand=1) # expand frame to the size of the container
# create a canvas
self.my_canvas = Canvas(self.main_frame)
self.my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
#self.my_canvas.bind_all("<MouseWheel>", self._on_mousewheel)
# add h and v scrollbar to canvas
self.my_vscrollbar = ttk.Scrollbar(self.main_frame, orient=VERTICAL, command=self.my_canvas.yview)
self.my_vscrollbar.pack(side=RIGHT, fill=Y)
self.my_hscrollbar = ttk.Scrollbar(container, orient=HORIZONTAL, command=self.my_canvas.xview)
self.my_hscrollbar.pack(side=BOTTOM, fill=X)
# configure canvas
self.my_canvas.configure(yscrollcommand=self.my_vscrollbar.set, xscrollcommand=self.my_hscrollbar.set)
self.my_canvas.bind('<Configure>', lambda e: self.my_canvas.configure(scrollregion=self.my_canvas.bbox('all')))
# create another frame inside the canvas
self.second_frame = Frame(self.my_canvas)
# add that new frame to a window in the canvas
self.my_canvas.create_window((0, 0), window=self.second_frame, anchor='nw')
def _on_mousewheel(self, event):
self.my_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
我注意到你已经解决了这个问题,但 @acw1668 给你的解决方案,就我而言,部分有效。所以,我决定 post 在这里我的代码,以防有人像我一样不幸。
我使用了@acw1668 class 但稍微修改了一下。
将 'bind_all' 替换为 'bind' 的唯一效果是在滚动条区域以外的任何地方都禁用鼠标滚动,因此我必须定义小部件的行为(self.canvas) 当事件 ('MouseWheel') 发生时。因此,我进行了一些搜索,最后找到了另一个问题
class ScrollableFrame(Frame):
"""
Defines a ScrollbarFrame class
"""
def __init__(self, container, width=800, height=700, bg = "white", *args, **kwargs):
super().__init__(container, *args, **kwargs)
# to hide the border
highlightthickness = 0
# Create a main frame
self.main_frame = Frame(container, width=width, height=height, bg=bg)
self.main_frame.pack(side=TOP, fill=BOTH, expand=1) # expand frame to the size of the container
#create a canvas
#highlightthickness = 0 to hide the border
self.canvas = Canvas(self.main_frame, width=width, height=height, bg=bg, highlightthickness=highlightthickness)
self.canvas.pack(side=LEFT, fill=BOTH, expand=1)
self.scrollbar = Scrollbar(self.main_frame, orient=VERTICAL, command=self.canvas.yview)
self.scrollbar.pack(side=RIGHT, fill=Y)
self.scrollbar_x = Scrollbar(container, orient=HORIZONTAL, command=self.canvas.xview)
self.scrollbar_x.pack(side=BOTTOM, fill=X)
self.canvas.configure(yscrollcommand=self.scrollbar.set, xscrollcommand=self.scrollbar_x.set)
self.canvas.bind(
"<Configure>",
lambda e: self.canvas.configure (
scrollregion=self.canvas.bbox("all")
)
)
self.scrollable_frame = Frame(self.canvas, bg=bg)
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
#DO NOT PUT bind_all!!!
self.canvas.bind("<MouseWheel>", self._on_mousewheel)
self.canvas.bind("<MouseWheel>", self.set_mousewheel(self.canvas, self._on_mousewheel))
def _on_mousewheel(self, event):
"""
Allowes to scroll the mouse
"""
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def set_mousewheel(self, widget, command):
"""Activate / deactivate mousewheel scrolling when
cursor is over / not over the widget respectively."""
widget.bind("<Enter>", lambda _: widget.bind_all('<MouseWheel>', command))
widget.bind("<Leave>", lambda _: widget.unbind_all('<MouseWheel>'))