bestsource

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

bestsource 2023. 4. 19. 23:18
반응형

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

WPF 어플리케이션에는ListView누구의.ScrollViewer.VerticalScrollBarVisibility로 설정되어 있다.Disabled에 포함되어 있습니다.ScrollViewer마우스 휠을 사용하려고 하면ListView, 외측ScrollViewer이 때문에 스크롤되지 않습니다.ListView스크롤 이벤트를 캡처하고 있습니다.

어떻게 하면 강제로ListView스크롤 이벤트가 버블업되도록 하려면ScrollViewer?

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

MyListView.PreviewMouseWheel += HandlePreviewMouseWheel;

또는 XAML에서

<ListView ... PreviewMouseWheel="HandlePreviewMouseWheel">

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

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

몇 달 전에 이 문제를 해결해 준 @robert-wagner에게 신용이 돌아간다.

부가 동작을 사용한 또 다른 훌륭한 솔루션.나는 그것을 좋아한다 왜냐하면 그것은 통제관으로부터 해결책을 끌어내기 때문이다.

미리보기를 캡처할 스크롤링 금지 동작을 만듭니다.MouseWheel(터널링) 이벤트 및 새 마우스 생성휠 이벤트(거품)

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 케이스가 있는 UIlement에 동작을 연결합니다.

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

모두 조쉬 아인슈타인 블로그 덕분이야

아이가 맨 위에 있을 때만 이벤트를 버블링할 수 있는 솔루션을 찾고 있는 경우 또는 맨 아래에 있을 때만 이벤트를 버블링할 수 있습니다.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>

마우스 키우기미리 보기 중 WheelEvent 사용자 지정MouseWheel이 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);
}

연결된 동작을 사용하여 동일한 작업을 수행할 수도 있습니다.이 방법에는 시스템이 필요하지 않다는 장점이 있습니다.창문들.인터랙티브 라이브러리이 논리는 다른 답변에서 따온 것이며, 구현만 다를 뿐입니다.

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>

고마워 Kyle

당신의 답변을 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);

제 사용 사례는 조금 달랐습니다.저는 매우 큰 스크롤 뷰어를 가지고 있고, 하단에는 최대 높이가 600인 스크롤 뷰어를 가지고 있습니다.스크롤레벤트를 내부 스크롤 뷰어에 전달할 때까지 페이지 전체를 아래로 스크롤합니다.이렇게 하면 스크롤을 시작하기 전에 스크롤 뷰어 전체를 먼저 볼 수 있습니다.

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

네, SO에 오랜만에 출연하지만 이 부분에 대해서 언급을 해야 할 것 같아요.미리보기 이벤트 터널인데 왜 우리가 그걸 부풀리는 거죠?부모에서 터널을 중지하고 종료합니다.부모에서 미리보기를 추가합니다.Mouse Wheel 이벤트

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

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

반응형