Bubbling scroll events from a ListView to its parent Bubbling scroll events from a ListView to its parent wpf wpf

Bubbling scroll events from a ListView to its parent


You need to capture the preview mouse wheel event in the inner listview

MyListView.PreviewMouseWheel += HandlePreviewMouseWheel;

Or in the XAML

<ListView ... PreviewMouseWheel="HandlePreviewMouseWheel">

then stop the event from scrolling the listview and raise the event in the parent listview.

private void HandlePreviewMouseWheel(object sender, MouseWheelEventArgs e) {    if (!e.Handled) {        e.Handled = true;        var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);        eventArg.RoutedEvent = UIElement.MouseWheelEvent;        eventArg.Source = sender;        var parent = ((Control)sender).Parent as UIElement;        parent.RaiseEvent(eventArg);    }}

Creds go to @robert-wagner who solved this for me a few months ago.


Another nice solution using attached behavior.I like it because it decoples the solution from the Control.

Create a no scroling behavior which will catch the PreviewMouseWheel(Tunneling) event and raise a new MouseWheelEvent(Bubbling)

public sealed class IgnoreMouseWheelBehavior : Behavior<UIElement>{  protected override void OnAttached( )  {    base.OnAttached( );    AssociatedObject.PreviewMouseWheel += AssociatedObject_PreviewMouseWheel ;  }protected override void OnDetaching( ){    AssociatedObject.PreviewMouseWheel -= AssociatedObject_PreviewMouseWheel;    base.OnDetaching( );}void AssociatedObject_PreviewMouseWheel(object sender, MouseWheelEventArgs e){    e.Handled = true;    var e2 = new MouseWheelEventArgs(e.MouseDevice,e.Timestamp,e.Delta);    e2.RoutedEvent = UIElement.MouseWheelEvent;        AssociatedObject.RaiseEvent(e2);    }}

Then attach the behavior to any UIElement with nested ScrollViewers case

 <ListBox Name="ForwardScrolling">    <i:Interaction.Behaviors>        <local:IgnoreMouseWheelBehavior />    </i:Interaction.Behaviors></ListBox>

all credit to Josh Einstein Blog


If you're coming here looking for a solution to bubble the event ONLY if the child is at the top and scrolling up or the bottom and scrolling down, here's a solution. I only tested this with DataGrid, but it should work with other controls as well.

public class ScrollParentWhenAtMax : Behavior<FrameworkElement>{    protected override void OnAttached()    {        base.OnAttached();        this.AssociatedObject.PreviewMouseWheel += PreviewMouseWheel;    }    protected override void OnDetaching()    {        this.AssociatedObject.PreviewMouseWheel -= PreviewMouseWheel;        base.OnDetaching();    }    private void PreviewMouseWheel(object sender, MouseWheelEventArgs e)    {        var scrollViewer = GetVisualChild<ScrollViewer>(this.AssociatedObject);        var scrollPos = scrollViewer.ContentVerticalOffset;        if ((scrollPos == scrollViewer.ScrollableHeight && e.Delta < 0)            || (scrollPos == 0 && e.Delta > 0))        {            e.Handled = true;            var e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta);            e2.RoutedEvent = UIElement.MouseWheelEvent;            AssociatedObject.RaiseEvent(e2);        }    }    private static T GetVisualChild<T>(DependencyObject parent) where T : Visual    {        T child = default(T);        int numVisuals = VisualTreeHelper.GetChildrenCount(parent);        for (int i = 0; i < numVisuals; i++)        {            Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);            child = v as T;            if (child == null)            {                child = GetVisualChild<T>(v);            }            if (child != null)            {                break;            }        }        return child;    }}

To attach this behavior, add the following XMLNS and XAML to your element:

    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"    <i:Interaction.Behaviors>        <shared:ScrollParentWhenAtMax />    </i:Interaction.Behaviors>