INotifyPropertyChanged does not work from class held in ObservableCollection<Class> - c#

I have a class, "BaseClass" that implements INotifyPropertyChanged and has the following:
BaseClass:
private bool isOn;
public bool IsOn
{
get { return isOn; }
set
{
isOn = value;
RaisePropertyChanged("BaseClass:IsOn");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
I then have a class, "DIClass" that also implements INotifyPropertyChanged. It also has an ObservableCollection<BaseClass>:
DIClass:
public ObservableCollection<BaseClass> ClassesOfA;
private string iNPCTest;
public string INPCTest
{
get { return iNPCTest; }
set
{
iNPCTest = value;
RaisePropertyChanged("DIClass: INPCTest");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
My ViewModel holds an intance of "DIClass" and registers to it's PropertyChanged event. When I set the value of INPCTest in "DIClass", the ViewModel 'captures' the event correctly. However when I updated the IsOn property within the ObservableCollection, as below, the event is not picked up in the ViewModel.
ClassesOfA[0].IsOn = true;
Why is the INPC interface not working with the nested property? The question and answer here seems quite relevant, but I can't figure it out.
EDIT: additional explanation and code:
I can register to the PropetyChanged events of the ObservableCollection's items, as such:
ClassesOfA[0].PropertyChanged += DIClass_PropertyChanged;
ClassesOfA[1].PropertyChanged += DIClass_PropertyChanged;
However, this still does not bubble up to notify my ViewModel, that a property of my DIClass's ObservableCollection<BaseClass> has changed. I want to use INPC to bubble up event information / property updates up via MVVM layers. But I want to "wrap" them to make my classes cleaner/ less properties lying around
EDIT:
I add this "sketch" of my problem/scenario, with basic naming to make it easy:

To answer your question: This is by design.
ObservableCollection has two events:
CollectionChanged: Fires when the collection changes, e.g. collection.Add( item )
PropertyChanged: Fires when the property changes, e.g. collection = new ObservablecCollection<T>();
I think you need no ObservableCollection, because - as far as I understand your question - you want to observe the changes of the properties of the items in the collection. To achieve that you need to register to each observed item's PropertyChanged like this:
public List<BaseClass> Collection {get;set;}
public void InitializeCollection( IEnumerable<BaseClass> baseClassCollection){
Collection = new List<BaseClass>();
foreach(var item in baseClassCollection){
item.PropertyChanged += MethodToCallOnPropertyChanges;
Collection.Add( item );
}
}
public void MethodToCallOnPropertyChanges(object sender, PropertyChangedEventArgs e){
//react to any property changes
doSomething();
//react to specific properties
if(e != null && e.PropertyName.Equals("isOn"))
doSomethingOtherStuff();
}
This can be very annoying and can causes some other problems.
If I would come across this, I would think about redesigning the ViewModels and the UI. I would try to have an UI which is bound to each BaseClass item. For example, if I have an ListView I would provide an ItemTemplate in which the BaseClass item is bound. Doing so would prevent the need of registering to each item's PropertyChanged.

My suggestion is that you could create a customized ObservableCollection class that raises a Reset action when a property on a list item changes. It enforces all items to implement INotifyPropertyChanged.
I made a simple demo and you that you could check:
public class DIClass : INotifyPropertyChanged
{
public ExObservableCollection<BaseClass> ClassesOfA
... other code...
}
public sealed class ExObservableCollection<T> : ObservableCollection<T>
where T : INotifyPropertyChanged
{
public ExObservableCollection()
{
CollectionChanged += AllObservableCollectionCollectionChanged;
}
public ExObservableCollection(IEnumerable<T> pItems) : this()
{
foreach (var item in pItems)
{
this.Add(item);
}
}
private void AllObservableCollectionCollectionChanged(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);
}
}
Then you could use the ExObservableCollection class in the DIClass object. When the properties inside the BaseClass changes, the UI will be updated.
Update:
Finally, I found out the unexpected behavior you mentioned based on the complex sample. The ExObservableCollection class works well and fires the property changed event correctly.
The key point is you think if the property change event in baseclass is fired then it will
trigger the property change event in DIClass as well, right? I have to say that is not correct. The property change event only fires in the current class. It won't pass to the parent class unless you handle it in the parent class. It fired only once and notify the UI when the target property is changed.
If I understand your scenario correctly, you want to change the ToggleButton's status when the same property in BaseClassobject is changed. But the ToggleButtons are bind to VMData objects so that you need to get notified when the BaseClass objects are changed in the DIClass objects. So you want the the property change event of BaseCasss triggers the property change event of the DIClass.
Handling the property changed event of BaseClass in the DIClass object is the correct way to do what you want. It's the same like handling DIClass event in the ViewModel. But you don't want it since there might be many objects.
Then the first version of your sample is the recommended way to achieve what you want by triggering the property changed event of the DIClass on your own.

Related

notify viewmodel2 if viewmodel1 property changes

I have a scenario in wpf +mvvm i.e if my particular property changes in viewmodel1 then i wan to notify viewmodel2 having observable collection that property "A" has bee changed
1)I want to fire it for particular property not for all.
i have tried below code but not working .please let me know how cam i do this.
public class Model1 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// Create custom event
public event EventHandler NotifyChange;
private string testProperty;
public string TestProperty
{
get
{
return testProperty;
}
set
{
testProperty = value;
// If changing properties, fire your OnPropertyChanged to update UI
OnPropertyChanged("TestProperty");
}
}
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
// Fire your custom event if a property changed
NotifyChange(this, null);
}
}
}
public class Model2 : INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public Model2()
{
// Assuming there is an accessible instance of model1
Model1 m1Instance = new Model1();
// Hook up your NotifyChange event from model1
m1Instance.NotifyChange += Model1Changed;
}
private void Model1Changed(object sender, EventArgs e)
{
// this will be triggered on change in model1
}
private void OnCollectionChanged(object singleObject)
{
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, singleObject));
}
}
Use PubSub Events
My suggestion would be to look into PubSub events.
My recommendation of doing this, is to use Prism. Here's some more information: http://www.c-sharpcorner.com/UploadFile/5ffb84/prism-event-aggregator-in-wpf-with-mvvm/
You will be sticking to proper MVVM practices in this case.
Here's MSDN's ever-useful guide: https://msdn.microsoft.com/en-us/library/ff649664.aspx
Have a really good read-up on how this works, and how to use/implement it.
Alternatively
This will work, but I would still defer to using PubSub events if possible.
You could try this:
public class Model1 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _property;
public string Property
{
get { return _property; }
set
{
_property = value;
OnPropertyChanged("Property");
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Model2
{
public Model2()
{
// You might be storing your Model1 as a property in the Model2?
// I don't know, but I've put it in the constructor just for example.
var model1 = new Model1();
model1.PropertyChanged += OnModel1PropertyChanged;
}
private void OnModel1PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Property")
{
// Do stuff here when the right property has changed in model 1
}
}
}
I have only new'd up a Model1 in the constructor of Model2 as an example - you might be assigning it and storing as a field or property elsewhere in the Model2 ViewModel.
This might be particularly useful if you have ViewModels within ViewModels (parent VM > child VMs).
I use parent > child VMs quite regularly, and I don't think it's against MVVM best practices, but I still use the EventAggregator, instead of events.
As a side note, if you are using C#6:
Use nameof(Property) instead of "magic strings" (e.g. "Property"). This makes for much easier refactoring and compiler can tell you about errors - but essentially does the same job. Use this in the OnPropertyChanged() call in your setter
You can also use the nameof keyword when checking the property name, with the same principle as above. Like this: if (e.PropertyName == nameof(Model1.Property)) ...
Use null propagation: PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));. Changes the method body of your OnPropertyChanged to a nice one-liner, whilst still doing the same job
I digress
I would always look at sticking to proper MVVM practices, where possible.
I use Prism's EventAggregator on a daily basis and will swear by it.
Have a good read up on PubSub Events (you can use any type of event aggregator, but I would say to use Prism's, preferably)
Hope this helps! :)
If this is just to notify Model2 I think you dont need all these implementations. You can do something like
public string TestProperty
{
get
{
return testProperty;
}
set
{
testProperty = value;
// If changing properties, fire your OnPropertyChanged to update UI
OnPropertyChanged("TestProperty");
//Here you can call a method of Model2 sating that its changed
Model2 m2Instance = new Model2();
m2Instance.ValueChanged();
}
}
Add the method ValueChanged in you model 2.
That's because you're not registering to PropertyChanged. You're registering your event handler on NotifyChange so that PropertyChanged in Model1 is NULL and so NotifyChange is not fired.
So, you need to implement your OnPropertyChanged as the following:
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
if (NotifyChange != null)
NotifyChange(this, null);
}
Finally, if you want your NotifyChange to be fired for a specific property, then adjust the code above to check for the propName before you fire the event.

Singleton OnPropertyChanged Not Firing

I have a class called DataModel where I am storing an ObservableCollection of projects. I was using a static ObservableCollection, but since I want to bind to it, and OnPropertyChanged doesn't seem to work correctly for static properties, I created it as a singleton:
public sealed class DataModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private static readonly DataModel instance = new DataModel();
private DataModel() { }
public static DataModel Instance
{
get
{
return instance;
}
}
#region Projects
private ObservableCollection<Project> projects = new ObservableCollection<Project>();
public ObservableCollection<Project> Projects
{
get
{
return projects;
}
set
{
if (projects == value)
{
return;
}
projects = value;
OnPropertyChanged("Projects");
}
}
#endregion Projects
public void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Then when I click a button,
Project newProject = new Project() { Title = "Test" };
DataModel.Instance.Projects.Add(newProject);
From what I could come up with from various sources, this ought to work correctly. However, the OnPropertyChanged event is never called. If I do
DataModel.Instance.Projects = new ObservableCollection<Project>();
it is called. But adding a Project to the collection won't call it.
OnPropertyChanged is only automatically fired when that property is reassigned. That is why reassigning your entire collection causes it to be fired. Modifying the collection fires the collection's own CollectionChanged event instead, since you're not actually changing the Projects reference, just mutating the same collection it's referring to.
If your collection is bound to a control's ItemsSource property correctly, e.g.
<ListView ItemsSource="{Binding Projects}"/>
where the data context is your DataModel instance, you should not need to do anything beyond adding the new item.
If you need to do something when the collection is changed, subscribe to its CollectionChanged event instead:
private DataModel()
{
Projects.CollectionChanged += Projects_CollectionChanged;
}
private void Projects_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
// An item was added...
}
}
I expect it NOT to work. Because you are adding item to Observation collection in which case there is no reason setter would be called and thus OnPropertyChanged. That's why it works when you initialize DataModel.Instance.Projects from calling code. You could call property changed explicitly as below:
Project newProject = new Project() { Title = "Test" };
DataModel.Instance.Projects.Add(newProject);
DataModel.Instance.OnPropertyChanged("Projects");
Disclaimer: Although this is possible, you really need not do this. That is why they have provided us with ObservableCollection. Any item/s added or removed to/from such collection are automatically notified to the View. If this was not true, why would anybody use ObservableCollection instead of simple List for data bindings.

binding to an Observable Collection in a usercontrol and getting notifications from it

I've been looking through some other questions here that look similar to my issue, but none have the answer. So, here goes...
I'm developing a C# application using WPF and the MVVM pattern. I have a UserControl (called GroupTree) that contains a dependency property:
public static DependencyProperty ChartGroupsProperty = DependencyProperty.Register("ChartGroups", typeof(ChartGroupCollection), typeof(GroupTree),
new FrameworkPropertyMetadata() { DefaultValue=new ChartGroupCollection(), PropertyChangedCallback = OnChartGroupsChanged, BindsTwoWayByDefault = true });
public ChartGroupCollection ChartGroups
{
get { return (ChartGroupCollection)GetValue(ChartGroupsProperty); }
set
{
SetValue(ChartGroupsProperty, value);
}
}
private static void OnChartGroupsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != e.NewValue)
{
// Nothing at the moment
}
}
Its of type ChartGroupCollection which is defined so:
public class ChartGroupCollection : ObservableCollection<ChartGroup>
{
}
The ChartGroup contains properties that use INotifyProperty and I've independently verified that all the change events that should fire for the main collection do so properly.
The MainWindow.Xaml that uses the control:
<Controls:GroupTree x:Name="Groups" ChartGroups="{Binding MainGroups}" />
I have another control that binds to the ChartGroups Value in the GroupTree Control:
<Controls:ChartsControl x:Name="Charts1_1" Tree="{Binding ElementName=Groups, Path=ChartGroups}" />
What I want to happen is when the GroupTree ChartGroups Value changes, for this to notify the ChartsControl dynamically of those changes and update accordingly. But at the moment, no joy. The data is there, because if force a refresh of the ChartsControl manually the changes show. I've tried binding to the MainGroups property hoping that would work, but that doesn't either.
I've probably missed something blindingly obvious, but I'm not sure what. Any ideas?
Rob
ObservableCollection only listens to the changes occur in the collection like items being added or removed and it does not notify about any changes happened in the individual items of its collection.
The following class enhances the funtionality of ObservableCollection by registering itself to the INofityPropertyChanged event of the items being added and raises the CollectionChanged event of the ObservableCollection when a property of an item of the collection changes.
Calling the ClearItems at the end releases all event handlers.
More details can be found from the below link.
How to Listen to Property Changes of Items of an ObservableCollection
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Collections;
namespace VJCollections
{
/// <summary>
/// This class adds the ability to refresh the list when any property of
/// the objects changes in the list which implements the INotifyPropertyChanged.
/// </summary>
/// <typeparam name="T">
public class ItemsChangeObservableCollection<T> :
ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
RegisterPropertyChanged(e.NewItems);
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
UnRegisterPropertyChanged(e.OldItems);
}
else if (e.Action == NotifyCollectionChangedAction.Replace)
{
UnRegisterPropertyChanged(e.OldItems);
RegisterPropertyChanged(e.NewItems);
}
base.OnCollectionChanged(e);
}
protected override void ClearItems()
{
UnRegisterPropertyChanged(this);
base.ClearItems();
}
private void RegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
if (item != null)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
private void UnRegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
if (item != null)
{
item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
}
Unfortunately, the PropertyChanged callback is only invoked when PropertyChanged is raised against the bound property (thus causing the binding to evalutate).
You need the CollectionChanged event provided by INotifyCollectionChanged. So you have two options:
Register for that event in OnChartGroupsChanged and handle it yourself (make sure to de-register for the old one, so you don't leak the handle!)
Derive from something like ItemsControl and use its Items property. This removes your dependency property and allows ItemsControl to handle all the collection changed stuff for you.
If its feasable, I would go with the second one. If not, utilizing the CollectionChanged event shouldn't be too bad.

List<string> INotifyPropertyChanged event

I have a simple class with a string property and a List property and I have the INofityPropertyChanged event implemented, but when I do an .Add to the string List this event is not hit so my Converter to display in the ListView is not hit. I am guessing the property changed is not hit for an Add to the List....how can I implement this in a way to get that property changed event hit???
Do I need to use some other type of collection?!
Thanks for any help!
namespace SVNQuickOpen.Configuration
{
public class DatabaseRecord : INotifyPropertyChanged
{
public DatabaseRecord()
{
IncludeFolders = new List<string>();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
private string _name;
public string Name
{
get { return _name; }
set
{
this._name = value;
Notify("Name");
}
}
private List<string> _includeFolders;
public List<string> IncludeFolders
{
get { return _includeFolders; }
set
{
this._includeFolders = value;
Notify("IncludeFolders");
}
}
}
}
You should use ObservableCollection<string> instead of List<string>, because unlike List, ObservableCollection will notify dependents when its contents are changed.
And in your case I'd make _includeFolders readonly - you can always work with one instance of the collection.
public class DatabaseRecord : INotifyPropertyChanged
{
private readonly ObservableCollection<string> _includeFolders;
public ObservableCollection<string> IncludeFolders
{
get { return _includeFolders; }
}
public DatabaseRecord()
{
_includeFolders = new ObservableCollection<string>();
_includeFolders.CollectionChanged += IncludeFolders_CollectionChanged;
}
private void IncludeFolders_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Notify("IncludeFolders");
}
...
}
The easiest way to make WPF's list binding work is to use a collection that implements INotifyCollectionChanged. A simple thing to do here is to replace or adapt your list with an ObservableCollection.
If you use ObservableCollection, then whenever you modify the list, it will raise the CollectionChanged event - an event that will tell the WPF binding to update. Note that if you swap out the actual collection object, you will want to raise the propertychanged event for the actual collection property.
Your List is not going to fire the NotifyPropertyChanged event automatically for you.
WPF controls that expose an ItemsSource property are designed to be bound to an ObservableCollection<T>, which will update automatically when items are added or removed.
You should have a look at ObservableCollection

How can I detect changes to item properties in the BindingList<T>?

I have a custom class Foo with properties A and B. I want to display it in a databinding control.
I have created a class Foos : BindingList<Foo> .
In order to update some internal properties of the Foos class I need to be notified of property changes (I can handle insertions, removals etc.) on the items in the list. How would you implement that functionality ?
Should I inherit Foo from some object in the framework that supports that ? I think I could create events that notify me if changes, but is that the way it should be done ? Or is there some pattern in the framework, that would help me ?
Foo should implement the INotifyPropertyChanged and INotifyPropertyChanging interfaces.
public void Foo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private int _someValue;
public int SomeValue
{
get { return _someValue; }
set { _someValue = value; NotifyPropertyChanged("SomeValue"); }
}
}
The BindingList should hook onto your event handler automatically, and your GUI should now update whenever you set your class invokes the PropertyChanged event handler.
[Edit to add:] Additionally, the BindingList class expose two events which notify you when the collection has been added to or modified:
public void DoSomething()
{
BindingList<Foo> foos = getBindingList();
foos.ListChanged += HandleFooChanged;
}
void HandleFooChanged(object sender, ListChangedEventArgs e)
{
MessageBox.Show(e.ListChangedType.ToString());
}

Categories