WPF - DataGrid 嵌套在 Expander 中没有滚动数据网格内容
WPF - DataGrid nested in Expander no scrolling datagrid content
我在 WPF 应用程序上有 Expander 和嵌套的 DataGrid - 它创建用户控件。我在代码隐藏上为数据(来自数据库)列表中的每个元素创建了这个控件。最后,我有一个列表,其中每个元素都是 Expadned 并带有嵌套的 DataGrid。当我开发项目时,我看到了 DataDrid,但是当我开发许多组件时,我必须滚动内容。当光标位于扩展器上时,元素滚动有效,但当我将鼠标悬停在 DataGrid 上时,滚动不起作用。
示例代码:
<ScrollViewer HorizontalAlignment="Left">
<DockPanel>
<Expander x:Name="Expander1" Expanded="Expander1_Expanded">
<Expander.Content>
<DataGrid x:Name="DataGrid1" MouseLeftButtonUp="DataGrid1_MouseLeftButtonDown" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden" >
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="name1" Binding="{Binding Name}" IsReadOnly="True" />
<DataGridTextColumn Header="name2" Binding="{Binding Value}" IsReadOnly="True"/>
<DataGridTextColumn Header="name3" Binding="{Binding UnitName}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
</Expander.Content>
<Expander.Style>
<Style TargetType="Expander">
<Setter Property="IsExpanded" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsExpanded, RelativeSource={RelativeSource Self}}" Value="True">
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
</Expander>
// and mor expander, added from codebehind
</DockPanel>
</ScrollViewer>
最后格子:
当鼠标位于绿色环向右滚动的位置时
发生这种情况是因为 DataGrid
本身可能包含一个 ScrollViewer
,当您的项目多于给定高度内的项目时,它就会出现。在这些情况下,您将希望允许 DataGrid
首先尝试处理滚动事件,如果它不知道该怎么做,例如当您试图向下滚动时已经在底部,将滚动事件传递给它的父级。
现在,看起来您的 DataGrid
s 实际上不是可滚动的,这意味着以下内容可能有点矫枉过正,但是通过对鼠标进行以下修改可以获得实现上述目的的通用解决方案车轮处理机:
/// <summary>
/// Helper for allowing scroll events to pass from a <see cref="DataGrid"/> to its parent.
/// This ensures that a "scroll down" event occurring at an already scrolled-down
/// <see cref="DataGrid"/> will be passed on to its parent, which might be able to handle
/// it instead.
/// </summary>
public class DataGridScrollCorrector
{
public static bool GetFixScrolling(DependencyObject obj) =>
(bool)obj.GetValue(FixScrollingProperty);
public static void SetFixScrolling(DependencyObject obj, bool value) =>
obj.SetValue(FixScrollingProperty, value);
public static readonly DependencyProperty FixScrollingProperty =
DependencyProperty.RegisterAttached("FixScrolling", typeof(bool), typeof(DataGridScrollCorrector), new FrameworkPropertyMetadata(false, OnFixScrollingPropertyChanged));
private static void OnFixScrollingPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var grid = sender as DataGrid;
if (grid == null)
throw new ArgumentException("The dependency property can only be attached to a DataGrid", nameof(sender));
if ((bool)e.NewValue)
grid.PreviewMouseWheel += HandlePreviewMouseWheel;
else
grid.PreviewMouseWheel -= HandlePreviewMouseWheel;
}
/// <summary>
/// Finds the first child of a given type in a given <see cref="DependencyObject"/>.
/// </summary>
/// <typeparam name="T">The type of the child to search for.</typeparam>
/// <param name="depObj">The object whose children we are interested in.</param>
/// <returns>The child object.</returns>
private static T FindVisualChild<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
var visualChild = child as T;
if (visualChild != null) return visualChild;
var childItem = FindVisualChild<T>(child);
if (childItem != null) return childItem;
}
return null;
}
/// <summary>
/// Attempts to scroll the <see cref="ScrollViewer"/> in the <see cref="DataGrid"/>.
/// If no scrolling occurs, pass the event to a parent.
/// </summary>
private static void HandlePreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var grid = sender as DataGrid;
var viewer = FindVisualChild<ScrollViewer>(grid);
if (viewer != null)
{
// We listen on changes to the ScrollViewer's scroll offset; if that changes
// we can consider our event handled. In case the ScrollChanged event is never
// raised, we take this to mean that we are at the top/bottom of our scroll viewer,
// in which case we provide the event to our parent.
ScrollChangedEventHandler handler = (senderScroll, eScroll) =>
e.Handled = true;
viewer.ScrollChanged += handler;
// Scroll +/- 3 rows depending on whether we are scrolling up or down. The
// forced layout update is necessary to ensure that the event is called
// immediately (as opposed to after some small delay).
double oldOffset = viewer.VerticalOffset;
double offsetDelta = e.Delta > 0 ? -3 : 3;
viewer.ScrollToVerticalOffset(oldOffset + offsetDelta);
viewer.UpdateLayout();
viewer.ScrollChanged -= handler;
}
if (e.Handled) return;
e.Handled = true;
var eventArg =
new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = UIElement.MouseWheelEvent,
Source = sender
};
var parent = ((Control)sender).Parent as UIElement;
parent?.RaiseEvent(eventArg);
}
}
这里,硬编码的 3
是要在 DataGrid
中滚动的行数。然后,您可以将此校正器应用于所有相关的 DataGrid
。例如,要在应用程序的所有网格上使用它,您可以将它添加到 App.xaml
中的 Application.Resources
,如下所示:
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Style TargetType="DataGrid">
<Setter Property="local:DataGridScrollCorrector.FixScrolling" Value="True" />
</Style>
</Application.Resources>
</Application>
信用:这个解决方案有点基于 on/inspired 提到的 in this blog post 但它将其功能限制为 DataGrid
(因为根据我的经验,否则它会破坏一堆其他不准备传输其事件的控件)。
我在 WPF 应用程序上有 Expander 和嵌套的 DataGrid - 它创建用户控件。我在代码隐藏上为数据(来自数据库)列表中的每个元素创建了这个控件。最后,我有一个列表,其中每个元素都是 Expadned 并带有嵌套的 DataGrid。当我开发项目时,我看到了 DataDrid,但是当我开发许多组件时,我必须滚动内容。当光标位于扩展器上时,元素滚动有效,但当我将鼠标悬停在 DataGrid 上时,滚动不起作用。
示例代码:
<ScrollViewer HorizontalAlignment="Left">
<DockPanel>
<Expander x:Name="Expander1" Expanded="Expander1_Expanded">
<Expander.Content>
<DataGrid x:Name="DataGrid1" MouseLeftButtonUp="DataGrid1_MouseLeftButtonDown" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden" >
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="name1" Binding="{Binding Name}" IsReadOnly="True" />
<DataGridTextColumn Header="name2" Binding="{Binding Value}" IsReadOnly="True"/>
<DataGridTextColumn Header="name3" Binding="{Binding UnitName}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
</Expander.Content>
<Expander.Style>
<Style TargetType="Expander">
<Setter Property="IsExpanded" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsExpanded, RelativeSource={RelativeSource Self}}" Value="True">
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
</Expander>
// and mor expander, added from codebehind
</DockPanel>
</ScrollViewer>
最后格子:
当鼠标位于绿色环向右滚动的位置时
发生这种情况是因为 DataGrid
本身可能包含一个 ScrollViewer
,当您的项目多于给定高度内的项目时,它就会出现。在这些情况下,您将希望允许 DataGrid
首先尝试处理滚动事件,如果它不知道该怎么做,例如当您试图向下滚动时已经在底部,将滚动事件传递给它的父级。
现在,看起来您的 DataGrid
s 实际上不是可滚动的,这意味着以下内容可能有点矫枉过正,但是通过对鼠标进行以下修改可以获得实现上述目的的通用解决方案车轮处理机:
/// <summary>
/// Helper for allowing scroll events to pass from a <see cref="DataGrid"/> to its parent.
/// This ensures that a "scroll down" event occurring at an already scrolled-down
/// <see cref="DataGrid"/> will be passed on to its parent, which might be able to handle
/// it instead.
/// </summary>
public class DataGridScrollCorrector
{
public static bool GetFixScrolling(DependencyObject obj) =>
(bool)obj.GetValue(FixScrollingProperty);
public static void SetFixScrolling(DependencyObject obj, bool value) =>
obj.SetValue(FixScrollingProperty, value);
public static readonly DependencyProperty FixScrollingProperty =
DependencyProperty.RegisterAttached("FixScrolling", typeof(bool), typeof(DataGridScrollCorrector), new FrameworkPropertyMetadata(false, OnFixScrollingPropertyChanged));
private static void OnFixScrollingPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var grid = sender as DataGrid;
if (grid == null)
throw new ArgumentException("The dependency property can only be attached to a DataGrid", nameof(sender));
if ((bool)e.NewValue)
grid.PreviewMouseWheel += HandlePreviewMouseWheel;
else
grid.PreviewMouseWheel -= HandlePreviewMouseWheel;
}
/// <summary>
/// Finds the first child of a given type in a given <see cref="DependencyObject"/>.
/// </summary>
/// <typeparam name="T">The type of the child to search for.</typeparam>
/// <param name="depObj">The object whose children we are interested in.</param>
/// <returns>The child object.</returns>
private static T FindVisualChild<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
var visualChild = child as T;
if (visualChild != null) return visualChild;
var childItem = FindVisualChild<T>(child);
if (childItem != null) return childItem;
}
return null;
}
/// <summary>
/// Attempts to scroll the <see cref="ScrollViewer"/> in the <see cref="DataGrid"/>.
/// If no scrolling occurs, pass the event to a parent.
/// </summary>
private static void HandlePreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var grid = sender as DataGrid;
var viewer = FindVisualChild<ScrollViewer>(grid);
if (viewer != null)
{
// We listen on changes to the ScrollViewer's scroll offset; if that changes
// we can consider our event handled. In case the ScrollChanged event is never
// raised, we take this to mean that we are at the top/bottom of our scroll viewer,
// in which case we provide the event to our parent.
ScrollChangedEventHandler handler = (senderScroll, eScroll) =>
e.Handled = true;
viewer.ScrollChanged += handler;
// Scroll +/- 3 rows depending on whether we are scrolling up or down. The
// forced layout update is necessary to ensure that the event is called
// immediately (as opposed to after some small delay).
double oldOffset = viewer.VerticalOffset;
double offsetDelta = e.Delta > 0 ? -3 : 3;
viewer.ScrollToVerticalOffset(oldOffset + offsetDelta);
viewer.UpdateLayout();
viewer.ScrollChanged -= handler;
}
if (e.Handled) return;
e.Handled = true;
var eventArg =
new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
{
RoutedEvent = UIElement.MouseWheelEvent,
Source = sender
};
var parent = ((Control)sender).Parent as UIElement;
parent?.RaiseEvent(eventArg);
}
}
这里,硬编码的 3
是要在 DataGrid
中滚动的行数。然后,您可以将此校正器应用于所有相关的 DataGrid
。例如,要在应用程序的所有网格上使用它,您可以将它添加到 App.xaml
中的 Application.Resources
,如下所示:
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Style TargetType="DataGrid">
<Setter Property="local:DataGridScrollCorrector.FixScrolling" Value="True" />
</Style>
</Application.Resources>
</Application>
信用:这个解决方案有点基于 on/inspired 提到的 in this blog post 但它将其功能限制为 DataGrid
(因为根据我的经验,否则它会破坏一堆其他不准备传输其事件的控件)。