From 5e538df4fcdcafb7d5b8624b30998c9ac09f0dfd Mon Sep 17 00:00:00 2001 From: Nicolai Henriksen Date: Thu, 5 Mar 2026 21:44:14 +0100 Subject: [PATCH] Remove ScrollViewerAssist weak event handlers on WM_DESTROY In a use case where a TabControl is hosted in an ElementHost inside of a WinForms application, the handlers were not removed, and thus an exception was thrown during application shutdown. --- .../ScrollViewerAssist.cs | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/MaterialDesignThemes.Wpf/ScrollViewerAssist.cs b/src/MaterialDesignThemes.Wpf/ScrollViewerAssist.cs index 2bfa6a398a..b5acb153b7 100644 --- a/src/MaterialDesignThemes.Wpf/ScrollViewerAssist.cs +++ b/src/MaterialDesignThemes.Wpf/ScrollViewerAssist.cs @@ -114,6 +114,12 @@ static void OnUnloaded(object? sender, RoutedEventArgs e) } } + static void RemoveHandlers(ScrollViewer scrollViewer) + { + WeakEventManager.RemoveHandler(scrollViewer, nameof(ScrollViewer.Loaded), OnLoaded); + WeakEventManager.RemoveHandler(scrollViewer, nameof(ScrollViewer.Unloaded), OnUnloaded); + } + static void RemoveHook(ScrollViewer scrollViewer) { if (scrollViewer.GetValue(HorizontalScrollHookProperty) is HwndSourceHook hook && @@ -138,12 +144,20 @@ static void RegisterHook(ScrollViewer scrollViewer) IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { const int WM_MOUSEHWHEEL = 0x020E; + const int WM_DESTROY = 0x0002; + const int WM_NCDESTROY = 0x0082; switch (msg) { case WM_MOUSEHWHEEL when scrollViewer.IsMouseOver: int tilt = (short)((wParam.ToInt64() >> 16) & 0xFFFF); scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + tilt); return (IntPtr)1; + case WM_DESTROY: + case WM_NCDESTROY: + RemoveHandlers(scrollViewer); + var source = PresentationSource.FromVisual(scrollViewer) as HwndSource; + source?.RemoveHook(Hook); + break; } return IntPtr.Zero; } @@ -196,6 +210,12 @@ static void OnUnloaded(object? sender, RoutedEventArgs e) } } + static void RemoveHandlers(ScrollViewer scrollViewer) + { + WeakEventManager.RemoveHandler(scrollViewer, nameof(ScrollViewer.Loaded), OnLoaded); + WeakEventManager.RemoveHandler(scrollViewer, nameof(ScrollViewer.Unloaded), OnUnloaded); + } + static void RemoveHook(ScrollViewer scrollViewer) { scrollViewer.RemoveHandler(UIElement.MouseWheelEvent, (RoutedEventHandler)ScrollViewerOnMouseWheel); @@ -204,7 +224,28 @@ static void RemoveHook(ScrollViewer scrollViewer) static void RegisterHook(ScrollViewer scrollViewer) { RemoveHook(scrollViewer); - scrollViewer.AddHandler(UIElement.MouseWheelEvent, (RoutedEventHandler)ScrollViewerOnMouseWheel, true); + if (PresentationSource.FromVisual(scrollViewer) is HwndSource source) + { + HwndSourceHook hook = Hook; + source.AddHook(hook); + scrollViewer.AddHandler(UIElement.MouseWheelEvent, (RoutedEventHandler)ScrollViewerOnMouseWheel, true); + } + + IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + const int WM_DESTROY = 0x0002; + const int WM_NCDESTROY = 0x0082; + switch (msg) + { + case WM_DESTROY: + case WM_NCDESTROY: + RemoveHandlers(scrollViewer); + var source = PresentationSource.FromVisual(scrollViewer) as HwndSource; + source?.RemoveHook(Hook); + break; + } + return IntPtr.Zero; + } } // This relay is only needed because the UIElement.AddHandler() has strict requirements for the signature of the passed Delegate