@@ -100,6 +100,8 @@ def __init__(
100100 self ._gui_complexity : str = ""
101101 self ._is_loading : bool = False
102102 self ._add_button_widget : Optional [ttk .Button ] = None
103+ self ._page_scroll_after_id : Optional [str ] = None
104+ self ._page_scroll_destroy_binding_registered : bool = False
103105
104106 style = ttk .Style ()
105107 style .configure ("narrow.TButton" , padding = 0 , width = 4 , border = (0 , 0 , 0 , 0 ))
@@ -226,7 +228,15 @@ def _apply_scroll_position(self, scroll_to_bottom: bool) -> None:
226228 self .canvas .yview_moveto (position )
227229
228230 def _update_table (self , params : dict [str , ArduPilotParameter ], gui_complexity : str ) -> None :
229- """Initialize scroll load."""
231+ """
232+ Update the table from a new parameter set and start incremental rendering.
233+
234+ This method is the entry point used when the table is rebuilt for a
235+ fresh ``params`` mapping. It resets the incremental-load state, stores
236+ the active GUI complexity, renders the first batch of rows immediately
237+ (currently 20 parameters), and then starts the page-scroll/polling
238+ mechanism that loads additional chunks as needed.
239+ """
230240 self ._params_list = list (params .items ())
231241 self ._total_params = len (self ._params_list )
232242 self ._current_idx = 0
@@ -291,18 +301,52 @@ def _load_next_chunk(self, chunk_size: int = 15) -> None:
291301 self .canvas .configure (scrollregion = self .canvas .bbox ("all" ))
292302 self ._is_loading = False
293303
304+ def _cancel_page_scroll_after (self ) -> None :
305+ """Cancel any scheduled page-scroll polling callback."""
306+ after_id = getattr (self , "_page_scroll_after_id" , None )
307+ if not after_id :
308+ return
309+ try :
310+ self .after_cancel (after_id )
311+ except tk .TclError :
312+ pass
313+ finally :
314+ self ._page_scroll_after_id = None
315+
316+ def _on_destroy_cancel_page_scroll (self , _event : Optional [tk .Event ] = None ) -> None :
317+ """Stop page-scroll polling when the widget is being destroyed."""
318+ self ._cancel_page_scroll_after ()
319+
294320 def _page_scroll (self ) -> None :
295321 """Load more items when reaching the bottom."""
296- if not self .winfo_exists () or self ._current_idx >= self ._total_params :
322+ self ._page_scroll_after_id = None
323+
324+ # 1. ALWAYS check if the widget exists FIRST before doing Tkinter operations
325+ if not self .winfo_exists ():
326+ self ._cancel_page_scroll_after ()
327+ return
328+
329+ # 2. Now it is safe to bind events
330+ if not getattr (self , "_page_scroll_destroy_binding_registered" , False ):
331+ self .bind ("<Destroy>" , self ._on_destroy_cancel_page_scroll , add = "+" )
332+ self ._page_scroll_destroy_binding_registered = True
333+
334+ # 3. Check if we are done loading
335+ if self ._current_idx >= self ._total_params :
336+ self ._cancel_page_scroll_after ()
297337 return
298338
299339 try :
300340 if self .canvas .yview ()[1 ] > 0.85 :
301341 self ._load_next_chunk (chunk_size = 15 )
302342 except tk .TclError :
303- pass
343+ return
304344
305- self .after (150 , self ._page_scroll )
345+ if self .winfo_exists () and self ._current_idx < self ._total_params :
346+ try :
347+ self ._page_scroll_after_id = self .after (150 , self ._page_scroll )
348+ except tk .TclError :
349+ self ._page_scroll_after_id = None
306350
307351 def _create_column_widgets (self , param_name : str , param : ArduPilotParameter , show_upload_column : bool ) -> list [tk .Widget ]:
308352 """Create all column widgets for a parameter row."""
0 commit comments