Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 107 additions & 50 deletions src/MaterialDesignThemes.Wpf/ScrollViewerAssist.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Windows.Interop;
using System.Windows.Interop;

namespace MaterialDesignThemes.Wpf;

Expand Down Expand Up @@ -64,25 +64,25 @@ public static PaddingMode GetPaddingMode(DependencyObject element)
private static readonly DependencyProperty HorizontalScrollHookProperty = DependencyProperty.RegisterAttached(
"HorizontalScrollHook", typeof(HwndSourceHook), typeof(ScrollViewerAssist), new PropertyMetadata(null));

private static readonly DependencyProperty HorizontalScrollSourceProperty = DependencyProperty.RegisterAttached(
"HorizontalScrollSource", typeof(HwndSource), typeof(ScrollViewerAssist), new PropertyMetadata(null));
private static readonly DependencyProperty BubbleVerticalScrollHookProperty = DependencyProperty.RegisterAttached(
"BubbleVerticalScrollHook", typeof(HwndSourceHook), typeof(ScrollViewerAssist), new PropertyMetadata(null));

public static readonly DependencyProperty SupportHorizontalScrollProperty = DependencyProperty.RegisterAttached(
"SupportHorizontalScroll", typeof(bool), typeof(ScrollViewerAssist), new PropertyMetadata(false, OnSupportHorizontalScrollChanged));

private static void OnSupportHorizontalScrollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//Based on: https://blog.walterlv.com/post/handle-horizontal-scrolling-of-touchpad-en.html
if (d is ScrollViewer scrollViewer)
if (d is ScrollViewer sv)
{
if (scrollViewer.IsLoaded)
if (sv.IsLoaded)
{
DoOnLoaded(scrollViewer);
DoOnLoaded(sv);
}
else
{
WeakEventManager<ScrollViewer, RoutedEventArgs>.AddHandler(scrollViewer, nameof(ScrollViewer.Loaded), OnLoaded);
WeakEventManager<ScrollViewer, RoutedEventArgs>.AddHandler(scrollViewer, nameof(ScrollViewer.Unloaded), OnUnloaded);
RegisterForLoadedEvent(sv);
RegisterForUnloadedEvent(sv);
}
}

Expand All @@ -94,10 +94,35 @@ static void OnLoaded(object? sender, RoutedEventArgs e)
}
}

static void UnregisterForLoadedEvent(ScrollViewer sv)
{
WeakEventManager<ScrollViewer, RoutedEventArgs>.RemoveHandler(sv, nameof(ScrollViewer.Loaded), OnLoaded);
}

static void RegisterForLoadedEvent(ScrollViewer sv)
{
// Avoid double registrations
UnregisterForLoadedEvent(sv);
WeakEventManager<ScrollViewer, RoutedEventArgs>.AddHandler(sv, nameof(ScrollViewer.Loaded), OnLoaded);
}

static void UnregisterForUnloadedEvent(ScrollViewer sv)
{
WeakEventManager<ScrollViewer, RoutedEventArgs>.RemoveHandler(sv, nameof(ScrollViewer.Unloaded), OnUnloaded);
}

static void RegisterForUnloadedEvent(ScrollViewer sv)
{
// Avoid double registrations
UnregisterForUnloadedEvent(sv);
WeakEventManager<ScrollViewer, RoutedEventArgs>.AddHandler(sv, nameof(ScrollViewer.Unloaded), OnUnloaded);
}

static void DoOnLoaded(ScrollViewer sv)
{
if (GetSupportHorizontalScroll(sv))
{
RegisterForUnloadedEvent(sv);
RegisterHook(sv);
}
else
Expand All @@ -114,30 +139,23 @@ static void OnUnloaded(object? sender, RoutedEventArgs e)
}
}

static void RemoveHandlers(ScrollViewer scrollViewer)
{
WeakEventManager<ScrollViewer, RoutedEventArgs>.RemoveHandler(scrollViewer, nameof(ScrollViewer.Loaded), OnLoaded);
WeakEventManager<ScrollViewer, RoutedEventArgs>.RemoveHandler(scrollViewer, nameof(ScrollViewer.Unloaded), OnUnloaded);
}

static void RemoveHook(ScrollViewer scrollViewer)
static void RemoveHook(ScrollViewer sv)
{
if (scrollViewer.GetValue(HorizontalScrollHookProperty) is HwndSourceHook hook &&
scrollViewer.GetValue(HorizontalScrollSourceProperty) is HwndSource source)
var source = PresentationSource.FromVisual(sv) as HwndSource;
if (source is not null && sv.GetValue(HorizontalScrollHookProperty) is HwndSourceHook hook)
{
source.RemoveHook(hook);
scrollViewer.SetValue(HorizontalScrollHookProperty, null);
sv.SetValue(HorizontalScrollHookProperty, null);
}
}

static void RegisterHook(ScrollViewer scrollViewer)
static void RegisterHook(ScrollViewer sv)
{
RemoveHook(scrollViewer);
if (PresentationSource.FromVisual(scrollViewer) is HwndSource source)
RemoveHook(sv);
if (PresentationSource.FromVisual(sv) is HwndSource source)
{
HwndSourceHook hook = Hook;
scrollViewer.SetValue(HorizontalScrollSourceProperty, source);
scrollViewer.SetValue(HorizontalScrollHookProperty, hook);
sv.SetValue(HorizontalScrollHookProperty, hook);
source.AddHook(hook);
}

Expand All @@ -148,15 +166,15 @@ IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled
const int WM_NCDESTROY = 0x0082;
switch (msg)
{
case WM_MOUSEHWHEEL when scrollViewer.IsMouseOver:
case WM_MOUSEHWHEEL when sv.IsMouseOver:
int tilt = (short)((wParam.ToInt64() >> 16) & 0xFFFF);
scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + tilt);
sv.ScrollToHorizontalOffset(sv.HorizontalOffset + tilt);
return (IntPtr)1;
case WM_DESTROY:
case WM_NCDESTROY:
RemoveHandlers(scrollViewer);
var source = PresentationSource.FromVisual(scrollViewer) as HwndSource;
source?.RemoveHook(Hook);
UnregisterForLoadedEvent(sv);
UnregisterForUnloadedEvent(sv);
RemoveHook(sv);
break;
}
return IntPtr.Zero;
Expand All @@ -166,19 +184,19 @@ IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled

public static readonly DependencyProperty BubbleVerticalScrollProperty = DependencyProperty.RegisterAttached(
"BubbleVerticalScroll", typeof(bool), typeof(ScrollViewerAssist), new PropertyMetadata(false, OnBubbleVerticalScrollChanged));

private static void OnBubbleVerticalScrollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is ScrollViewer scrollViewer)
if (d is ScrollViewer sv)
{
if (scrollViewer.IsLoaded)
if (sv.IsLoaded)
{
DoOnLoaded(scrollViewer);
DoOnLoaded(sv);
}
else
{
WeakEventManager<ScrollViewer, RoutedEventArgs>.AddHandler(scrollViewer, nameof(ScrollViewer.Loaded), OnLoaded);
WeakEventManager<ScrollViewer, RoutedEventArgs>.AddHandler(scrollViewer, nameof(ScrollViewer.Unloaded), OnUnloaded);
RegisterForLoadedEvent(sv);
RegisterForUnloadedEvent(sv);
}
}

Expand All @@ -190,10 +208,48 @@ static void OnLoaded(object? sender, RoutedEventArgs e)
}
}

static void UnregisterForLoadedEvent(ScrollViewer sv)
{
WeakEventManager<ScrollViewer, RoutedEventArgs>.RemoveHandler(sv, nameof(ScrollViewer.Loaded), OnLoaded);
}

static void RegisterForLoadedEvent(ScrollViewer sv)
{
// Avoid double registrations
UnregisterForLoadedEvent(sv);
WeakEventManager<ScrollViewer, RoutedEventArgs>.AddHandler(sv, nameof(ScrollViewer.Loaded), OnLoaded);
}

static void UnregisterForUnloadedEvent(ScrollViewer sv)
{
WeakEventManager<ScrollViewer, RoutedEventArgs>.RemoveHandler(sv, nameof(ScrollViewer.Unloaded), OnUnloaded);
}

static void RegisterForUnloadedEvent(ScrollViewer sv)
{
// Avoid double registrations
UnregisterForUnloadedEvent(sv);
WeakEventManager<ScrollViewer, RoutedEventArgs>.AddHandler(sv, nameof(ScrollViewer.Unloaded), OnUnloaded);
}

static void UnregisterForMouseWheelEvent(ScrollViewer sv)
{
sv.RemoveHandler(UIElement.MouseWheelEvent, (RoutedEventHandler)ScrollViewerOnMouseWheel);
}

static void RegisterForMouseWheelEvent(ScrollViewer sv)
{
// Avoid double registrations
UnregisterForMouseWheelEvent(sv);
sv.AddHandler(UIElement.MouseWheelEvent, (RoutedEventHandler)ScrollViewerOnMouseWheel, true);
}

static void DoOnLoaded(ScrollViewer sv)
{
if (GetBubbleVerticalScroll(sv))
{
RegisterForUnloadedEvent(sv);
RegisterForMouseWheelEvent(sv);
RegisterHook(sv);
}
else
Expand All @@ -206,29 +262,29 @@ static void OnUnloaded(object? sender, RoutedEventArgs e)
{
if (sender is ScrollViewer sv)
{
RemoveHook(sv);
UnregisterForUnloadedEvent(sv);
UnregisterForMouseWheelEvent(sv);
}
}

static void RemoveHandlers(ScrollViewer scrollViewer)
{
WeakEventManager<ScrollViewer, RoutedEventArgs>.RemoveHandler(scrollViewer, nameof(ScrollViewer.Loaded), OnLoaded);
WeakEventManager<ScrollViewer, RoutedEventArgs>.RemoveHandler(scrollViewer, nameof(ScrollViewer.Unloaded), OnUnloaded);
}

static void RemoveHook(ScrollViewer scrollViewer)
static void RemoveHook(ScrollViewer sv)
{
scrollViewer.RemoveHandler(UIElement.MouseWheelEvent, (RoutedEventHandler)ScrollViewerOnMouseWheel);
var source = PresentationSource.FromVisual(sv) as HwndSource;
if (source is not null && sv.GetValue(BubbleVerticalScrollHookProperty) is HwndSourceHook hook)
{
source.RemoveHook(hook);
sv.SetValue(BubbleVerticalScrollHookProperty, null);
}
}

static void RegisterHook(ScrollViewer scrollViewer)
static void RegisterHook(ScrollViewer sv)
{
RemoveHook(scrollViewer);
if (PresentationSource.FromVisual(scrollViewer) is HwndSource source)
RemoveHook(sv);
if (PresentationSource.FromVisual(sv) is HwndSource source)
{
HwndSourceHook hook = Hook;
source.AddHook(hook);
scrollViewer.AddHandler(UIElement.MouseWheelEvent, (RoutedEventHandler)ScrollViewerOnMouseWheel, true);
sv.SetValue(BubbleVerticalScrollHookProperty, hook);
}

IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
Expand All @@ -239,9 +295,10 @@ IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled
{
case WM_DESTROY:
case WM_NCDESTROY:
RemoveHandlers(scrollViewer);
var source = PresentationSource.FromVisual(scrollViewer) as HwndSource;
source?.RemoveHook(Hook);
UnregisterForMouseWheelEvent(sv);
UnregisterForLoadedEvent(sv);
UnregisterForUnloadedEvent(sv);
RemoveHook(sv);
break;
}
return IntPtr.Zero;
Expand Down
Loading