1- using System . Windows . Interop ;
1+ using System . Windows . Interop ;
22
33namespace MaterialDesignThemes . Wpf ;
44
@@ -67,6 +67,9 @@ public static PaddingMode GetPaddingMode(DependencyObject element)
6767 private static readonly DependencyProperty HorizontalScrollSourceProperty = DependencyProperty . RegisterAttached (
6868 "HorizontalScrollSource" , typeof ( HwndSource ) , typeof ( ScrollViewerAssist ) , new PropertyMetadata ( null ) ) ;
6969
70+ private static readonly DependencyProperty BubbleVerticalScrollHookProperty = DependencyProperty . RegisterAttached (
71+ "BubbleVerticalScrollHook" , typeof ( HwndSourceHook ) , typeof ( ScrollViewerAssist ) , new PropertyMetadata ( null ) ) ;
72+
7073 public static readonly DependencyProperty SupportHorizontalScrollProperty = DependencyProperty . RegisterAttached (
7174 "SupportHorizontalScroll" , typeof ( bool ) , typeof ( ScrollViewerAssist ) , new PropertyMetadata ( false , OnSupportHorizontalScrollChanged ) ) ;
7275
@@ -114,6 +117,12 @@ static void OnUnloaded(object? sender, RoutedEventArgs e)
114117 }
115118 }
116119
120+ static void RemoveHandlers ( ScrollViewer scrollViewer )
121+ {
122+ WeakEventManager < ScrollViewer , RoutedEventArgs > . RemoveHandler ( scrollViewer , nameof ( ScrollViewer . Loaded ) , OnLoaded ) ;
123+ WeakEventManager < ScrollViewer , RoutedEventArgs > . RemoveHandler ( scrollViewer , nameof ( ScrollViewer . Unloaded ) , OnUnloaded ) ;
124+ }
125+
117126 static void RemoveHook ( ScrollViewer scrollViewer )
118127 {
119128 if ( scrollViewer . GetValue ( HorizontalScrollHookProperty ) is HwndSourceHook hook &&
@@ -138,12 +147,20 @@ static void RegisterHook(ScrollViewer scrollViewer)
138147 IntPtr Hook ( IntPtr hwnd , int msg , IntPtr wParam , IntPtr lParam , ref bool handled )
139148 {
140149 const int WM_MOUSEHWHEEL = 0x020E ;
150+ const int WM_DESTROY = 0x0002 ;
151+ const int WM_NCDESTROY = 0x0082 ;
141152 switch ( msg )
142153 {
143154 case WM_MOUSEHWHEEL when scrollViewer . IsMouseOver :
144155 int tilt = ( short ) ( ( wParam . ToInt64 ( ) >> 16 ) & 0xFFFF ) ;
145156 scrollViewer . ScrollToHorizontalOffset ( scrollViewer . HorizontalOffset + tilt ) ;
146157 return ( IntPtr ) 1 ;
158+ case WM_DESTROY :
159+ case WM_NCDESTROY :
160+ RemoveHandlers ( scrollViewer ) ;
161+ var source = PresentationSource . FromVisual ( scrollViewer ) as HwndSource ;
162+ source ? . RemoveHook ( Hook ) ;
163+ break ;
147164 }
148165 return IntPtr . Zero ;
149166 }
@@ -152,19 +169,19 @@ IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled
152169
153170 public static readonly DependencyProperty BubbleVerticalScrollProperty = DependencyProperty . RegisterAttached (
154171 "BubbleVerticalScroll" , typeof ( bool ) , typeof ( ScrollViewerAssist ) , new PropertyMetadata ( false , OnBubbleVerticalScrollChanged ) ) ;
155-
172+
156173 private static void OnBubbleVerticalScrollChanged ( DependencyObject d , DependencyPropertyChangedEventArgs e )
157174 {
158- if ( d is ScrollViewer scrollViewer )
175+ if ( d is ScrollViewer sv )
159176 {
160- if ( scrollViewer . IsLoaded )
177+ if ( sv . IsLoaded )
161178 {
162- DoOnLoaded ( scrollViewer ) ;
179+ DoOnLoaded ( sv ) ;
163180 }
164181 else
165182 {
166- WeakEventManager < ScrollViewer , RoutedEventArgs > . AddHandler ( scrollViewer , nameof ( ScrollViewer . Loaded ) , OnLoaded ) ;
167- WeakEventManager < ScrollViewer , RoutedEventArgs > . AddHandler ( scrollViewer , nameof ( ScrollViewer . Unloaded ) , OnUnloaded ) ;
183+ RegisterForLoadedEvent ( sv ) ;
184+ RegisterForUnloadedEvent ( sv ) ;
168185 }
169186 }
170187
@@ -176,10 +193,48 @@ static void OnLoaded(object? sender, RoutedEventArgs e)
176193 }
177194 }
178195
196+ static void UnregisterForLoadedEvent ( ScrollViewer sv )
197+ {
198+ WeakEventManager < ScrollViewer , RoutedEventArgs > . RemoveHandler ( sv , nameof ( ScrollViewer . Loaded ) , OnLoaded ) ;
199+ }
200+
201+ static void RegisterForLoadedEvent ( ScrollViewer sv )
202+ {
203+ // Avoid double registrations
204+ UnregisterForLoadedEvent ( sv ) ;
205+ WeakEventManager < ScrollViewer , RoutedEventArgs > . AddHandler ( sv , nameof ( ScrollViewer . Loaded ) , OnLoaded ) ;
206+ }
207+
208+ static void UnregisterForUnloadedEvent ( ScrollViewer sv )
209+ {
210+ WeakEventManager < ScrollViewer , RoutedEventArgs > . RemoveHandler ( sv , nameof ( ScrollViewer . Unloaded ) , OnUnloaded ) ;
211+ }
212+
213+ static void RegisterForUnloadedEvent ( ScrollViewer sv )
214+ {
215+ // Avoid double registrations
216+ UnregisterForUnloadedEvent ( sv ) ;
217+ WeakEventManager < ScrollViewer , RoutedEventArgs > . AddHandler ( sv , nameof ( ScrollViewer . Unloaded ) , OnUnloaded ) ;
218+ }
219+
220+ static void UnregisterForMouseWheelEvent ( ScrollViewer sv )
221+ {
222+ sv . RemoveHandler ( UIElement . MouseWheelEvent , ( RoutedEventHandler ) ScrollViewerOnMouseWheel ) ;
223+ }
224+
225+ static void RegisterForMouseWheelEvent ( ScrollViewer sv )
226+ {
227+ // Avoid double registrations
228+ UnregisterForMouseWheelEvent ( sv ) ;
229+ sv . AddHandler ( UIElement . MouseWheelEvent , ( RoutedEventHandler ) ScrollViewerOnMouseWheel , true ) ;
230+ }
231+
179232 static void DoOnLoaded ( ScrollViewer sv )
180233 {
181234 if ( GetBubbleVerticalScroll ( sv ) )
182235 {
236+ RegisterForUnloadedEvent ( sv ) ;
237+ RegisterForMouseWheelEvent ( sv ) ;
183238 RegisterHook ( sv ) ;
184239 }
185240 else
@@ -192,29 +247,55 @@ static void OnUnloaded(object? sender, RoutedEventArgs e)
192247 {
193248 if ( sender is ScrollViewer sv )
194249 {
195- RemoveHook ( sv ) ;
250+ UnregisterForUnloadedEvent ( sv ) ;
251+ UnregisterForMouseWheelEvent ( sv ) ;
196252 }
197253 }
198254
199- static void RemoveHook ( ScrollViewer scrollViewer )
255+ static void RemoveHook ( ScrollViewer sv )
200256 {
201- scrollViewer . RemoveHandler ( UIElement . MouseWheelEvent , ( RoutedEventHandler ) ScrollViewerOnMouseWheel ) ;
257+ var source = PresentationSource . FromVisual ( sv ) as HwndSource ;
258+ if ( source is not null && sv . GetValue ( BubbleVerticalScrollHookProperty ) is HwndSourceHook hook )
259+ {
260+ source . RemoveHook ( hook ) ;
261+ sv . SetValue ( BubbleVerticalScrollHookProperty , null ) ;
262+ }
202263 }
203264
204- static void RegisterHook ( ScrollViewer scrollViewer )
265+ static void RegisterHook ( ScrollViewer sv )
205266 {
206- RemoveHook ( scrollViewer ) ;
207- scrollViewer . AddHandler ( UIElement . MouseWheelEvent , ( RoutedEventHandler ) ScrollViewerOnMouseWheel , true ) ;
267+ RemoveHook ( sv ) ;
268+ if ( PresentationSource . FromVisual ( sv ) is HwndSource source )
269+ {
270+ HwndSourceHook hook = Hook ;
271+ source . AddHook ( hook ) ;
272+ sv . SetValue ( BubbleVerticalScrollHookProperty , hook ) ;
273+ }
274+
275+ IntPtr Hook ( IntPtr hwnd , int msg , IntPtr wParam , IntPtr lParam , ref bool handled )
276+ {
277+ const int WM_DESTROY = 0x0002 ;
278+ const int WM_NCDESTROY = 0x0082 ;
279+ switch ( msg )
280+ {
281+ case WM_DESTROY :
282+ case WM_NCDESTROY :
283+ UnregisterForMouseWheelEvent ( sv ) ;
284+ UnregisterForLoadedEvent ( sv ) ;
285+ UnregisterForUnloadedEvent ( sv ) ;
286+ RemoveHook ( sv ) ;
287+ break ;
288+ }
289+ return IntPtr . Zero ;
290+ }
208291 }
209292
210293 // This relay is only needed because the UIElement.AddHandler() has strict requirements for the signature of the passed Delegate
211- static void ScrollViewerOnMouseWheel ( object sender , RoutedEventArgs e ) => HandleMouseWheel ( sender , ( MouseWheelEventArgs ) e ) ;
294+ static void ScrollViewerOnMouseWheel ( object ? sender , RoutedEventArgs e ) => HandleMouseWheel ( sender , ( MouseWheelEventArgs ) e ) ;
212295
213- static void HandleMouseWheel ( object sender , MouseWheelEventArgs e )
296+ static void HandleMouseWheel ( object ? sender , MouseWheelEventArgs e )
214297 {
215- var scrollViewer = ( ScrollViewer ) sender ;
216-
217- if ( scrollViewer . GetVisualAncestry ( ) . Skip ( 1 ) . FirstOrDefault ( ) is not UIElement parentUiElement )
298+ if ( sender is not ScrollViewer sv || sv . GetVisualAncestry ( ) . Skip ( 1 ) . FirstOrDefault ( ) is not UIElement parentUiElement )
218299 return ;
219300
220301 // Re-raise the mouse wheel event on the visual parent to bubble it upwards
0 commit comments