Faire défiler les événements de défilement depuis un ListView vers son parent

Dans mon application WPF, j’ai un ListView dont ScrollViewer.VerticalScrollBarVisibility est défini sur Disabled . Il est contenu dans un ScrollViewer . Lorsque ScrollViewer d’utiliser la molette de la souris sur ListView , le ScrollViewer externe ne défile pas car ListView capture les événements de défilement.

Comment puis-je forcer ListView pour permettre aux événements de défilement de remonter à ScrollViewer ?

Vous devez capturer l’événement de la molette de la souris dans l’aperçu interne

 ctl.PreviewMouseWheel += PreviewMouseWheel; 

puis arrêtez l’événement de faire défiler la vue de liste et déclenchez l’événement dans la vue de liste parente.

 private static void PreviewMouseWheel(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 va à @ robert-wagner qui a résolu ce problème pour moi il y a quelques mois.

Une autre solution intéressante en utilisant le comportement attaché. Je l’aime bien parce que ça déconne la solution du contrôle.

Créez un comportement sans scroling qui interceptera l’événement PreviewMouseWheel (Tunneling) et déclenchera un nouveau MouseWheelEvent (Bubbling)

 public sealed class IgnoreMouseWheelBehavior : Behavior { 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); } } 

Ensuite, attachez le comportement à un UIElement avec un cas ScrollViewers nested

       

tout crédit à Josh Einstein Blog

Si vous venez ici à la recherche d’une solution pour faire éclater l’événement SEULEMENT si l’enfant est en haut et que vous faites défiler vers le haut ou vers le bas et vers le bas, voici une solution. Je l’ai seulement testé avec DataGrid, mais il devrait également fonctionner avec d’autres contrôles.

 public class ScrollParentWhenAtMax : Behavior { 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(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(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(v); } if (child != null) { break; } } return child; } } 

Pour attacher ce comportement, ajoutez les XMLNS et XAML suivants à votre élément:

  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"    

Il existe différentes approches en fonction de votre situation exacte, mais j’ai trouvé que cela fonctionnait bien. En supposant que votre situation de base est la suivante:

         1,2,3,4,5,6,7,8,9,10             

Elever MouseWheelEvent vous-même pendant PreviewMouseWheel semble forcer ScrollViewer à fonctionner. Je voudrais savoir pourquoi, cela semble très contre-intuitif.

 private void listTest_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { e.Handled = true; MouseWheelEventArgs e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta); e2.RoutedEvent = UIElement.MouseWheelEvent; listTest.RaiseEvent(e2); } 

Mon cas d’utilisation était légèrement différent. J’ai un très grand scrollviewer et en bas un scrollviewer qui a une hauteur maximale de 600. Je veux faire défiler toute la page vers le bas jusqu’à ce que je passe scrollevents au scrollviewer interne. Cela garantit que vous voyez tout le scrollviewer en premier, avant de commencer le défilement.

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Interactivity; using System.Windows.Media; namespace CleverScroller.Helper { public class ScrollParentWhenAtMax : Behavior { 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) { if (e.Delta < 0) { var outerscroller = GetVisualParent(this.AssociatedObject); if (outerscroller.ContentVerticalOffset < outerscroller.ScrollableHeight) { e.Handled = true; var e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta); e2.RoutedEvent = UIElement.MouseWheelEvent; AssociatedObject.RaiseEvent(e2); } } else { var scrollViewer = GetVisualChild(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(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(v); } if (child != null) { break; } } return child; } private static T GetVisualParent(DependencyObject parent) where T : Visual { T obj = default(T); Visual v = (Visual)VisualTreeHelper.GetParent(parent); do { v = (Visual)VisualTreeHelper.GetParent(v); obj = v as T; } while (obj == null); return obj; } } } 

Ok depuis un moment que je suis sur SO mais je devais commenter ça. N’importe quel tunnel d’événement de prévisualisation, alors pourquoi nous le faisons bouillir? Arrête le tunnel chez le parent et finis-en. dans le parent, ajoutez un événement PreviewMouseWheel.

  private void UIElement_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) { var scrollViewer = FindName("LeftPanelScrollViwer"); // name your parent mine is a scrollViewer ((ScrollViewer) scrollViewer)?.ScrollToVerticalOffset(e.Delta); e.Handled = true; } 

Vous pouvez également réaliser la même chose en utilisant un comportement associé. Cela présente l’avantage de ne pas avoir besoin de la bibliothèque System.Windows.Interactivity. La logique a été prise des autres réponses, seule la mise en œuvre est différente.

 public static class IgnoreScrollBehaviour { public static readonly DependencyProperty IgnoreScrollProperty = DependencyProperty.RegisterAttached("IgnoreScroll", typeof(bool), typeof(IgnoreScrollBehaviour), new PropertyMetadata(OnIgnoreScollChanged)); public static void SetIgnoreScroll(DependencyObject o, ssortingng value) { o.SetValue(IgnoreScrollProperty, value); } public static ssortingng GetIgnoreScroll(DependencyObject o) { return (ssortingng)o.GetValue(IgnoreScrollProperty); } private static void OnIgnoreScollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { bool ignoreScoll = (bool)e.NewValue; UIElement element = d as UIElement; if (element == null) return; if (ignoreScoll) { element.PreviewMouseWheel += Element_PreviewMouseWheel; } else { element.PreviewMouseWheel -= Element_PreviewMouseWheel; } } private static void Element_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { UIElement element = sender as UIElement; if (element != null) { e.Handled = true; var e2 = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta); e2.RoutedEvent = UIElement.MouseWheelEvent; element.RaiseEvent(e2); } } } 

Et puis dans le XAML:

       ...       ...