'Error scrolling in main window after top level window was closed
I am using Tkinter for building an app and i need scrollable windows. I created a scrollable container using Canvas: ScrollContainer . After this I incorporated the main logic of my program into this container where I put a button that opens another separate TopLevel window. This separate window also has to be scrollable . Therefore i also include it in the same container class
Now, the problem: When I run the program my main window scrolls fine. I open the TopLevel window after clicking the button. Secondary window scrolls fine. After I close the secondary window and hover the mouse again over the main window, now it doesn't scroll and i get an error in the console:
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"
Note:
Instead of self.my_canvas.bind_all("<MouseWheel>", self._on_mousewheel) i also tried with self.my_canvas.bind("<MouseWheel>", self._on_mousewheel) and now there is no error .However, the scrolling changes. You cannot scroll anymore if you hover over the labels. You only can scroll if you enlarge the window and hover mouse a bit on the right . I guess it's because you have to bring the mouse over the canvas as it is the only entity scrollable
ScrollContainer 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")
Main program logic:
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()
Solution 1:[1]
I noticed you already solved the problem but the solution @acw1668 gave to you, in my case, worked partially. So, I decided to post here my code in case someone was unlucky just like me.
I used @acw1668 class but modified it a bit.
The replacement of 'bind_all' with 'bind' had the mere effect to deactivate the scroll with the mouse everywhere besides of the scrolling bar zone so I had to define the behaviour of the widget (self.canvas) when the event ('MouseWheel') happens. So I made some searches and I ended up on another question HERE answered by @Saad who explained how to change a widget behaviour when the cursor is over it by means of a new method (set_mousewheel). Then, I putted all together and that's what I got:
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>'))
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | claudialorusso |
