WPF DataGrid选择矩形滚动时不跟随内容移动问题求助
WPF DataGrid 选择矩形随滚动失效问题解决
我在WPF DataGrid中复刻Windows文件资源管理器的选择矩形功能,通过Adorner实现了框选逻辑,但滚动时出现异常:当DataGrid自带的ScrollViewer滚动内容时,选择矩形保持固定不动,导致原本与矩形相交的行因滚动偏移被错误取消选择,矩形像是悬浮在DataGrid控件上方,而非绑定到滚动内容上。需要实现像资源管理器那样,选择矩形随DataGrid内容同步滚动的效果。
原始代码
XAML部分
<DataGrid Grid.Row="2" x:Name="FileDataGrid" HeadersVisibility="Column" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" AutoGenerateColumns="False" CanUserResizeColumns="True" GridLinesVisibility="None" IsReadOnly="False" HorizontalScrollBarVisibility="Auto" PreviewMouseLeftButtonDown="DataGrid_PreviewMouseLeftButtonDown" PreviewMouseLeftButtonUp="DataGrid_PreviewMouseLeftButtonUp" RowHeight="25" Loaded="Grid_Loaded" PreviewMouseMove="DataGrid_PreviewMouseMove" ScrollViewer.ScrollChanged="FileDataGrid_ScrollChanged" SelectionChanged="FileDataGrid_SelectionChanged" SelectionMode="Extended"> <DataGrid.CellStyle> <Style TargetType="{x:Type DataGridCell}"> <Style.Triggers> <Trigger Property="DataGridCell.IsSelected" Value="True"> <Setter Property="BorderBrush"> <Setter.Value> <SolidColorBrush Color="Transparent" /> </Setter.Value> </Setter> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" /> <Setter Property="Background"> <Setter.Value> <SolidColorBrush Color="Yellow" /> </Setter.Value> </Setter> </Trigger> </Style.Triggers> </Style> </DataGrid.CellStyle> <DataGrid.RowStyle> <Style BasedOn="{StaticResource @DataGridRowStyle}" TargetType="{x:Type DataGridRow}"> <Setter Property="IsHitTestVisible" Value="True" /> <EventSetter Event="MouseEnter" Handler="DataGridRow_MouseEnter"/> </Style> </DataGrid.RowStyle> <DataGrid.Columns> <DataGridTemplateColumn x:Name="LeftName" Width="1.5*" MinWidth="180" CanUserResize="True" CanUserSort="True" Header="Name" IsReadOnly="False" SortMemberPath="Name"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Image Width="17" Height="17" Margin="15,0,-25,0" HorizontalAlignment="Right" Source="Images/sms-mobile.png" /> <TextBlock Margin="35,4,0,0" Text="{Binding Name}" ToolTip="{Binding Folder}" /> </StackPanel> </DataTemplate> </DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Image Width="17" Height="17" Margin="15,0,-25,0" HorizontalAlignment="Right" Source="Images/sms-mobile.png" /> <TextBox Width="auto" Height="Auto" Margin="35,0,10,0" Padding="0" HorizontalAlignment="Left" VerticalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Loaded="TextBox_Loaded" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" /> </StackPanel> </DataTemplate> </DataGridTemplateColumn.CellEditingTemplate> </DataGridTemplateColumn> <DataGridTextColumn x:Name="sizecolumn" MaxWidth="100" Binding="{Binding FormattedSize}" Header="Size" /> <DataGridTextColumn x:Name="lastModifiedColumn" MaxWidth="120" Binding="{Binding LastModified}" Header="Date Modified " /> </DataGrid.Columns> </DataGrid>
C#部分
private void DataGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { isleftbuttonpressed = true; isSelecting = true; selectionStartPoint = e.GetPosition(FileDataGrid); adorner = new SelectionAdorner(FileDataGrid, selectionStartPoint); var adornerLayer = AdornerLayer.GetAdornerLayer(FileDataGrid); adornerLayer.Add(adorner); adorner.SetStartPoint(selectionStartPoint); } private void DataGrid_PreviewMouseMove(object sender, MouseEventArgs e) { if (e.LeftButton == MouseButtonState.Pressed) { isleftbuttonpressed = true; } if (isSelecting && adorner != null) { adorner.SetEndPoint(e.GetPosition(FileDataGrid)); var selectionRect = adorner.GetSelectionRect(); SelectItemsInRectangle(selectionRect, FileDataGrid, sender, e); } } private void SelectItemsInRectangle(Rect selectionRect, DataGrid dataGrid, object sender, MouseEventArgs e) { foreach (var item in dataGrid.Items) { var row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromItem(item); if (row != null) { var rowBounds = GetRowBoundsRelativeToDataGrid(row, dataGrid); // 检查行是否与选择矩形相交 if (selectionRect.IntersectsWith(rowBounds)) { row.IsSelected = true; } else { row.IsSelected = false; } } } }
问题根源
原来的实现将Adorner添加到了DataGrid控件本身的AdornerLayer,但DataGrid的滚动内容实际承载在内部的ScrollContentPresenter控件中,这个Presenter会随ScrollViewer滚动而移动。因此Adorner固定在DataGrid控件层面,无法跟随内容滚动。
解决方案
1. 修改Adorner的挂载目标
将Adorner添加到DataGrid内部的ScrollContentPresenter的AdornerLayer,而非DataGrid本身。
2. 调整坐标计算逻辑
所有鼠标坐标和行边界的计算都基于ScrollContentPresenter的坐标系,确保滚动时坐标匹配。
修改后的代码
新增视觉元素查找辅助方法
private T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { var child = VisualTreeHelper.GetChild(parent, i); if (child is T tChild) return tChild; var result = FindVisualChild<T>(child); if (result != null) return result; } return null; }
修改鼠标按下事件
private void DataGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { isleftbuttonpressed = true; isSelecting = true; // 获取DataGrid内部的ScrollContentPresenter var scrollContentPresenter = FindVisualChild<ScrollContentPresenter>(FileDataGrid); if (scrollContentPresenter == null) return; // 基于ScrollContentPresenter获取鼠标起始点 selectionStartPoint = e.GetPosition(scrollContentPresenter); // 将Adorner挂载到ScrollContentPresenter上 adorner = new SelectionAdorner(scrollContentPresenter, selectionStartPoint); var adornerLayer = AdornerLayer.GetAdornerLayer(scrollContentPresenter); adornerLayer.Add(adorner); adorner.SetStartPoint(selectionStartPoint); }
修改鼠标移动事件
private void DataGrid_PreviewMouseMove(object sender, MouseEventArgs e) { if (e.LeftButton == MouseButtonState.Pressed) { isleftbuttonpressed = true; } if (isSelecting && adorner != null) { var scrollContentPresenter = FindVisualChild<ScrollContentPresenter>(FileDataGrid); if (scrollContentPresenter == null) return; // 基于ScrollContentPresenter获取鼠标终点 adorner.SetEndPoint(e.GetPosition(scrollContentPresenter)); var selectionRect = adorner.GetSelectionRect(); SelectItemsInRectangle(selectionRect, FileDataGrid, sender, e); } }
修改行选择判断逻辑
private void SelectItemsInRectangle(Rect selectionRect, DataGrid dataGrid, object sender, MouseEventArgs e) { var scrollContentPresenter = FindVisualChild<ScrollContentPresenter>(dataGrid); if (scrollContentPresenter == null) return; foreach (var item in dataGrid.Items) { var row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromItem(item); if (row != null) { // 将行的坐标转换为ScrollContentPresenter的相对坐标 var rowTopLeft = row.TransformToAncestor(scrollContentPresenter).Transform(new Point(0, 0)); var rowBounds = new Rect(rowTopLeft, new Size(row.ActualWidth, row.ActualHeight)); if (selectionRect.IntersectsWith(rowBounds)) { row.IsSelected = true; } else { row.IsSelected = false; } } } }
确保SelectionAdorner实现正确
public class SelectionAdorner : Adorner { private Point startPoint; private Point endPoint; private readonly Pen pen = new Pen(Brushes.Blue, 1) { DashStyle = DashStyles.Dash }; private readonly Brush brush = new SolidColorBrush(Color.FromArgb(50, 0, 0, 255)); public SelectionAdorner(UIElement adornedElement, Point start) : base(adornedElement) { startPoint = start; endPoint = start; IsHitTestVisible = false; // 不干扰DataGrid的鼠标事件 } public void SetStartPoint(Point point) { startPoint = point; InvalidateVisual(); } public void SetEndPoint(Point point) { endPoint = point; InvalidateVisual(); } public Rect GetSelectionRect() { return new Rect( Math.Min(startPoint.X, endPoint.X), Math.Min(startPoint.Y, endPoint.Y), Math.Abs(startPoint.X - endPoint.X), Math.Abs(startPoint.Y - endPoint.Y)); } protected override void OnRender(DrawingContext drawingContext) { base.OnRender(drawingContext); var rect = GetSelectionRect(); drawingContext.DrawRectangle(brush, pen, rect); } }
效果说明
修改后,选择矩形会跟随ScrollContentPresenter一起滚动,坐标计算始终基于滚动内容的容器,确保滚动时矩形与行的相交判断准确,完全复刻Windows文件资源管理器的框选滚动行为。
内容的提问来源于stack exchange,提问作者Amit
相关产品推荐
相关产品推荐

