如何获取WPF DataGrid中可见行的索引?用于十六进制编辑器分块加载
WPF DataGrid 获取可见行索引实现十六进制编辑器按需加载
一、获取可见行的起始/结束索引
要实现按需加载,首先得准确获取当前DataGrid中可见行的索引范围。可以通过遍历可视化树找到DataGrid的ScrollViewer,再结合行的位置判断是否在可见区域内:
public (int StartIndex, int EndIndex) GetVisibleRowRange(DataGrid dataGrid) { var scrollViewer = FindVisualChild<ScrollViewer>(dataGrid); if (scrollViewer == null) return (-1, -1); double viewportTop = scrollViewer.VerticalOffset; double viewportBottom = viewportTop + scrollViewer.ViewportHeight; int startIdx = int.MaxValue; int endIdx = int.MinValue; for (int i = 0; i < dataGrid.Items.Count; i++) { var row = dataGrid.ItemContainerGenerator.ContainerFromIndex(i) as DataGridRow; if (row == null) continue; // 计算行在ScrollViewer坐标系中的位置 Point rowTopPoint = row.TransformToAncestor(scrollViewer).Transform(new Point(0, 0)); double rowTop = rowTopPoint.Y; double rowBottom = rowTop + row.ActualHeight; // 判断行是否与可见视口有交集 if (rowBottom > viewportTop && rowTop < viewportBottom) { if (i < startIdx) startIdx = i; if (i > endIdx) endIdx = i; } } return (startIdx == int.MaxValue ? -1 : startIdx, endIdx == int.MinValue ? -1 : endIdx); } private T FindVisualChild<T>(DependencyObject parent) where T : Visual { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { Visual child = (Visual)VisualTreeHelper.GetChild(parent, i); if (child is T target) return target; T nestedChild = FindVisualChild<T>(child); if (nestedChild != null) return nestedChild; } return null; }
二、绑定自定义滚动条与加载逻辑
- 初始化滚动条参数:根据文件总大小计算总行数,设置滚动条的最大值和步长:
// 假设每行显示16字节 int bytesPerRow = 16; long totalFileSize = new FileInfo(filePath).Length; int totalRows = (int)Math.Ceiling((double)totalFileSize / bytesPerRow); customVerticalScrollBar.Maximum = totalRows - visibleRowCount; // 避免滚动到超出文件范围 customVerticalScrollBar.LargeChange = visibleRowCount; // 一页行数 customVerticalScrollBar.SmallChange = 1; // 单行滚动
- 处理滚动事件:根据滚动位置计算需要加载的文件块,建议预留缓冲区域(比如前后各多加载半页),避免滚动时频繁加载:
private void CustomVerticalScrollBar_Scroll(object sender, ScrollEventArgs e) { var scrollBar = sender as ScrollBar; if (scrollBar == null) return; int targetStartRow = (int)scrollBar.Value; // 计算加载范围:前后各加半页作为缓冲 int buffer = visibleRowCount / 2; int loadStart = Math.Max(0, targetStartRow - buffer); int loadEnd = Math.Min(totalRows - 1, targetStartRow + visibleRowCount + buffer); // 调用文件读取方法加载对应块 LoadHexDataBlock(loadStart, loadEnd, bytesPerRow); // 让DataGrid滚动到目标起始行 dataGrid.ScrollIntoView(dataGrid.Items[targetStartRow]); }
- 同步DataGrid与滚动条位置:当窗口大小变化或DataGrid布局更新时,同步滚动条位置避免错位:
private void DataGrid_LayoutUpdated(object sender, EventArgs e) { var (startIdx, _) = GetVisibleRowRange(dataGrid); if (startIdx != -1 && !customVerticalScrollBar.IsMouseCaptured) { customVerticalScrollBar.Value = startIdx; } }
三、优化DataGrid虚拟化配置
确保DataGrid启用虚拟化,提升渲染效率:
<DataGrid x:Name="hexDataGrid" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" ScrollViewer.VerticalScrollBarVisibility="Hidden" CanUserScrollRows="False"> <!-- 列定义 --> </DataGrid>
内容的提问来源于stack exchange,提问作者Tsaras
相关产品推荐
相关产品推荐

