实现CollectionChanged

33

我已经向一个ObservableCollection属性添加了CollectionChanged事件处理程序(onCollectionChanged)

我发现只有在向集合中添加或移除项目时,才会调用onCollectionChanged方法,但如果编辑集合项,则不会调用。

我想知道如何将新添加、删除和编辑的项目列表/集合发送到单个集合中。

谢谢。

8个回答

57

你需要为每个对象添加一个 PropertyChanged 监听器(这些对象必须实现INotifyPropertyChanged接口),以便在可观察列表中编辑对象时获得通知。

public ObservableCollection<Item> Names { get; set; }
public List<Item> ModifiedItems { get; set; }

public ViewModel()
{
   this.ModifiedItems = new List<Item>();

   this.Names = new ObservableCollection<Item>();
   this.Names.CollectionChanged += this.OnCollectionChanged;
}

void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.NewItems != null)
    {
        foreach(Item newItem in e.NewItems)
        {
            ModifiedItems.Add(newItem);

            //Add listener for each item on PropertyChanged event
            newItem.PropertyChanged += this.OnItemPropertyChanged;         
        }
    }

    if (e.OldItems != null)
    {
        foreach(Item oldItem in e.OldItems)
        {
            ModifiedItems.Add(oldItem);

            oldItem.PropertyChanged -= this.OnItemPropertyChanged;
        }
    }
}

void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    Item item = sender as Item;
    if(item != null)
       ModifiedItems.Add(item);
}
< p >可能需要检查某些项是否已经在ModifiedItems列表中(使用列表的Contains(object obj)方法),并且只有在该方法的结果为false时才添加一个新的项。

类必须实现INotifyPropertyChanged。请参阅此示例以了解如何实现。正如Robert Rossney所说,如果您有该要求,还可以使用IEditableObject来实现。


3
除非你修改代码检查 e.NewItems 和 e.OldItems 是否为 null,否则在添加和移除操作时都会抛出异常。 - Alain
2
当你已经有了Names集合,为什么还需要这个ModifiedItems列表呢? - v.g.

11
一个ItemsControl监听CollectionChanged来管理它在屏幕上呈现的项目集合的显示。一个ContentControl监听PropertyChanged来管理它在屏幕上呈现的特定项的显示。一旦你理解了这一点,很容易将两个概念分开记忆。
跟踪项目是否已编辑不是这些接口执行的操作。属性更改并不表示编辑,也就是说,它们不一定代表对象状态的某种由用户引发的更改。例如,一个对象可能具有由计时器不断更新的ElapsedTime属性;UI需要被通知这些属性更改事件,但它们肯定不代表对象底层数据的更改。
跟踪对象是否已编辑的标准方法是首先使该对象实现IEditableObject。然后,在对象类内部,可以决定哪些更改构成编辑(即需要调用BeginEdit),哪些更改不构成编辑。然后可以实现布尔值的IsDirty属性,当调用BeginEdit时设置该属性,并在调用EndEditCancelEdit时清除该属性。(我真的不明白为什么这个属性不是IEditableObject的一部分;我还没有实现不需要它的可编辑对象。)
当然,如果不需要第二级抽象,就没有必要实现 - 可以监听PropertyChanged事件,并假定如果引发了该事件,则对象已经被编辑了。这确实取决于你的需求。

7
我的对 '这个回答' 的编辑被拒绝了!因此,我将我的编辑放在这里:
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
    foreach(Item newItem in e.NewItems)
    {
        ModifiedItems.Add(newItem);

        //Add listener for each item on PropertyChanged event
        if (e.Action == NotifyCollectionChangedAction.Add)
            newItem.PropertyChanged += this.ListTagInfo_PropertyChanged;
        else if (e.Action == NotifyCollectionChangedAction.Remove)
            newItem.PropertyChanged -= this.ListTagInfo_PropertyChanged;
    }
}

// MSDN: OldItems:Gets the list of items affected by a Replace, Remove, or Move action.  
//if (e.OldItems != null) <--- removed
}

2

INotifyCollectionChangedINotiftyPropertyChanged不是同一概念。改变基础对象的属性并不意味着集合已经发生了改变。

实现这种行为的一种方法是创建一个自定义集合,在添加对象时对其进行询问,并注册INotifyPropertyChanged.PropertyChanged事件;在删除项目时,还需要适当地取消注册。

使用这种方法的一个注意事项是,当您的对象嵌套N层深时,需要使用反射来查询每个属性,以确定它是否可能是另一个实现INotifyCollectionChanged或其他需要遍历的容器的集合。

以下是一个未经测试的基本示例...

    public class ObservableCollectionExt<T> : ObservableCollection<T>
    {
        public override event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged;

        protected override void SetItem(int index, T item)
        {
            base.SetItem(index, item);

            if(item is INotifyPropertyChanged)
                (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
        }

        protected override void ClearItems()
        {
            for (int i = 0; i < this.Items.Count; i++)
                DeRegisterINotifyPropertyChanged(this.IndexOf(this.Items[i]));

            base.ClearItems();
        }

        protected override void InsertItem(int index, T item)
        {
            base.InsertItem(index, item);
            RegisterINotifyPropertyChanged(item);
        }

        protected override void RemoveItem(int index)
        {
            base.RemoveItem(index);
            DeRegisterINotifyPropertyChanged(index);
        }

        private void RegisterINotifyPropertyChanged(T item)
        {
            if (item is INotifyPropertyChanged)
                (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
        }

        private void DeRegisterINotifyPropertyChanged(int index)
        {
            if (this.Items[index] is INotifyPropertyChanged)
                (this.Items[index] as INotifyPropertyChanged).PropertyChanged -= OnPropertyChanged;
        }

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            T item = (T)sender;
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, item)); 
        }
    }

这会失败。当您将一个项作为参数添加时,NotifyCollectionChangedEventArgs 的构造函数会抛出以下异常:System.ArgumentException: 'Reset action must be initialized with no changed items.' - tjmoore

2

我认为使用实现了INotifyPropertyChanged接口的项目来填充ObservableCollection将会导致当项目引发其PropertyChanged通知时,CollectionChanged事件被触发。

仔细想想,我认为你需要使用BindingList<T>来使单个项目的更改自动传播。

否则,您需要手动订阅每个项目的更改通知并触发CollectionChanged事件。请注意,如果您创建自己的派生ObservableCollection<T>,则必须在实例化和Add()Insert()上订阅,并在Remove()RemoveAt()Clear()上取消订阅。否则,您可以订阅CollectionChanged事件并使用事件参数中添加和删除的项目来订阅/取消订阅。


4
当容器内的项目更改时,您将不会收到更改通知,这一点是正确的。 - Aaron McIver
提到BindingList,点赞!它在WPF中表现良好,并且在许多情况下比ObservableCollection更好。 - Riva

2
在WinForms中,BindingList是标准做法。在WPF和Silverlight中,你通常会被迫使用ObservableCollection并需要监听每个项目的PropertyChanged

1
据我所知,BindingList 在 WPF 中实际上运作得非常好。 - Riva

0
请使用以下代码:

-我的模型:

 public class IceCream: INotifyPropertyChanged
{
    private int liczba;

    public int Liczba
    {
        get { return liczba; }
        set { liczba = value;
        Zmiana("Liczba");
        }
    }

    public IceCream(){}

//in the same class implement the below-it will be responsible for track a changes

    public event PropertyChangedEventHandler PropertyChanged;

    private void Zmiana(string propertyName) 
    {
        if (PropertyChanged != null) 
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

在我的PersonList类中,实现了一个方法,负责在AppBarControl中的按钮点击后将值增加1。
async private void Add_Click(object sender, RoutedEventArgs e)
    {
        List<IceCream> items = new List<IceCream>();
        foreach (IceCream item in IceCreamList.SelectedItems)
        {
            int i=Flavors.IndexOf(item);
            Flavors[i].Liczba =item.Liczba+ 1;
            //Flavors.Remove(item);

            //item.Liczba += 1;

           // items.Add(item);
           // Flavors.Add(item);
        }

        MessageDialog d = new MessageDialog("Zwiększono liczbę o jeden");
        d.Content = "Zwiększono liczbę o jeden";
        await d.ShowAsync();


        IceCreamList.SelectedIndex = -1;
    }
}

我希望这对某人有用 请注意:

private ObservableCollection<IceCream> Flavors;

-


0
我发现最简单的解决方法是使用RemoveAt(index)删除该项,然后使用InsertAt(index)在相同的索引上添加修改后的项,这样ObservableCollection将会通知视图。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接