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
'bestsource' 카테고리의 다른 글
WPF: TextBox에서 포커스를 프로그래밍 방식으로 삭제하는 방법 (0) | 2023.04.19 |
---|---|
다른 페이지에서 로그인이 필요한 페이지에 액세스하기 위한 CURL (0) | 2023.04.19 |
Swift를 사용하여 루트 보기로 팝업하려면 어떻게 해야 합니까?UI? (0) | 2023.04.19 |
VBA를 사용하여 파일이 있는지 확인합니다. (0) | 2023.04.14 |
bash completion을 에일리어스로 작업하려면 어떻게 해야 하나요? (0) | 2023.04.14 |