2009-10-18 5 views
41

В моем приложении WPF у меня есть ListView, у которого ScrollViewer.VerticalScrollBarVisibility установлено значение Disabled. Он содержится в пределах ScrollViewer. Когда я пытаюсь использовать колесико мыши над ListView, внешний ScrollViewer не прокручивается, потому что ListView захватывает события прокрутки.Bubbling прокручивает события от ListView до его родителя

Как я могу заставить ListView разрешить событиям прокрутки до уровня ScrollViewer?

ответ

59

Вам нужно захватить событие просмотра колеса мыши во внутренней ListView

ctl.PreviewMouseWheel += PreviewMouseWheel; 

затем остановить событие от прокрутки ListView и поднять событие в родительском ListView.

Кредиты идут к @ robert-wagner, которые решили это для меня несколько месяцев назад.

+5

Я не могу за это признаться: http://stackoverflow.com/questions/3498686/wpf-remove-scrollviewer-from-treeview –

+0

Это работало для моего UserControl с ListView в Это! : D – QuantumHive

3

В зависимости от вашей конкретной ситуации существуют разные подходы, но я нашел, что это работает хорошо. Если предположить, что основные положения заключается в следующем:

<Window Height="200" Width="200"> 
<Grid> 
    <ScrollViewer Name="sViewer"> 
     <StackPanel> 
      <Label Content="Scroll works here" Margin="10" /> 
      <ListView Name="listTest" Margin="10" 
         PreviewMouseWheel="listTest_PreviewMouseWheel" 
         ScrollViewer.VerticalScrollBarVisibility="Disabled"> 
       <ListView.ItemsSource> 
        <Int32Collection> 
         1,2,3,4,5,6,7,8,9,10 
        </Int32Collection> 
       </ListView.ItemsSource> 
       <ListView.View> 
        <GridView> 
         <GridViewColumn Header="Column 1" /> 
        </GridView> 
       </ListView.View> 
      </ListView> 
     </StackPanel> 
    </ScrollViewer> 
</Grid> 
</Window> 

Поднятие MouseWheelEvent себя во время PreviewMouseWheel кажется, чтобы заставить ScrollViewer работать. Хотелось бы, чтобы я знал, почему это кажется очень противоречивым.

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); 
} 
+0

это работает, спасибо! – Drake

+1

По-прежнему работа после пяти лет :-) – Sunny

21

Другое хорошее решение, использующее приложенное поведение. Мне нравится, потому что он отменяет решение от Control.

Создать поведение не scroling, который будет поймать (туннельный) событие PreviewMouseWheel и поднять новую 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); 

    } 
} 

Затем присоедините поведение к любому UIElement с вложенными ScrollViewers случае

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

все кредиты от Josh Einstein Blog

+5

Для всех, кто использует это решение, вы должны убедиться, что вы добавили пространство имен 'using System.Windows.Interactivity', которое можно добавить в ваш текущий проект с помощью команды' Install-Package Expression.Blend.Sdk' from консоль менеджера пакетов NuGet. Кроме того, вы должны добавить пространство имен в ваш .xaml-файл с помощью 'xmlns: i =" http://schemas.microsoft.com/expression/2010/interactivity "'. – gbmhunter

+1

Кроме того, 'System.Windows',' System.Windows.Input' и 'System.Windows.Controls'. И я также нашел альтернативный способ ссылки на сборку интерактивности: 'xmlns: i =" clr-namespace: System.Windows.Interactivity; assembly = System.Windows.Interactivity "'. – vapcguy

6

Если вы собираетесь искать решение, чтобы пузырить событие ТОЛЬКО, если ребенок находится в верхняя часть и прокрутка вверх или снизу и прокрутка вниз, вот решение. Я тестировал это только с помощью DataGrid, но он должен работать и с другими элементами управления.

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; 
    } 
} 

Чтобы прикрепить эту проблему, добавьте следующие Xmlns и XAML к вашему элементу:

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

    <i:Interaction.Behaviors> 
     <shared:ScrollParentWhenAtMax /> 
    </i:Interaction.Behaviors> 
+1

Работает очень хорошо. Спасибо. –

1

Мой случай использования был несколько иной. У меня очень большой scrollviewer, а внизу - еще один scrollviewer с максимальным числом 600. Я хочу прокрутить всю страницу до нижней части, пока не прохожу прокрутки к внутреннему scrollviewer. Это гарантирует, что вы сначала увидите весь scrollviewer, прежде чем начинать прокрутку.

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<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) 
    { 
     if (e.Delta < 0) 
     { 
      var outerscroller = GetVisualParent<ScrollViewer>(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<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; 
    } 

    private static T GetVisualParent<T>(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; 
    } 
} 
} 
0

Прошло некоторое время, так как я был на СО, но мне пришлось прокомментировать это. Любой туннель для предварительного просмотра событий, так почему мы это пузырям? Остановите туннель в родительском доме и сделайте это. в родителе добавить событие 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; 
} 
0

Вы также можете добиться того же, используя приложенное поведение. Это имеет то преимущество, что не нужна библиотека System.Windows.Interactivity. Логика была взята из других ответов, только реализация отличается.

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, string value) 
    { 
     o.SetValue(IgnoreScrollProperty, value); 
    } 

    public static string GetIgnoreScroll(DependencyObject o) 
    { 
     return (string)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); 
     } 
    } 
} 

А потом в XAML:

<DataGrid ItemsSource="{Binding Items}"> 

<DataGrid.RowDetailsTemplate> 
    <DataTemplate> 

     <ListView ItemsSource="{Binding Results}" 
        behaviours:IgnoreScrollBehaviour.IgnoreScroll="True"> 
      <ListView.ItemTemplate> 
       <DataTemplate> 
        ... 
       </DataTemplate> 
      </ListView.ItemTemplate> 
     </ListView> 
    </DataTemplate> 
</DataGrid.RowDetailsTemplate> 

<DataGrid.Columns> 
    ... 
</DataGrid.Columns> 

</DataGrid>