导致WPF ListCollectionView使用自定义排序重新排序其项目的原因是什么?

时间:2009-03-02 10:17:54

标签: .net wpf sorting

考虑这个代码(为了举例的目的,通用化的类型名称):

// Bound to ListBox.ItemsSource
_items = new ObservableCollection<Item>();

// ...Items are added here ...

// Specify custom IComparer for this collection view
_itemsView = CollectionViewSource.GetDefaultView(_items)
((ListCollectionView)_itemsView).CustomSort = new ItemComparer();

当我设置CustomSort时,集合按照我的预期排序。

但是我要求数据在运行时重新排序,以响应Item上属性的更改。 Item类派生自INotifyPropertyChanged,我知道当我的数据模板更新屏幕上的值时,属性会正确触发,只会调用排序逻辑。

我还尝试提升INotifyPropertyChanged.PropertyChanged传递空字符串,以查看通用通知是否会导致启动排序。没有香蕉。

编辑为了回应肯特的建议,我想我会指出使用此方法对项目进行排序具有相同的结果,即集合排序一次,但随着数据的变化重新排序:

_itemsView.SortDescriptions.Add(
    new SortDescription("PropertyName", ListSortDirection.Ascending));

5 个答案:

答案 0 :(得分:8)

我发现this article by Dr. WPF开始时回答了我的问题,然后继续讨论调用Refresh对性能的影响。一些关键摘录:

  

不幸的是,Refresh()方法导致视图完全重新生成。当视图中出现Refresh()时,它会引发CollectionChanged通知并将Action提供为“Reset”。 ListBox的ItemContainerGenerator接收此通知,并通过丢弃项目的所有现有视觉效果进行响应。然后它完全重新生成新的项目容器和视觉效果。

然后,他描述了一种改善性能的hacky解决方法。而不是调用刷新,删除,更改然后重新添加项目。

我原本以为列表视图可以跟踪一个更改的项目,并知道在视图中单独重新定位该项目。

.NET 3.5 SP1中引入了一种新方法,涉及接口IEditableObject,通过方法BeginEdit()CancelEdit()和{{1}通过数据绑定提供事务编辑}}。请阅读the article以获取更多信息。

编辑作为user346528 points outEndEdit()在3.5SP1中实际上并不是新的。它实际上看起来像是在since 1.0框架中。

答案 1 :(得分:8)

由于接受的答案指示我,我能够强制单项重新定位代码

IEditableCollectionView collectionView = DataGrid.Items;

collectionView.EditItem(changedItem);
collectionView.CommitEdit();

changedItem视图模型ItemsSource集合中的项目)。

通过这种方式,您不需要您的项目来实现IEditableObject之类的任何接口(在我看来,在某些情况下,这些接口非常复杂且难以实施)。

答案 2 :(得分:2)

压缩旧帖子,但只创建一个继承自ListViewCollection和Overrides OnPropertyChanged的新集合类(对于IBindingList,ListChanged事件将包含ListChangedEventArgs参数中的属性更改)。并确保集合中的项实现并在属性更改(由您引发)时使用INotifyPropertyChange,否则集合将不会绑定到属性更改。

然后在此OnPropertyChanged方法中,发件人将成为项目。如果 - 并且仅当 - 导致度假胜地的属性被更改,则删除该项目,然后重新添加(如果添加它,则将其插入已排序的位置)。如果项目可用而不是删除/添加项目,则最好移动项目。同样,这也应该通过过滤(检查过滤器谓词)来完成。

不需要IEditableObject!如果你想编辑几个属性,然后完成编辑(比如编辑3个属性,然后在WinForm DataGridView中的另一行上选择),然后对它进行排序,这将是使其工作的正确方法。但很多时候,您可能希望在更改每个属性后使用该集合,而无需手动调用BeginEdit / EndEdit。 IEditableObject,btw,存在于.NET 2.0框架中,并不是.NET 3.5的新手(如果您阅读Dr的文章)。

注意:使用BeginEdit()和EndEdit()以及对同一项目进行多次编辑时可能会出现问题 - 除非您增加(对于true)/减量(对于false)整数而不是设置布尔值!记得增加/减少一个整数,以便真正知道编辑完成的时间。

保持永久排序的列表非常耗时且容易出错(如果强制排序插入,它可能会破坏插入协定),并且只能在某些位置使用,例如ComboBoxes。在任何网格上,这是一个非常糟糕的主意,因为更改一行将导致它从用户当前位置下撤出。

Public Class ListView
  Inherits ListCollectionView

  Protected Overrides Sub OnPropertyChanged(sender As Object, e As PropertyChangedEventArgs)

    ' Add resorting/filtering logic here.
  End Sub
End Class

与Jesse Johnsons ObjectListView类似的集合上的最佳示例是Jesse Johnsons ObjectListView,尽管这是特定于.NET 2.0的(IBindingList而不是INotifyCollectionChanged / ObservableCollection / ListCollectionView)并且使用非常严格的许可证。他的博客在完成这项工作方面可能仍然非常有价值。

编辑:

忘记添加Sender将是您需要使用的项目,并且您将需要使用e.PropertyName来确定它是否在SortDescriptions中。如果不是,则对该属性的更改将不会导致需要度假村。如果e.PropertyName为Nothing,则它就像刷新一样,其中许多属性可能已经改变并且应该进行求助。

要确定是否需要过滤,只需通过FilterPredicate运行它,并在需要时将其删除。过滤比分拣要便宜得多。

希望有帮助,

TamusJRoyce

答案 3 :(得分:1)

鉴于您正在使用自定义排序,ListCollectionView无法知道触发刷新的标准。因此,您需要自己在集合视图上调用Refresh()

答案 4 :(得分:1)

在.NET 4.5中,WPF添加了实时整形: Powershell Tutorial

这允许您设置属性以进行排序/分组/过滤,如果属性值发生更改,则将再次对项目进行排序/分组/过滤。就您而言:

// Bound to ListBox.ItemsSource
_items = new ObservableCollection<Item>();

// ...Items are added here ...

// Add sorting
_itemsView = CollectionViewSource.GetDefaultView(_items)
_itemsView.SortDescriptions.Add(new SortDescription("PropertyName", ListSortDirection.Ascending));

// Enable live sorting
// Note that if you don't add any LiveSortingProperties, it will use SortDescriptions
((ICollectionViewLiveShaping)_itemsView).LiveSortingProperties.Add("PropertyName");
((ICollectionViewLiveShaping)_itemsView).IsLiveSorting = true;