ObservableCollection没有注意到它中的Item何时发生变化(即使使用INotifyPropertyChanged)

时间:2009-09-15 14:17:40

标签: c# observablecollection inotifypropertychanged

有谁知道为什么这段代码不起作用:

public class CollectionViewModel : ViewModelBase {  
    public ObservableCollection<EntityViewModel> ContentList
    {
        get { return _contentList; }
        set 
        { 
            _contentList = value; 
            RaisePropertyChanged("ContentList"); 
            //I want to be notified here when something changes..?
            //debugger doesn't stop here when IsRowChecked is toggled
        }
     }
}

public class EntityViewModel : ViewModelBase
{

    private bool _isRowChecked;

    public bool IsRowChecked
    {
        get { return _isRowChecked; }
        set { _isRowChecked = value; RaisePropertyChanged("IsRowChecked"); }
    }
}

ViewModelBase包含RaisePropertyChanged等的所有内容,除了此问题外,它还可以用于其他所有内容。

18 个答案:

答案 0 :(得分:171)

这是一个子类ObservableCollection的drop-in类,当列表项的属性发生更改时,实际上会引发Reset操作。它强制执行所有项目INotifyPropertyChanged

这里的好处是您可以将数据绑定到此类,并且所有绑定都将随着项属性的更改而更新。

public sealed class TrulyObservableCollection<T> : ObservableCollection<T>
    where T : INotifyPropertyChanged
{
    public TrulyObservableCollection()
    {
        CollectionChanged += FullObservableCollectionCollectionChanged;
    }

    public TrulyObservableCollection(IEnumerable<T> pItems) : this()
    {
        foreach (var item in pItems)
        {
            this.Add(item);
        }
    }

    private void FullObservableCollectionCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
        {
            foreach (Object item in e.NewItems)
            {
                ((INotifyPropertyChanged)item).PropertyChanged += ItemPropertyChanged;
            }
        }
        if (e.OldItems != null)
        {
            foreach (Object item in e.OldItems)
            {
                ((INotifyPropertyChanged)item).PropertyChanged -= ItemPropertyChanged;
            }
        }
    }

    private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {            
        NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));
        OnCollectionChanged(args);
    }
}

答案 1 :(得分:110)

当您更改集合中的值时,不会调用ContentList的Set方法,而是应该注意CollectionChanged事件触发。

public class CollectionViewModel : ViewModelBase
{          
    public ObservableCollection<EntityViewModel> ContentList
    {
        get { return _contentList; }
    }

    public CollectionViewModel()
    {
         _contentList = new ObservableCollection<EntityViewModel>();
         _contentList.CollectionChanged += ContentCollectionChanged;
    }

    public void ContentCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        //This will get called when the collection is changed
    }
}

好的,今天两次我被MSDN文档错误所困扰。在我给你的链接中说:

  

添加,删除项目时发生,   已更改,移动或整个列表   刷新。

但是当项目被更改时,实际上不会触发。我想你需要一个更强大的方法:

public class CollectionViewModel : ViewModelBase
{          
    public ObservableCollection<EntityViewModel> ContentList
    {
        get { return _contentList; }
    }

    public CollectionViewModel()
    {
         _contentList = new ObservableCollection<EntityViewModel>();
         _contentList.CollectionChanged += ContentCollectionChanged;
    }

    public void ContentCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Remove)
        {
            foreach(EntityViewModel item in e.OldItems)
            {
                //Removed items
                item.PropertyChanged -= EntityViewModelPropertyChanged;
            }
        }
        else if (e.Action == NotifyCollectionChangedAction.Add)
        {
            foreach(EntityViewModel item in e.NewItems)
            {
                //Added items
                item.PropertyChanged += EntityViewModelPropertyChanged;
            }     
        }       
    }

    public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        //This will get called when the property of an object inside the collection changes
    }
}

如果您需要这么多,您可能想要自己的ObservableCollection子类,当成员自动触发CollectionChanged事件时触发PropertyChanged事件(就像它说的那样)应该在文档......)

答案 2 :(得分:19)

这使用了上述想法,但使其成为派生的“更敏感”的集合:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections;

namespace somethingelse
{
    public class ObservableCollectionEx<T> : ObservableCollection<T> where T : INotifyPropertyChanged
    {
        // this collection also reacts to changes in its components' properties

        public ObservableCollectionEx() : base()
        {
            this.CollectionChanged +=new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ObservableCollectionEx_CollectionChanged);
        }

        void ObservableCollectionEx_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach(T item in e.OldItems)
                {
                    //Removed items
                    item.PropertyChanged -= EntityViewModelPropertyChanged;
                }
            }
            else if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach(T item in e.NewItems)
                {
                    //Added items
                    item.PropertyChanged += EntityViewModelPropertyChanged;
                }     
            }       
        }

        public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            //This will get called when the property of an object inside the collection changes - note you must make it a 'reset' - dunno why
            NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
            OnCollectionChanged(args);
        }
    }
}

答案 3 :(得分:10)

ObservableCollection不会将单个项目更改传播为CollectionChanged事件。您需要订阅每个事件并手动转发,或者您可以查看BindingList[T]类,它将为您执行此操作。

答案 4 :(得分:7)

添加到TruelyObservableCollection事件“ItemPropertyChanged”:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; // ObservableCollection
using System.ComponentModel; // INotifyPropertyChanged
using System.Collections.Specialized; // NotifyCollectionChangedEventHandler
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ObservableCollectionTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // ATTN: Please note it's a "TrulyObservableCollection" that's instantiated. Otherwise, "Trades[0].Qty = 999" will NOT trigger event handler "Trades_CollectionChanged" in main.
            // REF: http://stackoverflow.com/questions/8490533/notify-observablecollection-when-item-changes
            TrulyObservableCollection<Trade> Trades = new TrulyObservableCollection<Trade>();
            Trades.Add(new Trade { Symbol = "APPL", Qty = 123 });
            Trades.Add(new Trade { Symbol = "IBM", Qty = 456});
            Trades.Add(new Trade { Symbol = "CSCO", Qty = 789 });

            Trades.CollectionChanged += Trades_CollectionChanged;
            Trades.ItemPropertyChanged += PropertyChangedHandler;
            Trades.RemoveAt(2);

            Trades[0].Qty = 999;

            Console.WriteLine("Hit any key to exit");
            Console.ReadLine();

            return;
        }

        static void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
        {
            Console.WriteLine(DateTime.Now.ToString() + ", Property changed: " + e.PropertyName + ", Symbol: " + ((Trade) sender).Symbol + ", Qty: " + ((Trade) sender).Qty);
            return;
        }

        static void Trades_CollectionChanged(object sender, EventArgs e)
        {
            Console.WriteLine(DateTime.Now.ToString() + ", Collection changed");
            return;
        }
    }

    #region TrulyObservableCollection
    public class TrulyObservableCollection<T> : ObservableCollection<T>
        where T : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler ItemPropertyChanged;

        public TrulyObservableCollection()
            : base()
        {
            CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollection_CollectionChanged);
        }

        void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null)
            {
                foreach (Object item in e.NewItems)
                {
                    (item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
                }
            }
            if (e.OldItems != null)
            {
                foreach (Object item in e.OldItems)
                {
                    (item as INotifyPropertyChanged).PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
                }
            }
        }

        void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
            OnCollectionChanged(a);

            if (ItemPropertyChanged != null)
            {
                ItemPropertyChanged(sender, e);
            }
        }
    }
    #endregion

    #region Sample entity
    class Trade : INotifyPropertyChanged
    {
        protected string _Symbol;
        protected int _Qty = 0;
        protected DateTime _OrderPlaced = DateTime.Now;

        public DateTime OrderPlaced
        {
            get { return _OrderPlaced; }
        }

        public string Symbol
        {
            get
            {
                return _Symbol;
            }
            set
            {
                _Symbol = value;
                NotifyPropertyChanged("Symbol");
            }
        }

        public int Qty
        {
            get
            {
                return _Qty;
            }
            set
            {
                _Qty = value;
                NotifyPropertyChanged("Qty");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
#endregion
}

答案 5 :(得分:6)

我使用Jack Kenyons的答案来实现我自己的OC,但我想指出我必须做出的一个改变才能让它发挥作用。而不是:

    if (e.Action == NotifyCollectionChangedAction.Remove)
    {
        foreach(T item in e.NewItems)
        {
            //Removed items
            item.PropertyChanged -= EntityViewModelPropertyChanged;
        }
    }

我用过这个:

    if (e.Action == NotifyCollectionChangedAction.Remove)
    {
        foreach(T item in e.OldItems)
        {
            //Removed items
            item.PropertyChanged -= EntityViewModelPropertyChanged;
        }
    }

如果action为.Remove,似乎“e.NewItems”产生null。

答案 6 :(得分:6)

在这个主题上加上我的2美分。感觉TrulyObservableCollection需要使用ObservableCollection找到的另外两个构造函数:

public TrulyObservableCollection()
        : base()
    {
        HookupCollectionChangedEvent();
    }

    public TrulyObservableCollection(IEnumerable<T> collection)
        : base(collection)
    {
        foreach (T item in collection)
            item.PropertyChanged += ItemPropertyChanged;

        HookupCollectionChangedEvent();
    }

    public TrulyObservableCollection(List<T> list)
        : base(list)
    {
        list.ForEach(item => item.PropertyChanged += ItemPropertyChanged);

        HookupCollectionChangedEvent();
    }

    private void HookupCollectionChangedEvent()
    {
        CollectionChanged += new NotifyCollectionChangedEventHandler(TrulyObservableCollectionChanged);
    }

答案 7 :(得分:5)

我知道我这次聚会太迟了,但也许 - 这会对某人有所帮助..

Here你可以找到我的ObservableCollectionEx实现。它有一些功能:

  • 它支持ObservableCollection
  • 中的所有内容
  • 它的线程安全
  • 它支持ItemPropertyChanged事件(每次触发Item.PropertyChanged项时都会引发)
  • 它支持过滤器(因此,您可以创建ObservableCollectionEx,将另一个集合作为Source传递给它,并使用简单谓词过滤。在WPF中非常有用,我在我的应用程序中使用了很多这个功能)。更多 - 通过INotifyPropertyChanged界面过滤跟踪项目的更改。

当然,任何评论都表示赞赏;)

答案 8 :(得分:3)

如果我知道ObservableCollection只在我们添加/删除或移动集合中的项目时才发生事件。当我们简单地更新集合项目集合中的某些属性时,不要发信号通知它,UI也不会更新。

您可以在Model类中简单地实现 INotifyPropertyChange 。 而且当我们更新集合项目中的某些属性时,它会自动更新UI。

public class Model:INotifyPropertyChange
{
//...
}

而不是

public ObservableCollection<Model> {get; set;}

在我的情况下,我使用ListView绑定此集合,并在ItemTemplate中设置Binding to Model属性,它运行良好。

这是一些代码段

Windows XAML:

<Window.DataContext>
    <local:ViewModel/>
</Window.DataContext>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <ListView 
        Margin="10"
        BorderBrush="Black"
        HorizontalAlignment="Center"
        SelectedItem="{Binding SelectedPerson}"
        ItemsSource="{Binding Persons}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <Label Content="{Binding Name}"/>
                    <Label Content="-"/>
                    <Label Content="{Binding Age}"/>
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
    <Grid 
        Grid.Row="1"
        VerticalAlignment="Center"
        HorizontalAlignment="Center">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Label 
            VerticalAlignment="Center"
            Content="Name:"/>
        <TextBox
            Text="{Binding SelectedPerson.Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
            Margin="10"
            Grid.Column="1" 
            Width="100"/>
        <Label 
            VerticalAlignment="Center"
            Grid.Row="1"
            Content="Age:"/>
        <TextBox
            Text="{Binding SelectedPerson.Age,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
            Margin="10"
            Grid.Row="1"
            Grid.Column="1" 
            Width="100"/>


    </Grid>
</Grid>

模型代码示例:

public class PersonModel:INotifyPropertyChanged
{
    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            OnPropertyChanged();
        }
    }

    public int Age
    {
        get => _age;
        set
        {
            _age = value;
            OnPropertyChanged();
        }
    }

    private string _name;
    private int _age;
    //INotifyPropertyChanged implementation
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

和ViewModel实施:

 public class ViewModel:INotifyPropertyChanged
{
    public ViewModel()
    {
        Persons = new ObservableCollection<PersonModel>
        {
            new PersonModel
            {
                Name = "Jack",
                Age = 30
            },
            new PersonModel
            {
                Name = "Jon",
                Age = 23
            },
            new PersonModel
            {
                Name = "Max",
                Age = 23
            },
        };
    }

    public ObservableCollection<PersonModel> Persons { get;}

    public PersonModel SelectedPerson
    {
        get => _selectedPerson;
        set
        {
            _selectedPerson = value;
            OnPropertyChanged();
        }
    }

    //INotifyPropertyChanged Implementation
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private PersonModel _selectedPerson;
}

答案 9 :(得分:1)

我在这里看到的大多数示例都将 INotifyPropertyChanged 约束置于泛型类型上,这会强制模型实现 INotifyPropertyChanged。

如果您遵循在模型上放置 INotifyPropertyChanged 约束的示例,那么这与在您的模型中实现 INotifyPropertyChanged 并允许 ObservableCollection 处理 Update 属性更改一样好。

但如果你不想让你的模型实现 INotifyPropertyChanged,你可以试试这个。

CustomObservableCollection

const items = [
  { name: "item 1", enabled: true, id: 0, timeleft: 50 },
  { name: "item 2", enabled: true, id: 1, timeleft: 120 },
  { name: "item 3", enabled: true, id: 2, timeleft: 0 }
];

模型

 public class CustomObservableCollection<T> : ObservableCollection<T>
 {

      public void Refresh(T item)
      {
          var index = IndexOf(item);

          RemoveAt(index);
          Insert(index, item);

          OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, IndexOf(item)));
      }
 }

视图模型

public class Person
{
    public string FirstName { get; set; }   
    public string LastName { get; set; }   
    public int Age { get; set; }   
}

答案 10 :(得分:1)

考虑使用BindingList并调用ResetBindings方法,而不是ObservableCollection或TrulyObservableCollection。

例如:

private BindingList<TfsFile> _tfsFiles;

public BindingList<TfsFile> TfsFiles
{
    get { return _tfsFiles; }
    set
    {
        _tfsFiles = value;
        NotifyPropertyChanged();
    }
}

鉴于某个事件,例如点击,您的代码将如下所示:

foreach (var file in TfsFiles)
{
    SelectedFile = file;
    file.Name = "Different Text";
    TfsFiles.ResetBindings();
}

我的模特看起来像这样:

namespace Models
{
    public class TfsFile 
    {
        public string ImagePath { get; set; }

        public string FullPath { get; set; }

        public string Name { get; set; }

        public string Text { get; set; }

    }
}

答案 11 :(得分:1)

我尝试这个解决方案,但只有当我更改集合时才会像RaisePropertyChange(“SourceGroupeGridView”)一样对我有效,为每个添加或更改的项目触发。

问题在于:

public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
     NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
    OnCollectionChanged(args);
}

NotifyCollectionChangedAction.Reset此操作对groupsgrid中的所有项进行完全重新绑定,在RaisePropertyChanged中等效。当你使用它时,所有gridview组都会刷新。

如果您只想在UI中刷新新项目的组,则不使用重置操作,您需要在itemproperty中模拟添加操作,如下所示:

void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{         
    var index = this.IndexOf((T)sender);

    this.RemoveAt(index);
    this.Insert(index, (T)sender);

    var a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, sender);
    OnCollectionChanged(a);
}

对不起我的英文,感谢基本代码:), 我希望这有助于某人^ _ ^

Enjoi !!

答案 12 :(得分:1)

这是上述解决方案的扩展方法......

public static TrulyObservableCollection<T> ToTrulyObservableCollection<T>(this List<T> list)
     where T : INotifyPropertyChanged
{
    var newList = new TrulyObservableCollection<T>();

    if (list != null)
    {
        list.ForEach(o => newList.Add(o));
    }

    return newList;
}  

答案 13 :(得分:1)

我使用的标准observablecollection的简单解决方案:

不要添加到您的财产或直接更改它的内部项目,而是创建一些像这样的临时集合

ObservableCollection<EntityViewModel> tmpList= new ObservableCollection<EntityViewModel>();

添加项目或对tmpList进行更改,

tmpList.Add(new EntityViewModel(){IsRowChecked=false}); //Example
tmpList[0].IsRowChecked= true; //Example
...

然后通过转让将其传递给您的实际财产。

ContentList=tmpList;

这会改变整个属性,导致你需要注意INotifyPropertyChanged。

答案 14 :(得分:0)

这是我的实施版本。如果列表中的对象没有实现INotifyPropertyChanged,它会检查并抛出错误,因此在开发时不能忘记该问题。在外部,您使用ListItemChanged事件来确定列表或列表项本身是否已更改。

public class SpecialObservableCollection<T> : ObservableCollection<T>
{
    public SpecialObservableCollection()
    {
        this.CollectionChanged += OnCollectionChanged;
    }

    void OnCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        AddOrRemoveListToPropertyChanged(e.NewItems,true); 
        AddOrRemoveListToPropertyChanged(e.OldItems,false); 
    }

    private void AddOrRemoveListToPropertyChanged(IList list, Boolean add)
    {
        if (list == null) { return; }
        foreach (object item in list)
        {
            INotifyPropertyChanged o = item as INotifyPropertyChanged;
            if (o != null)
            {
                if (add)  { o.PropertyChanged += ListItemPropertyChanged; }
                if (!add) { o.PropertyChanged -= ListItemPropertyChanged; }
            }
            else
            {
                throw new Exception("INotifyPropertyChanged is required");
            }
        }
    }

    void ListItemPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        OnListItemChanged(this, e);
    }

    public delegate void ListItemChangedEventHandler(object sender, PropertyChangedEventArgs e);

    public event ListItemChangedEventHandler ListItemChanged;

    private void OnListItemChanged(Object sender, PropertyChangedEventArgs e)
    {
        if (ListItemChanged != null) { this.ListItemChanged(this, e); }
    }


}

答案 15 :(得分:0)

2行代码中的简单解决方案。只需使用复制构造函数。 无需编写TrulyObservableCollection等。

示例:

        speakers.list[0].Status = "offline";
        speakers.list[0] = new Speaker(speakers.list[0]);

没有复制构造函数的另一种方法。您可以使用序列化。

        speakers.list[0].Status = "offline";
        //speakers.list[0] = new Speaker(speakers.list[0]);
        var tmp  = JsonConvert.SerializeObject(speakers.list[0]);
        var tmp2 = JsonConvert.DeserializeObject<Speaker>(tmp);
        speakers.list[0] = tmp2;

答案 16 :(得分:0)

您还可以使用此扩展方法在相关集合中轻松注册用于项属性更改的处理程序。此方法会自动添加到所有实现INotifyCollectionChanged的集合中,这些集合包含实现INotifyPropertyChanged的项目:

public static class ObservableCollectionEx
{
    public static void SetOnCollectionItemPropertyChanged<T>(this T _this, PropertyChangedEventHandler handler)
        where T : INotifyCollectionChanged, ICollection<INotifyPropertyChanged> 
    {
        _this.CollectionChanged += (sender,e)=> {
            if (e.NewItems != null)
            {
                foreach (Object item in e.NewItems)
                {
                    ((INotifyPropertyChanged)item).PropertyChanged += handler;
                }
            }
            if (e.OldItems != null)
            {
                foreach (Object item in e.OldItems)
                {
                    ((INotifyPropertyChanged)item).PropertyChanged -= handler;
                }
            }
        };
    }
}

使用方法:

public class Test
{
    public static void MyExtensionTest()
    {
        ObservableCollection<INotifyPropertyChanged> c = new ObservableCollection<INotifyPropertyChanged>();
        c.SetOnCollectionItemPropertyChanged((item, e) =>
        {
             //whatever you want to do on item change
        });
    }
}

答案 17 :(得分:-1)

在ObservableCollection列表中触发OnChange

  1. 获取所选商品的索引
  2. 从父级删除项目
  3. 将项目添加到父项中的相同索引处

示例:

int index = NotificationDetails.IndexOf(notificationDetails);
NotificationDetails.Remove(notificationDetails);
NotificationDetails.Insert(index, notificationDetails);