【问题标题】:Implementing CollectionChanged实现 CollectionChanged
【发布时间】:2011-06-03 01:37:19
【问题描述】:

我已将CollectionChanged eventhandler(onCollectionChanged) 添加到ObservableCollection 属性之一。

我发现onCollectionChanged 方法仅在向集合添加项目或删除项目的情况下被调用,而不是在集合项目被编辑的情况下被调用。

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

谢谢。

【问题讨论】:

    标签: c# wpf mvvm mvvm-light


    【解决方案1】:

    认为使用实现INotifyPropertyChanged 的项目填充ObservableCollection 将导致CollectionChanged 事件在项目引发其PropertyChanged 通知时触发。

    再想一想,我认为您需要使用 BindingList<T> 来让单个项目更改以这种开箱即用的方式传播。

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

    【讨论】:

    • 这是正确的,因为当容器内的项目发生变化时,您不会收到更改通知。
    • 1up 用于提及 BindingList。它实际上与 WPF 配合得很好,恕我直言,在许多情况下比 ObservableCollection 更好。
    【解决方案2】:

    在 winforms 中,BindingList 是标准做法。在 WPF 和 Silverlight 中,您通常被困在使用 ObservableCollection 并且需要在每个项目上收听 PropertyChanged

    【讨论】:

    • Afaik BindingList 实际上与 WPF 配合得很好。
    【解决方案3】:

    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.'
    【解决方案4】:

    您必须为每个项目添加一个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);
    }
    

    也许您必须检查某个项目是否已经在 ModifedItems-List 中(使用 List 的方法 Contains(object obj)),并且仅当该方法的结果为 false强>.

    Item 类必须实现 INotifyPropertyChanged。请参阅此example 了解如何操作。正如 Robert Rossney 所说,您也可以使用 IEditableObject 来实现 - 如果您有此要求。

    【讨论】:

    • 这将在添加和删除操作中引发异常,除非您修改代码以检查 e.NewItems 和 e.OldItems 是否为空,然后再尝试遍历它们。
    • 当你有 Names 集合时,为什么需要这个 ModifiedItems 列表?
    【解决方案5】:

    ItemsControl 监听 CollectionChanged 以管理其在屏幕上呈现的项目集合的显示。 ContentControl 监听 PropertyChanged 以管理它在屏幕上呈现的特定项目的显示。一旦理解了这一点,就很容易将这两个概念分开。

    跟踪项目是否被编辑不是这些界面中的任何一个。属性更改不是编辑——也就是说,它们不一定代表用户对对象状态的某种类型的更改。例如,一个对象可能有一个ElapsedTime 属性,该属性被计时器不断更新; UI 需要收到这些属性更改事件的通知,但它们当然不代表对象底层数据的更改。

    跟踪对象是否被编辑的标准方法是首先使该对象实现IEditableObject。然后,您可以在对象的类内部决定哪些更改构成编辑(即要求您调用BeginEdit)以及哪些更改不构成编辑。然后,您可以实现一个布尔型 IsDirty 属性,该属性在调用 BeginEdit 时设置,并在调用 EndEditCancelEdit 时清除。 (我真的不明白为什么该属性不是IEditableObject 的一部分;我还没有实现不需要它的可编辑对象。)

    当然,如果您不需要第二级抽象,则无需实现它——您当然可以监听PropertyChanged 事件,并假设该对象在被引发时已被编辑。这真的取决于您的要求。

    【讨论】:

      【解决方案6】:

      使用以下代码:

      -我的模特:

       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 中单击按钮后主动增加一个值的方法

      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;
      

      -

      【讨论】:

        【解决方案7】:

        我发现解决此限制的最简单的解决方案是使用 RemoveAt(index) 删除项目,然后使用 InsertAt(index) 在同一索引上添加修改后的项目,因此 ObservableCollection 将通知视图。

        【讨论】:

          【解决方案8】:

          我对“this answer”的编辑被拒绝! 所以我把我的编辑放在这里:

          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
          }
          

          【讨论】:

            猜你喜欢
            • 2016-12-24
            • 1970-01-01
            • 2011-06-03
            • 2023-04-09
            • 2021-08-16
            • 2017-12-06
            • 2013-01-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多