What causes a WPF ListCollectionView that uses custom sorting to re-sort its items?
I found this article by Dr. WPF which starts out with an answer to my question, then moves on to discuss the performance impact of calling Refresh
. Some key excerpts:
Unfortunately, the Refresh() method results in a complete regeneration of the view. When a Refresh() occurs within the view, it raises a CollectionChanged notification and supplies the Action as "Reset". The ItemContainerGenerator for the ListBox receives this notification and responds by discarding all the existing visuals for the items. It then completely regenerates new item containers and visuals.
He then describes a hacky workaround that improves performance. Rather than calling Refresh, remove, change then re-add the item.
I would have thought it possible that the list view could track an item that changes and know to re-position that item alone within the view.
A new approach was introduced in .NET 3.5 SP1 involving the interface IEditableObject
that provides transactional editing via data bindings to the template with methods BeginEdit()
, CancelEdit()
, and EndEdit()
. Read the article for more information.
EDIT As user346528 points out, IEditableObject
was not in fact new in 3.5SP1. It actually looks like it's been in the framework since 1.0.
As the accepted answer directs me to, I am able to force single item reposition with code
IEditableCollectionView collectionView = DataGrid.Items;
collectionView.EditItem(changedItem);
collectionView.CommitEdit();
Where changedItem
is the view model (the item in the ItemsSource
collection).
This way you don't need your items to implement any interfaces like IEditableObject
(which in my opinion has very complex and hard to implement contract in some cases).
Bumping an old post, but just make a new collection class which inherits from ListViewCollection and Overrides OnPropertyChanged (for an IBindingList, ListChanged events will contain the property change in the ListChangedEventArgs parameter). And make sure the items within the collection implements and uses INotifyPropertyChange whenever a property changes (raised by you), or the collection won't bind to property changes.
Then in this OnPropertyChanged method, sender will be the item. Remove the item if--and only if--a property which would cause a resort is changed, then re-add it (insert it in sorted position if adding it doesn't do this already). Moving an item is preferable if it is available instead of removing/adding it. Similarly, this should also be done with filtering (checking the filter predicate).
IEditableObject is not needed! If you desire to edit several properties, then finish editing (like editing 3 properties and then selecting on a different row in WinForm DataGridView), then having it sort, this would be the proper method of getting this to work. But a lot of times you will probably want the collection to resort after changing each property without having to manually call BeginEdit/EndEdit. IEditableObject, btw, is present in the .NET 2.0 framework and is not new to .NET 3.5 (if you read the Dr's Article).
Note: Issues can occur using BeginEdit() and EndEdit() with multiple edits to the same item--unless you increment (for true)/decrement (for false) an integer instead of setting a Boolean! Remember to increment/decrement an integer to truly know when editing is finished.
Keeping a perpetually sorted list is time consuming and error prone (and it can break the insert contract if you force sorted inserts), and should only be used in certain places such as ComboBoxes. On any grid, this is a very bad idea, as changing a row will cause it to resort out from under the users current position.
Public Class ListView
Inherits ListCollectionView
Protected Overrides Sub OnPropertyChanged(sender As Object, e As PropertyChangedEventArgs)
' Add resorting/filtering logic here.
End Sub
End Class
The best example on a collection that does similar to what you need is Jesse Johnsons ObjectListView, although this is .NET 2.0 specific (IBindingList instead of INotifyCollectionChanged/ObservableCollection/ListCollectionView) and uses a very restrictive license. His blog may still be very valuable in how he accomplished this.
Edit:
Forgot to add that Sender will be the the item you need to resort, and e.PropertyName is what you will need to use to determine if it is within the SortDescriptions. If it isn't, a change to that property won't result in a resort being needed. If e.PropertyName is Nothing, then it is just like a refresh, where many properties may have changed and resorting should be done.
To determine if it needs filtered, just run it through the FilterPredicate, and remove it if needed. Filtering is a lot less expensive than sorting.
Hopefully Helpful,
TamusJRoyce