IT Share you

ListView에서 부모로 스크롤 이벤트 버블 링

shareyou 2021. 1. 10. 19:22
반응형

ListView에서 부모로 스크롤 이벤트 버블 링


내 WPF 응용 프로그램에서 나는이 ListView누구 ScrollViewer.VerticalScrollBarVisibility로 설정됩니다 Disabled. 그것은 내에 포함되어 있습니다 ScrollViewer. 에서 마우스 휠을 사용하려고하면 에서 스크롤 이벤트를 캡처 하기 때문에 ListView바깥 쪽 ScrollViewer이 스크롤되지 않습니다 ListView.

어떻게 강제 할 수 ListView받는 거품까지 스크롤 이벤트를 할 수 있도록 ScrollViewer?


내부 목록보기에서 미리보기 마우스 휠 이벤트를 캡처해야합니다.

ctl.PreviewMouseWheel += PreviewMouseWheel;

그런 다음 이벤트가 목록보기 스크롤을 중지하고 상위 목록보기에서 이벤트를 발생시킵니다.

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는 몇 달 전에 저를 위해이 문제를 해결 한 @ robert-wagner로 이동합니다.


연결된 동작을 사용하는 또 다른 좋은 솔루션입니다. Control에서 솔루션을 분리하기 때문에 좋아합니다.

PreviewMouseWheel (Tunneling) 이벤트를 포착하고 새 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);

    }
}

그런 다음 중첩 된 ScrollViewers 케이스가있는 모든 UIElement에 동작을 연결합니다.

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

Josh Einstein 블로그에 대한 모든 크레딧


자녀가 상단에 있고 위로 스크롤하거나 아래로 스크롤하고 아래로 스크롤하는 경우에만 이벤트를 버블 링하는 솔루션을 찾고 있다면 여기에 해결책이 있습니다. 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>

정확한 상황에 따라 다른 접근 방식이 있지만 이것이 잘 작동한다는 것을 알았습니다. 기본 상황이 다음과 같다고 가정합니다.

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

PreviewMouseWheel 중에 MouseWheelEvent를 직접 발생 시키면 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);
}

첨부 된 동작을 사용하여 동일한 결과를 얻을 수도 있습니다. 이것은 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>

내 사용 사례는 약간 달랐습니다. 나는 매우 큰 scrollviewer와 아래에 최대 600의 또 다른 scrollviewer를 가지고 있습니다. scrollevents를 내부 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;
    }
}
}

감사합니다 Keyle

귀하의 답변을 RX 확장 방법으로 수정했습니다.

    public static IDisposable ScrollsParent(this ItemsControl itemsControl)
    {
        return Observable.FromEventPattern<MouseWheelEventHandler, MouseWheelEventArgs>(
           x => itemsControl.PreviewMouseWheel += x,
           x => itemsControl.PreviewMouseWheel -= x)
           .Subscribe(e =>
           {
               if(!e.EventArgs.Handled)
               {
                   e.EventArgs.Handled = true;
                   var eventArg = new MouseWheelEventArgs(e.EventArgs.MouseDevice, e.EventArgs.Timestamp, e.EventArgs.Delta)
                   {
                       RoutedEvent = UIElement.MouseWheelEvent,
                       Source = e.Sender
                   };
                   var parent = ((Control)e.Sender).Parent as UIElement;
                   parent.RaiseEvent(eventArg);
               }
           });
    }

용법:

 myList.ScrollsParent().DisposeWith(disposables);

Ok been a while since I have been on SO but I had to comment on this. Any Preview event tunnels, so why are we bubbling it up? Stop the tunnel in the parent and be done with it. in the parent add a PreviewMouseWheel event.

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

ReferenceURL : https://stackoverflow.com/questions/1585462/bubbling-scroll-events-from-a-listview-to-its-parent

반응형