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

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


person Eternal21    schedule 17.01.2014    source источник


Ответы (3)


Итак, я провел немного больше исследований, и оказалось, что моя проблема связана с ограничением сети данных WPF. Он не будет автоматически пересортировывать коллекцию при изменении базовых данных. Другими словами, когда вы впервые добавляете свой элемент, он будет отсортирован и помещен в правильное место, но если вы измените свойство элемента, он не будет повторно отсортирован. INotifyPropertyChanged не имеет отношения к сортировке обновлений. Он занимается только обновлением отображаемых данных, но не запускает их сортировку. Это событие CollectionChanged, которое вызывает повторную сортировку, но изменение элемента, который уже находится в коллекции, не вызовет это конкретное событие, и, следовательно, сортировка не будет выполняться.

Вот еще одна похожая проблема: C # WPF Datagrid не динамически сортировать по обновлению данных

Решение этого пользователя заключалось в том, чтобы вручную вызвать OnCollectionChanged ().

В конце концов, я объединил ответы из этих двух тем:

  1. ObservableCollection не замечает, когда элемент в он меняется (даже с INotifyPropertyChanged)
  2. 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);          
            }
        }
person Eternal21    schedule 20.01.2014

Весь мой ответ ниже - тарабарщина. Как указано в комментариях, если вы привязываетесь к самой коллекции, вы неявно привязываетесь к умолчанию просмотр коллекции. (Однако, как указано в примечании к ссылке, Silverlight является исключением - там неявно создается представление коллекции по умолчанию, если коллекция не реализует ICollectionViewFactory.)

CollectionViewSource не изменяет базовую коллекцию. Чтобы получить сортировку, вам необходимо выполнить привязку к сам просмотр, например:

<DataGrid ItemsSource="{Binding Path=CollectionViewSource.View}">

Обратите внимание, что, хотя исходная коллекция (сообщения) остается нетронутой, ваше отсортированное представление будет обновлено через событие уведомления:

Если исходная коллекция реализует интерфейс INotifyCollectionChanged, изменения, вызванные событием CollectionChanged, распространяются на представления.

person McGarnagle    schedule 17.01.2014
comment
Я почти уверен, что привязка к ObservableCollection аналогична привязке к представлению по умолчанию, потому что это то, что WPF делает за кулисами. - person Eternal21; 18.01.2014
comment
@ Eternal21, как такое могло быть? Затем представление должно было бы изменить базовую исходную коллекцию, что противоречит свойству представлений только для чтения ... - person McGarnagle; 18.01.2014
comment
stackoverflow.com/questions/6317860 / - person Eternal21; 19.01.2014
comment
@ Eternal21, хотя я заметил, что этот факт не относится к Silverlight, что, на мой взгляд, извиняет меня за мое невежество! : D - person McGarnagle; 19.01.2014
comment
Ваша последняя строка, которую вы вычеркнули, на самом деле верна и связана с моей проблемой. Оказывается, когда вы изменяете элемент внутри коллекции, он не запускает событие CollectionChanged. Согласно документации MSDN, это событие происходит только при добавлении, удалении, изменении, перемещении элемента или обновлении всего списка. Проблема в том, что сортировка обновляется только при срабатывании этого события. - person Eternal21; 20.01.2014

Я обнаружил проблему только после того, как попытался отсортировать другое свойство и заметил, что он работает. Оказывается, когда мои сообщения добавлялись в коллекцию, свойство TimeSent инициализировалось значением MinDate и только затем обновлялось до фактической даты. Так что он был правильно помещен в конец списка. Проблема в том, что позиция не обновлялась при изменении свойства TimeSent. Похоже, у меня проблема с распространением событий INotifyPropertyChanged (TimeSent находится в другом объекте внутри объекта Message).

person Eternal21    schedule 17.01.2014