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

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

class WizardControl : INotifyPropertyChanged
{
    public int ZIndex { get; set; /* set emits PropertyChanged event */}
}

class WizardStep
{
    ObservableCollection<WizardControl> Controls { get; set; }
}

class Wizard
{
    ObservableCollection<WizardStep> Steps { get; set; }
}

У меня также есть TreeView, использующий HierarchicalDataTemplate, где каждый WizardStep имеет узел дерева со всеми WizardControl в виде листьев дерева.

Теперь я хотел бы отсортировать элементы управления по их ZIndex. Я нашел решение, используя пользовательский Converter (http://stackoverflow.com/a/5730402/69868), который отлично работает, пока ZIndex не меняется.

Когда ZIndex изменяется, отсортированный CollectionView не генерирует событие CollectionChanged, а графический интерфейс не выбирает изменение порядка.

Мой вопрос: как создать отсортированную коллекцию, которая будет генерировать правильные события, когда элементы переупорядочиваются из-за изменения значений сортировки?


person Miroslav Bajtoš    schedule 19.06.2012    source источник


Ответы (3)


Для этого вы должны использовать CollectionView, обернутый вокруг вашего ObservableCollection с CollectionView.SortDescriptions.Add(new SortDescription(ZIndex)).

Таким образом, всякий раз, когда изменяется ZIndex любого элемента внутри наблюдаемой коллекции, он автоматически получает правильную позицию сортировки в графическом интерфейсе.

person WPF-it    schedule 19.06.2012
comment
Я сделал это, и графический интерфейс не уловил изменения ZIndex. - person Miroslav Bajtoš; 19.06.2012
comment
Странно!! ... Это только что сработало для меня ... Вы уверены, что правильно вызываете событие PropertyChanged в установщике? - person WPF-it; 19.06.2012

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

WizardControl.cs

    public class WizardControl : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        int zIndex;
        PropertyChangedEventArgs zIndexArgs = new PropertyChangedEventArgs("ZIndex");

        public int ZIndex
        {
            get { return zIndex; }
            set
            {
                if (zIndex != value)
                {
                    zIndex = value;
                    PropertyChangedEventHandler temp = PropertyChanged;
                    if (temp != null)
                        temp(this, zIndexArgs);
                }
            }
        }

        public override string ToString()
        {
            return zIndex.ToString();      

        }
}

WizardCollection.cs

public class WizardCollection : INotifyCollectionChanged, IEnumerable<WizardControl>
{
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    NotifyCollectionChangedEventArgs collectionChangedMoveArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);

    List<WizardControl> items = new List<WizardControl>();

    public WizardControl this[int index]
    {
        get { return items[index]; }
    }

    public void Add(WizardControl item)
    {
        if (items == null) items = new List<WizardControl>();
        items.Add(item);
        item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
        NotifyCollectionChangedEventHandler temp = CollectionChanged;
        if (temp != null)
            temp(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
    }

    public void Remove(WizardControl item)
    {
        item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
        NotifyCollectionChangedEventHandler temp = CollectionChanged;
        if (temp != null)
            temp(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
    }

    void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "ZIndex")
        {
            items = items.OrderBy(x => x.ZIndex).ToList();
            NotifyCollectionChangedEventHandler temp = CollectionChanged;
            if (temp != null)
                temp(this, collectionChangedMoveArgs);
        }
    }

    public IEnumerator<WizardControl> GetEnumerator()
    {
        return items.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return items.GetEnumerator();
    }
}
person Andy    schedule 19.06.2012

ObservableCollection вызывает уведомление PropertyChange только при изменении коллекции, а не при изменении объектов внутри коллекции.

Если вы хотите такого поведения, вы должны добавить его самостоятельно. Обычно я добавляю его в событие CollectionChanged.

public MyViewModel()
{
    MyCollection.CollectionChanged += MyCollection_CollectionChanged;
}

void MyCollection_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
    if (e.NewItems != null)
        foreach(MyItem item in e.NewItems)
            item.PropertyChanged += MyItem_PropertyChanged;

    if (e.OldItems != null)
        foreach(MyItem item in e.OldItems)
            item.PropertyChanged -= MyItem_PropertyChanged;
}

void MyItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "Some Property")
    {
        // Do work
        RaisePropertyChanged("MyCollection");
    }
}

Поскольку вы используете конвертер в коллекции, просто вызов события PropertyChanged для коллекции должен работать для повторного запуска конвертера.

person Rachel    schedule 19.06.2012