WPF ScrollViewer内嵌套ScrollViewer或DataGrid、ListBox等包含滚动条的控件,鼠标置于内部
ScrollViewer时滚轮动作无法传递到外部ScrollViewer,该方法实现内部ScrollViewer到底或到顶后,滚轮动作传递至外部,让外部ScrollViewer滚动
1.1 ScrollViewer嵌套
- MainWindow.xaml.cs添加隧道事件
private void ChildScroll_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { if (e.Delta > 0 && ChildScroll.VerticalOffset == 0) { // 向上滚动并且已经到顶部 OuterScrollViewer.ScrollToVerticalOffset(OuterScrollViewer.VerticalOffset - e.Delta); e.Handled = true; } else if (e.Delta < 0 && ChildScroll.VerticalOffset >= ChildScroll.ExtentHeight - ChildScroll.ViewportHeight) { // 向下滚动并且已经到底部 OuterScrollViewer.ScrollToVerticalOffset(OuterScrollViewer.VerticalOffset - e.Delta); e.Handled = true; } }
- MainWindow.xaml
<ScrollViewer
x:Name="OuterScrollViewer"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ScrollViewer
x:Name="ChildScroll"
HorizontalScrollBarVisibility="Auto"
PreviewMouseWheel="ChildScroll_PreviewMouseWheel"
VerticalScrollBarVisibility="Auto">
<!-- 其他内容 -->
</ScrollViewer>
</ScrollViewer>
1.2 ScrollViewer内DataGrid、ListBox
- MainWindow.xaml.cs
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ListBoxScrollViewer = GetScrollViewer(Listbox1);
DataGridScrollViewer = GetScrollViewer(DG);
}
private ScrollViewer? ListBoxScrollViewer;
private void Listbox1_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (ListBoxScrollViewer == null)
{
return;
}
if (e.Delta > 0 && ListBoxScrollViewer.VerticalOffset == 0)
{
// 向上滚动并且已经到顶部
OuterScrollViewer.ScrollToVerticalOffset(OuterScrollViewer.VerticalOffset - e.Delta);
e.Handled = true;
}
else if (e.Delta < 0 && ListBoxScrollViewer.VerticalOffset >= ListBoxScrollViewer.ExtentHeight - ListBoxScrollViewer.ViewportHeight)
{
// 向下滚动并且已经到底部
OuterScrollViewer.ScrollToVerticalOffset(OuterScrollViewer.VerticalOffset - e.Delta);
e.Handled = true;
}
}
private ScrollViewer? DataGridScrollViewer;
private void DG_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (DataGridScrollViewer == null)
{
return ;
}
if (e.Delta > 0 && DataGridScrollViewer.VerticalOffset == 0)
{
// 向上滚动并且已经到顶部
OuterScrollViewer.ScrollToVerticalOffset(OuterScrollViewer.VerticalOffset - e.Delta);
e.Handled = true;
}
else if (e.Delta < 0 && DataGridScrollViewer.VerticalOffset >= DataGridScrollViewer.ExtentHeight - DataGridScrollViewer.ViewportHeight)
{
// 向下滚动并且已经到底部
OuterScrollViewer.ScrollToVerticalOffset(OuterScrollViewer.VerticalOffset - e.Delta);
e.Handled = true;
}
}
/// <summary>
/// 获取控件内部ScrollViewer
/// </summary>
/// <param name="depObj"></param>
/// <returns></returns>
private ScrollViewer? GetScrollViewer(DependencyObject depObj)
{
if (depObj is ScrollViewer) return depObj as ScrollViewer;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = GetScrollViewer(child);
if (result != null) return result;
}
return null;
}
- MainWindow.xaml
<ScrollViewer
x:Name="OuterScrollViewer"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<DataGrid
x:Name="DG"
PreviewMouseWheel="DG_PreviewMouseWheel" />
<ListBox
x:Name="Listbox1"
PreviewMouseWheel="Listbox1_PreviewMouseWheel">
</ListBox>
</ScrollViewer>
2、MVVM
- 添加行为ScrollViewerMouseWheelBehavior.cs
using Microsoft.Xaml.Behaviors;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows;
namespace WpfApp1.Lib
{
public class ScrollViewerMouseWheelBehavior : Behavior<ScrollViewer>
{
public static readonly DependencyProperty OuterScrollViewerProperty =
DependencyProperty.Register(
nameof(OuterScrollViewer),
typeof(ScrollViewer),
typeof(ScrollViewerMouseWheelBehavior));
public ScrollViewer OuterScrollViewer
{
get { return (ScrollViewer)GetValue(OuterScrollViewerProperty); }
set { SetValue(OuterScrollViewerProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PreviewMouseWheel += OnPreviewMouseWheel;
}
protected override void OnDetaching()
{
AssociatedObject.PreviewMouseWheel -= OnPreviewMouseWheel;
base.OnDetaching();
}
private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (OuterScrollViewer == null) return;
var scrollViewer = (ScrollViewer)sender;
if (e.Delta > 0 && scrollViewer.VerticalOffset == 0)
{
// 向上滚动并且已经到顶部
OuterScrollViewer.ScrollToVerticalOffset(OuterScrollViewer.VerticalOffset - e.Delta);
e.Handled = true;
}
else if (e.Delta < 0 && scrollViewer.VerticalOffset >= scrollViewer.ExtentHeight - scrollViewer.ViewportHeight)
{
// 向下滚动并且已经到底部
OuterScrollViewer.ScrollToVerticalOffset(OuterScrollViewer.VerticalOffset - e.Delta);
e.Handled = true;
}
}
}
}
- 添加ListBoxScrollViewerMouseWheelBehavior.cs(DataGrid同)
using Microsoft.Xaml.Behaviors;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows;
namespace WpfApp1.Lib
{
public partial class ListBoxScrollViewerMouseWheelBehavior : Behavior<ListBox>
{
public static readonly DependencyProperty OuterScrollViewerProperty =
DependencyProperty.Register(
nameof(OuterScrollViewer),
typeof(ScrollViewer),
typeof(ListBoxScrollViewerMouseWheelBehavior));
public ScrollViewer OuterScrollViewer
{
get { return (ScrollViewer)GetValue(OuterScrollViewerProperty); }
set { SetValue(OuterScrollViewerProperty, value); }
}
private ScrollViewer _innerScrollViewer;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += OnDataGridLoaded;
}
private void OnDataGridLoaded(object sender, RoutedEventArgs e)
{
// 获取 DataGrid 内部的 ScrollViewer
_innerScrollViewer = GetScrollViewer(AssociatedObject);
if (_innerScrollViewer != null)
{
_innerScrollViewer.PreviewMouseWheel += OnPreviewMouseWheel;
}
}
protected override void OnDetaching()
{
if (_innerScrollViewer != null)
{
_innerScrollViewer.PreviewMouseWheel -= OnPreviewMouseWheel;
}
AssociatedObject.Loaded -= OnDataGridLoaded;
base.OnDetaching();
}
private ScrollViewer GetScrollViewer(DependencyObject depObj)
{
if (depObj is ScrollViewer) return depObj as ScrollViewer;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = GetScrollViewer(child);
if (result != null) return result;
}
return null;
}
private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (OuterScrollViewer == null || _innerScrollViewer == null) return;
if (e.Delta > 0 && _innerScrollViewer.VerticalOffset == 0)
{
// 向上滚动并且已经到顶部
OuterScrollViewer.ScrollToVerticalOffset(OuterScrollViewer.VerticalOffset - e.Delta);
e.Handled = true;
}
else if (e.Delta < 0 && _innerScrollViewer.VerticalOffset >= _innerScrollViewer.ExtentHeight - _innerScrollViewer.ViewportHeight)
{
// 向下滚动并且已经到底部
OuterScrollViewer.ScrollToVerticalOffset(OuterScrollViewer.VerticalOffset - e.Delta);
e.Handled = true;
}
}
}
}
- MainWindow.xaml
<!-- 引入命名空间 -->
xmlns:scroll="clr-namespace:WpfApp1.Lib"
<ScrollViewer
x:Name="OuterScrollViewer"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ScrollViewer
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<i:Interaction.Behaviors >
<scroll:ScrollViewerMouseWheelBehavior
OuterScrollViewer="{Binding ElementName=OuterScrollViewer}" />
</i:Interaction.Behaviors>
</ScrollViewer>
<DataGrid>
<i:Interaction.Behaviors>
<scroll:DataGridScrollViewerMouseWheelBehavior
OuterScrollViewer="{Binding ElementName=OuterScrollViewer}" />
</i:Interaction.Behaviors>
</DataGrid>
<ListBox>
<i:Interaction.Behaviors>
<scroll:ListBoxScrollViewerMouseWheelBehavior
OuterScrollViewer="{Binding ElementName=OuterScrollViewer}" />
</i:Interaction.Behaviors>
</ListBox>
</ScrollViewer>