Что заставляет WPF ListCollectionView использовать пользовательскую сортировку для повторной сортировки своих элементов?

StackOverflow https://stackoverflow.com/questions/601864

  •  03-07-2019
  •  | 
  •  

Вопрос

Рассмотрим этот код (имена типов обобщены для примера):

// 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, коллекция отсортирована так, как я ожидал.

Однако мне требуется, чтобы данные повторно сортировались во время выполнения в ответ на изменение свойств на ItemItem класс происходит от INotifyPropertyChanged и я знаю, что это свойство срабатывает правильно, поскольку мой шаблон данных обновляет значения на экране, только логика сортировки не вызывается.

Я также пытался поднять INotifyPropertyChanged.PropertyChanged передача пустой строки, чтобы увидеть, приведет ли общее уведомление к запуску сортировки.Никаких бананов.

РЕДАКТИРОВАТЬ В ответ на предложение Кента я подумал, что стоит отметить, что сортировка элементов с использованием этого метода дает тот же результат, а именно: коллекция сортируется один раз, но не пересортировать по мере изменения данных:

_itemsView.SortDescriptions.Add(
    new SortDescription("PropertyName", ListSortDirection.Ascending));
Это было полезно?

Решение

я нашел эта статья доктораWPF который начинается с ответа на мой вопрос, а затем переходит к обсуждению влияния вызова на производительность Refresh.Некоторые ключевые выдержки:

К сожалению, метод Refresh() приводит к полной регенерации представления.Когда в представлении происходит обновление(), оно вызывает уведомление CollectionChanged и предоставляет действие как «Сброс».ItemContainerGenerator для ListBox получает это уведомление и в ответ удаляет все существующие визуальные элементы для элементов.Затем он полностью восстанавливает новые контейнеры элементов и визуальные эффекты.

Затем он описывает хакерский обходной путь, который повышает производительность.Вместо вызова Refresh удалите, измените и снова добавьте элемент.

Я мог бы подумать, что представление списка может отслеживать элемент, который изменяется, и знать, что нужно переместить только этот элемент в представлении.

Новый подход был представлен в .NET 3.5 SP1 с участием интерфейса IEditableObject который обеспечивает редактирование транзакций через привязку данных к шаблону с помощью методов BeginEdit(), CancelEdit(), и EndEdit().Читать статья Чтобы получить больше информации.

РЕДАКТИРОВАТЬ Как user346528 указывает, IEditableObject на самом деле не было чем-то новым в версии 3.5SP1.На самом деле это выглядит так, как будто это было в рамках с версии 1.0.

Другие советы

Как указывает принятый ответ, я могу принудительно изменить положение одного элемента с помощью кода.

IEditableCollectionView collectionView = DataGrid.Items;

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

Где changedItem это просмотреть модель (товар в ItemsSource коллекция).

Таким образом, вам не нужны ваши элементы для реализации каких-либо интерфейсов, таких как IEditableObject (который, на мой взгляд, в некоторых случаях имеет очень сложный и трудный для реализации контракт).

Поднимите старое сообщение, но просто создайте новый класс коллекции, который наследуется от ListViewCollection и переопределяет OnPropertyChanged (для IBindingList события ListChanged будут содержать изменение свойства в параметре ListChangedEventArgs).И убедитесь, что элементы в коллекции реализуют и используют INotifyPropertyChange всякий раз, когда изменяется свойство (вызванное вами), иначе коллекция не будет привязываться к изменениям свойств.

Тогда в этом методе OnPropertyChanged отправителем будет элемент.Удалите элемент, если — и только если — свойство, которое может привести к изменению курорта, затем добавьте его заново (вставьте его в отсортированном положении, если при добавлении этого еще не произошло).Перемещение элемента предпочтительнее, если он доступен, а не удаление/добавление.Аналогично это следует сделать и с фильтрацией (проверкой предиката фильтра).

IEditableObject не нужен!Если вы хотите отредактировать несколько свойств, а затем завершить редактирование (например, отредактировать 3 свойства и затем выбрать другую строку в WinForm DataGridView), а затем отсортировать их, это будет правильный метод заставить это работать.Но во многих случаях вы, вероятно, захотите, чтобы коллекция возвращалась после изменения каждого свойства без необходимости вручную вызывать BeginEdit/EndEdit.Кстати, IEditableObject присутствует в среде .NET 2.0 и не является новым для .NET 3.5 (если вы читаете статью доктора).

Примечание:Проблемы могут возникнуть при использовании BeginEdit() и EndEdit() при нескольких изменениях одного и того же элемента, если только вы не увеличиваете (для true)/уменьшаете (для false) целое число вместо установки логического значения!Не забудьте увеличить/уменьшить целое число, чтобы точно знать, когда редактирование завершено.

Хранение постоянно отсортированного списка отнимает много времени и подвержено ошибкам (и это может привести к нарушению контракта на вставку, если вы принудительно отсортировали вставки), и его следует использовать только в определенных местах, таких как ComboBox.В любой сетке это очень плохая идея, поскольку изменение строки приведет к ее выходу из-под текущей позиции пользователя.

Public Class ListView
  Inherits ListCollectionView

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

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

Лучшим примером коллекции, которая делает то, что вам нужно, является ObjectListView Джесси Джонсона, хотя он специфичен для .NET 2.0 (IBindingList вместо INotifyCollectionChanged/ObservableCollection/ListCollectionView) и использует очень ограничительную лицензию.Его блог все еще может быть очень ценным, поскольку он добился этого.

Редактировать:

Забыл добавить, что Sender будет элементом, который вам нужно использовать, а e.PropertyName — это то, что вам нужно будет использовать, чтобы определить, находится ли он в SortDescriptions.В противном случае изменение этого объекта недвижимости не приведет к необходимости создания курорта.Если e.PropertyName имеет значение Nothing, то это похоже на обновление, при котором многие свойства могли быть изменены и необходимо выполнить пересортировку.

Чтобы определить, нужно ли его фильтровать, просто запустите его через FilterPredicate и при необходимости удалите.Фильтрация обходится намного дешевле, чем сортировка.

Надеюсь, полезно,

Тамус ДжейРойс

Учитывая, что вы используете пользовательскую сортировку, невозможно ListCollectionView чтобы узнать, какие критерии должны вызывать обновление.Поэтому вам придется позвонить Refresh() в коллекции посмотрите сами.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top