You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ICollectionView刷新后让WPF DataGrid滚动到选中项?

问题描述

我的DataGrid的ItemsSource绑定到ViewModel的ObservableCollection,通过附加行为实现选中项自动滚动到可视区域,但遇到以下问题:

  • 按某列排序后,修改选中项的该列绑定属性,原ObservableCollection无法更新项的位置;改用ICollectionView后能更新位置,但如果项移出可视区域,DataGrid不会自动滚动到选中项。
  • 推测原因是附加行为仅在SelectionChanged事件触发时生效,而ICollectionView刷新后选中项位置变化但未触发该事件。

解决方案

方案1:扩展附加行为,监听ICollectionView变化

修改ScrollIntoViewBehavior,让它同时监听SelectionChanged和ICollectionView的CollectionChanged事件,当集合视图因排序、刷新等发生变化时,自动滚动到当前选中项。

修改后的行为代码:

using Microsoft.Xaml.Behaviors;
using System.Windows.Controls;
using System.Windows.Data;

public class ScrollIntoViewBehavior : Behavior<DataGrid>
{
    private object _currentSelectedItem;
    private ICollectionView _collectionView;

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
        AssociatedObject.ItemsSourceChanged += AssociatedObject_ItemsSourceChanged;
        UpdateCollectionView();
    }

    private void AssociatedObject_ItemsSourceChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        UpdateCollectionView();
    }

    private void UpdateCollectionView()
    {
        if (_collectionView != null)
        {
            _collectionView.CollectionChanged -= CollectionView_CollectionChanged;
        }
        _collectionView = CollectionViewSource.GetDefaultView(AssociatedObject.ItemsSource);
        if (_collectionView != null)
        {
            _collectionView.CollectionChanged += CollectionView_CollectionChanged;
        }
    }

    private void CollectionView_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (_currentSelectedItem != null && AssociatedObject.SelectedItem == _currentSelectedItem)
        {
            ScrollToSelectedItem();
        }
    }

    private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        _currentSelectedItem = AssociatedObject.SelectedItem;
        ScrollToSelectedItem();
    }

    private void ScrollToSelectedItem()
    {
        if (AssociatedObject.SelectedItem != null)
        {
            AssociatedObject.UpdateLayout();
            AssociatedObject.ScrollIntoView(AssociatedObject.SelectedItem, null);
        }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged;
        AssociatedObject.ItemsSourceChanged -= AssociatedObject_ItemsSourceChanged;
        if (_collectionView != null)
        {
            _collectionView.CollectionChanged -= CollectionView_CollectionChanged;
        }
    }
}

方案2:ViewModel刷新后手动通知滚动

如果不想修改行为,可以在ViewModel的更新方法中通过消息通知视图触发滚动(以MVVM Light的Messenger为例,其他框架逻辑类似):

ViewModel修改:

void UpdateItem(MyClass paramSelectedItem)
{
    ItemsSelectedItem = paramSelectedItem;
    MyCollectionViewInViewModel.Refresh();
    // 发送滚动通知
    Messenger.Default.Send(new ScrollToSelectedItemMessage(ItemsSelectedItem));
}

// 定义消息类
public class ScrollToSelectedItemMessage
{
    public object Item { get; }
    public ScrollToSelectedItemMessage(object item) => Item = item;
}

视图中注册消息:

在DataGrid所在视图的构造函数或Loaded事件中添加:

public MyView()
{
    InitializeComponent();
    Messenger.Default.Register<ScrollToSelectedItemMessage>(this, msg =>
    {
        if (MyDataGrid.SelectedItem == msg.Item)
        {
            MyDataGrid.UpdateLayout();
            MyDataGrid.ScrollIntoView(msg.Item);
        }
    });
}

方案3:通过附加属性绑定选中项

修改行为添加附加属性,绑定ViewModel的ItemsSelectedItem,同时监听集合视图变化,双向触发滚动:

修改后的行为代码:

using Microsoft.Xaml.Behaviors;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

public class ScrollIntoViewBehavior : Behavior<DataGrid>
{
    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(ScrollIntoViewBehavior),
            new PropertyMetadata(null, OnSelectedItemChanged));

    public object SelectedItem
    {
        get => GetValue(SelectedItemProperty);
        set => SetValue(SelectedItemProperty, value);
    }

    private ICollectionView _collectionView;

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.ItemsSourceChanged += AssociatedObject_ItemsSourceChanged;
        UpdateCollectionView();
    }

    private void AssociatedObject_ItemsSourceChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        UpdateCollectionView();
    }

    private void UpdateCollectionView()
    {
        if (_collectionView != null)
        {
            _collectionView.CollectionChanged -= CollectionView_CollectionChanged;
        }
        _collectionView = CollectionViewSource.GetDefaultView(AssociatedObject.ItemsSource);
        if (_collectionView != null)
        {
            _collectionView.CollectionChanged += CollectionView_CollectionChanged;
        }
    }

    private void CollectionView_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        ScrollToItem(SelectedItem);
    }

    private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is ScrollIntoViewBehavior behavior)
        {
            behavior.ScrollToItem(e.NewValue);
        }
    }

    private void ScrollToItem(object item)
    {
        if (item != null && AssociatedObject.Items.Contains(item))
        {
            AssociatedObject.UpdateLayout();
            AssociatedObject.ScrollIntoView(item, null);
        }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.ItemsSourceChanged -= AssociatedObject_ItemsSourceChanged;
        if (_collectionView != null)
        {
            _collectionView.CollectionChanged -= CollectionView_CollectionChanged;
        }
    }
}

XAML使用:

<DataGrid ItemsSource="{Binding IFacturas, IsAsync=true}"
          SelectedItem="{Binding ItemsSelectedItem}">
    <i:Interaction.Behaviors>
        <behaviors:ScrollIntoViewBehavior SelectedItem="{Binding ItemsSelectedItem}"/>
    </i:Interaction.Behaviors>
</DataGrid>

内容的提问来源于stack exchange,提问作者Álvaro García

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 10:35:41