如何在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
相关产品推荐
相关产品推荐

