WPF 获取DataGrid可见行数据

  我需要考虑如何检测DataGrid中当前可见的行。WPF的DataGrid内部使用ScrollViewer来管理滚动,因此需要监听ScrollViewer的ScrollChanged事件。当用户滚动时,可以通过计算可视区域的位置来确定哪些行被显示。在MVVM模式下,直接访问DataGrid的行可能会破坏模式,因此需要使用附加行为或事件来将信息传递到ViewModel。

  目前决定构建一个附加行为类,监听ScrollViewer的滚动事件,计算可见行,并通过依赖属性或消息传递给ViewModel。

  开始建立DataGridVisibleRowsBehavior类 (注意:要通过NuGet安装Microsoft.Xaml.Behaviors.Wpf实现附加行为)

  

    /// <summary>
    /// 获取DataGrid可见行数据附加属性
    /// </summary>
    public class DataGridVisibleRowsBehavior : Behavior<DataGrid>
    {
        // 绑定到ViewModel的可观察属性 
        public static readonly DependencyProperty VisibleItemsProperty =
            DependencyProperty.Register("VisibleItems", typeof(IList<object>),
                typeof(DataGridVisibleRowsBehavior),
                new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

        public IList<object> VisibleItems
        {
            get => (IList<object>)GetValue(VisibleItemsProperty);
            set => SetValue(VisibleItemsProperty, value);
        }

        private ScrollViewer _scrollViewer;
        private DispatcherTimer _throttleTimer;

        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.Loaded += OnLoaded;
            _throttleTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };//延迟200毫秒刷新
            _throttleTimer.Tick += (s, e) => UpdateVisibleItems();
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            _scrollViewer = FindVisualChild<ScrollViewer>(AssociatedObject);
            if (_scrollViewer != null)
            {
                _scrollViewer.ScrollChanged += OnScrollChanged;
            }
        }

        private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            _throttleTimer.Stop();
            _throttleTimer.Start();
        }

        /// <summary>
        /// 更新行的新数据
        /// </summary>
        private void UpdateVisibleItems()
        {
            var visibleRows = new List<object>();
            var itemsControl = AssociatedObject;

            foreach (var item in itemsControl.Items)
            {
                if (itemsControl.ItemContainerGenerator.ContainerFromItem(item) is DataGridRow row && IsRowVisible(row, AssociatedObject))
                {
                    visibleRows.Add(item);
                }
            }

            VisibleItems = visibleRows;
        }

        // 视觉树遍历方法 
        private static 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 result) return result;
                if (FindVisualChild<T>(child) is T descendant) return descendant;
            }
            return null;
        }

        // 判断行是否可见 
        private bool IsRowVisible(DataGridRow row, DataGrid dataGrid)
        {
            var rowTransform = row.TransformToAncestor(dataGrid);
            var rowRectangle = rowTransform.TransformBounds(new Rect(0, 0, row.ActualWidth, row.ActualHeight));
            var dataGridRectangle = new Rect(0, 0, dataGrid.ActualWidth, dataGrid.ActualHeight);
            return dataGridRectangle.IntersectsWith(rowRectangle);
        }
    }

XAML代码使用

<!-- 引用命名空间 Local 是你附加属性类所在的文件位置-->
xmlns:i="http://schemas.microsoft.com/xaml/behaviors" 
xmlns:local="clr-namespace:YourNamespace.Behaviors"
 
<!-- DataGrid定义 -->
<DataGrid ItemsSource="{{Binding Items}}">
    <i:Interaction.Behaviors>
        <local:DataGridVisibleRowsBehavior 
            VisibleItems="{{Binding VisibleItems, Mode=OneWayToSource}}"/>
    </i:Interaction.Behaviors>
</DataGrid>

ViewModel类实现(当前使用了CommunityToolkit.Mvvm来进行绑定,你也可以用其他)

public class MainViewModel : ObservableObject 
{
    private IList<object> _visibleItems;
    public IList<object> VisibleItems 
    {
        get => _visibleItems;
        set => SetProperty(ref _visibleItems, value);
    }
}
  1. 节流机制:200ms延迟计算避免高频滚动导致的性能问题
  2. 精确可视判断:通过TransformToAncestor计算行在DataGrid中的实际位置
  3. 动态数据支持:兼容数据更新时的自动重新计算

 

  

posted @ 2025-03-08 14:15  FalyEnd  阅读(48)  评论(0)    收藏  举报