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>