ObservableCollection не сортирует вновь добавленные элементы
У меня есть следующая ObservableCollection, которая связана с DataGrid:
public ObservableCollection<Message> Messages = new ObservableCollection<Message>;
XAML:
<DataGrid ItemsSource="{Binding Path=Messages}">
Я сортирую его при запуске, используя вид по умолчанию:
ICollectionView view = CollectionViewSource.GetDefaultView(Messages);
view.SortDescriptions.Add(new SortDescription("TimeSent", ListSortDirection.Descending));
Все работает нормально, но проблема в том, что всякий раз, когда я добавляю новое сообщение в коллекцию сообщений, оно просто добавляется в конец списка и не сортируется автоматически.
Messages.Add(message);
Я делаю что-то неправильно? Я уверен, что смогу обойти эту проблему, обновляя представление каждый раз, когда добавляю элемент, но это просто неправильный способ сделать это (не говоря уже о производительности).
3 ответа
Так что я провел немного больше исследований, и оказалось, что моя проблема связана с ограничением сетки данных WPF. Он не будет автоматически пересортировать коллекцию при изменении базовых данных. Другими словами, когда вы впервые добавите свой элемент, он будет отсортирован и помещен в правильное место, но если вы измените свойство элемента, он не будет пересортирован. INotifyPropertyChanged не имеет отношения к сортировке обновлений. Он имеет дело только с обновлением отображаемых данных, но не вызывает их сортировку. Это событие CollectionChanged, которое вызывает повторную сортировку, но изменение элемента, который уже находится в коллекции, не вызовет это конкретное событие, и, следовательно, сортировка не будет выполнена.
Вот еще одна похожая проблема: C# WPF Datagrid не выполняет динамическую сортировку при обновлении данных
Решением этого пользователя было вручную вызвать OnCollectionChanged().
В итоге я объединил ответы из этих двух тем:
- ObservableCollection не замечает, когда элемент в нем изменяется (даже с INotifyPropertyChanged)
- ObservableCollection и Item PropertyChanged
Я также добавил "умную" сортировку, которая вызывает только OnCollectionChanged (), если свойство изменилось, это значение, которое в настоящее время используется в SortDescription.
public class MessageCollection : ObservableCollection<Message>
{
ICollectionView _view;
public MessageCollection()
{
_view = CollectionViewSource.GetDefaultView(this);
}
public void Sort(string propertyName, ListSortDirection sortDirection)
{
_view.SortDescriptions.Clear();
_view.SortDescriptions.Add(new SortDescription(propertyName, sortDirection));
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
this.AddPropertyChanged(e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
this.RemovePropertyChanged(e.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Reset:
this.RemovePropertyChanged(e.OldItems);
this.AddPropertyChanged(e.NewItems);
break;
}
base.OnCollectionChanged(e);
}
private void AddPropertyChanged(IEnumerable items)
{
if (items != null)
{
foreach (var obj in items.OfType<INotifyPropertyChanged>())
{
obj.PropertyChanged += OnItemPropertyChanged;
}
}
}
private void RemovePropertyChanged(IEnumerable items)
{
if (items != null)
{
foreach (var obj in items.OfType<INotifyPropertyChanged>())
{
obj.PropertyChanged -= OnItemPropertyChanged;
}
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
bool sortedPropertyChanged = false;
foreach (SortDescription sortDescription in _view.SortDescriptions)
{
if (sortDescription.PropertyName == e.PropertyName)
sortedPropertyChanged = true;
}
if (sortedPropertyChanged)
{
NotifyCollectionChangedEventArgs arg = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Replace, sender, sender, this.Items.IndexOf((Message)sender));
OnCollectionChanged(arg);
}
}
Я только что нашел проблему, после попытки отсортировать другое свойство и заметив, что оно работает. Оказывается, когда мои сообщения добавлялись в коллекцию, свойство TimeSent инициализировалось в MinDate и только затем обновлялось до фактической даты. Таким образом, это было должным образом размещено внизу списка. Проблема в том, что позиция не обновлялась при изменении свойства TimeSent. Похоже, у меня проблема с распространением событий INotifyPropertyChanged (TimeSent находится в другом объекте внутри объекта Message).
Весь мой ответ ниже - бред. Как указано в комментариях, если вы привязываетесь к самой коллекции, то вы неявно привязываетесь к представлению коллекции по умолчанию. (Однако, как комментарий к примечаниям к ссылке, Silverlight является исключением - там не создается неявное представление коллекции по умолчанию, если коллекция не реализует ICollectionViewFactory
.)
CollectionViewSource
не изменяет основную коллекцию.Чтобы выполнить сортировку, вам нужно привязать само представление, например:
<DataGrid ItemsSource="{Binding Path=CollectionViewSource.View}">
Обратите внимание, что, хотя исходная коллекция (Сообщения) не затронута, ваш отсортированный вид будет обновляться через событие уведомления:
Если исходная коллекция реализует интерфейс INotifyCollectionChanged, изменения, вызванные событием CollectionChanged, распространяются на представления.