feat: Improve canvas scrolling functionality with enhanced mouse wheel event handling

This commit is contained in:
William Valentin
2025-07-29 17:42:38 -07:00
parent 30750710b8
commit aad02f0d36
+119 -23
View File
@@ -70,18 +70,6 @@ class UIManager:
input_frame = ttk.Frame(canvas)
input_frame.grid_columnconfigure(1, weight=1)
# Configure canvas scrolling
def configure_scroll_region(event=None):
canvas.configure(scrollregion=canvas.bbox("all"))
def on_mousewheel(event):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
input_frame.bind("<Configure>", configure_scroll_region)
canvas.bind("<MouseWheel>", on_mousewheel) # Windows/Linux
canvas.bind("<Button-4>", lambda e: canvas.yview_scroll(-1, "units")) # Linux
canvas.bind("<Button-5>", lambda e: canvas.yview_scroll(1, "units")) # Linux
# Place canvas and scrollbar in the container
canvas.grid(row=0, column=0, sticky="nsew")
scrollbar.grid(row=0, column=1, sticky="ns")
@@ -94,8 +82,53 @@ class UIManager:
canvas_width = canvas.winfo_width()
canvas.itemconfig(canvas_window, width=canvas_width)
# Configure canvas scrolling
def configure_scroll_region(event=None):
canvas.configure(scrollregion=canvas.bbox("all"))
def on_mousewheel(event):
# Check if canvas is scrollable before scrolling
if canvas.cget("scrollregion"):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def on_mousewheel_linux_up(event):
# Linux mouse wheel up
if canvas.cget("scrollregion"):
canvas.yview_scroll(-1, "units")
def on_mousewheel_linux_down(event):
# Linux mouse wheel down
if canvas.cget("scrollregion"):
canvas.yview_scroll(1, "units")
input_frame.bind("<Configure>", configure_scroll_region)
canvas.bind("<Configure>", configure_canvas_width)
# Bind mouse wheel events to canvas and main container
canvas.bind("<MouseWheel>", on_mousewheel) # Windows/Linux
canvas.bind("<Button-4>", on_mousewheel_linux_up) # Linux
canvas.bind("<Button-5>", on_mousewheel_linux_down) # Linux
main_container.bind("<MouseWheel>", on_mousewheel) # Windows/Linux
main_container.bind("<Button-4>", on_mousewheel_linux_up) # Linux
main_container.bind("<Button-5>", on_mousewheel_linux_down) # Linux
# Bind mouse wheel to input frame and its children for better scrolling
self._bind_mousewheel_to_widget_tree(input_frame, canvas)
# Set focus to canvas to ensure it receives scroll events
canvas.focus_set()
# Add mouse enter/leave events to manage focus for scrolling
def on_mouse_enter(event):
canvas.focus_set()
def on_mouse_leave(event):
# Don't change focus when leaving to avoid disrupting user interaction
pass
main_container.bind("<Enter>", on_mouse_enter)
canvas.bind("<Enter>", on_mouse_enter)
# Create variables for symptoms
symptom_vars: dict[str, tk.IntVar] = {
"depression": tk.IntVar(value=0),
@@ -168,6 +201,11 @@ class UIManager:
# Set default date to today
date_var.set(datetime.now().strftime("%m/%d/%Y"))
# Ensure mouse wheel binding is applied to all newly created widgets
main_container.update_idletasks()
canvas.configure(scrollregion=canvas.bbox("all"))
self._bind_mousewheel_to_widget_tree(input_frame, canvas)
# Return all UI elements and variables
return {
"frame": main_container,
@@ -311,17 +349,48 @@ class UIManager:
canvas.itemconfig(canvas_window, width=canvas_width)
def on_mousewheel(event):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
# Check if canvas is scrollable before scrolling
if canvas.cget("scrollregion"):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def on_mousewheel_linux_up(event):
# Linux mouse wheel up
if canvas.cget("scrollregion"):
canvas.yview_scroll(-1, "units")
def on_mousewheel_linux_down(event):
# Linux mouse wheel down
if canvas.cget("scrollregion"):
canvas.yview_scroll(1, "units")
main_container.bind("<Configure>", configure_scroll_region)
canvas.bind("<Configure>", configure_canvas_width)
# Bind mouse wheel events to canvas and edit window
canvas.bind("<MouseWheel>", on_mousewheel) # Windows/Linux
canvas.bind("<Button-4>", lambda e: canvas.yview_scroll(-1, "units")) # Linux
canvas.bind("<Button-5>", lambda e: canvas.yview_scroll(1, "units")) # Linux
canvas.bind("<Button-4>", on_mousewheel_linux_up) # Linux
canvas.bind("<Button-5>", on_mousewheel_linux_down) # Linux
edit_win.bind("<MouseWheel>", on_mousewheel) # Windows/Linux
edit_win.bind("<Button-4>", on_mousewheel_linux_up) # Linux
edit_win.bind("<Button-5>", on_mousewheel_linux_down) # Linux
# Bind mouse wheel to main container and its children for better scrolling
self._bind_mousewheel_to_widget_tree(main_container, canvas)
# Set focus to canvas to ensure it receives scroll events
canvas.focus_set()
# Add mouse enter/leave events to manage focus for scrolling
def on_mouse_enter(event):
canvas.focus_set()
def on_mouse_leave(event):
# Don't change focus when leaving to avoid disrupting user interaction
pass
edit_win.bind("<Enter>", on_mouse_enter)
canvas.bind("<Enter>", on_mouse_enter)
# Unpack values - handle both old and new CSV formats
if len(values) == 10:
# Old format: date, dep, anx, slp, app, bup, hydro, gaba, prop, note
@@ -427,6 +496,9 @@ class UIManager:
edit_win.update_idletasks()
canvas.configure(scrollregion=canvas.bbox("all"))
# Ensure mouse wheel binding is applied to all newly created widgets
self._bind_mousewheel_to_widget_tree(main_container, canvas)
# Make window modal
edit_win.focus_set()
edit_win.grab_set()
@@ -968,18 +1040,42 @@ class UIManager:
"""Recursively bind mouse wheel events to all widgets in the tree."""
def on_mousewheel(event):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
# Check if canvas is scrollable before scrolling
if canvas.cget("scrollregion"):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def on_mousewheel_linux_up(event):
if canvas.cget("scrollregion"):
canvas.yview_scroll(-1, "units")
def on_mousewheel_linux_down(event):
if canvas.cget("scrollregion"):
canvas.yview_scroll(1, "units")
# Bind to the widget itself
widget.bind("<MouseWheel>", on_mousewheel)
widget.bind("<Button-4>", lambda e: canvas.yview_scroll(-1, "units"))
widget.bind("<Button-5>", lambda e: canvas.yview_scroll(1, "units"))
try:
widget.bind("<MouseWheel>", on_mousewheel)
widget.bind("<Button-4>", on_mousewheel_linux_up)
widget.bind("<Button-5>", on_mousewheel_linux_down)
except tk.TclError:
# Some widgets might not support binding
pass
# Recursively bind to all children
for child in widget.winfo_children():
# Skip certain widgets that have their own scrolling behavior
if not isinstance(child, tk.Text | tk.Listbox | tk.Canvas):
self._bind_mousewheel_to_widget_tree(child, canvas)
try:
for child in widget.winfo_children():
# Skip widgets that have their own scrolling behavior or are problematic
skip_types = (tk.Text, tk.Listbox, tk.Canvas, ttk.Notebook)
if not isinstance(child, skip_types):
self._bind_mousewheel_to_widget_tree(child, canvas)
elif isinstance(child, ttk.Notebook):
# For notebooks, bind to their tab frames
for tab_id in child.tabs():
tab_widget = child.nametowidget(tab_id)
self._bind_mousewheel_to_widget_tree(tab_widget, canvas)
except tk.TclError:
# Handle potential errors when accessing children
pass
def _create_edit_fields(
self,