Что вызывает WPF ListCollectionView, который использует пользовательскую сортировку для повторной сортировки своих элементов? - PullRequest
10 голосов
/ 02 марта 2009

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

// 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));

Ответы [ 4 ]

8 голосов
/ 05 апреля 2013

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

IEditableCollectionView collectionView = DataGrid.Items;

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

Где changedItem - модель (элемент в коллекции ItemsSource).

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

8 голосов
/ 02 марта 2009

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

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

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

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

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

EDIT Поскольку user346528 указывает, , IEditableObject на самом деле не было новым в 3.5SP1. На самом деле это выглядит так, как будто это было в рамках с 1.0 .

2 голосов
/ 26 мая 2010

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

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

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

Примечание. Проблемы могут возникать при использовании 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

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

Edit:

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

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

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

TamusJRoyce

1 голос
/ 02 марта 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...