Suppose, I have the following class hierarchy:
public NestedClass : INotifyPropertyChanged {
string prop1;
public String Prop1 {
get { return prop1; }
set {
prop1 = value;
OnPropertyChanged("Prop1");
}
}
void OnPropertyChanged(String name) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public Class1 : INotifyPropertyChanged {
ObservableCollection<NestedClass> collection;
public Class1() {
collection = new ObservableCollection<NestedClass>();
}
public ObservableCollection<NestedClass> Collection {
get { return collection; }
set {
if (collection != null) {
collection.CollectionChanged -= childOnCollectionChanged;
}
collection = value;
if (collection != null) {
collection.CollectionChanged += childOnCollectionChanged;
}
}
}
void childOnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e) {
switch (e.Action) {
case NotifyCollectionChangedAction.Add:
((INotifyPropertyChanged)e.NewItems[0]).PropertyChanged += childOnPropertyChanged;
break;
case NotifyCollectionChangedAction.Remove:
((INotifyPropertyChanged)e.OldItems[0]).PropertyChanged -= childOnPropertyChanged;
break;
case NotifyCollectionChangedAction.Reset:
if (e.OldItems == null) { break; }
foreach (INotifyPropertyChanged itemToRemove in e.OldItems) {
itemToRemove.PropertyChanged -= childOnPropertyChanged;
}
break;
}
}
void childOnPropertyChanged(Object sender, PropertyChangedEventArgs e) {
OnPropertyChanged("nested");
}
void OnPropertyChanged(String name) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
there is class Class1 which contains some properties and several collections of different classes. All collections are defined in the same was as a collection of NestedClass. They are properly bound to the UI, I have no problems there.
Class1 object and nested objects/collections can be created at runtime, or can be generated by a XML deserializer. To properly handle deserialized collection, I have to subscribe to INotifyPropertyChanged event of each collection item when collection is assigned.
My goal is to handle all changes of all items of NestedClass in Class1 to get information that data was changed (and pass this information up to its parent).
The problem is that I have mutiple nested collections and they notify parent class Class1 and it successfully bubbles the event to its parent. However, one collection notifies parent Class1 about changes, but handler in the Class1 is null, as the result Class1 doesn't bubble event to its parent class (is not shown here as irrelevant). I went through debugger but was unable to find where is the problem. When other nested collections call parent's OnPropertyChanged, the handler is not null and everything is working correctly.
Edit: this issue is raised only when Class1 and NestedClass objects are generated by XML deserializer.
I read a lot of similar posts on SO, but most of them are about invalid DataContext in the view, which is not my case (I believe). What else I can check to find the root of the issue?
You might want to consider switching to a BindingList<T> instead of a ObserveableCollection<T>, it has the logic you do in childOnCollectionChanged already built in to it. Just make sure RaiseListChangedEvents is set to true and the ListChanged event will be raised with args.ListChangedType == ListChangedType.ItemChanged any time one of the members raises its PropertyChanged event.
Related
I'm new to C# and I'm trying to get a change in my datagrid to trigger a method in my main.
I've got my class:
public class siteMatch : INotifyPropertyChanged
{
public string SelectedTag
{
get { return _SelectedTag; }
set
{
if (value != _SelectedTag)
{
_SelectedTag = value;
OnPropertyChanged(_SelectedTag);
}
}
}
private string _SelectedTag;
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
And I've got this in my main class:
public partial class MainWindow : Window
{
public ObservableCollection<siteMatch> sitesMatched = new ObservableCollection<siteMatch>();
public MainWindow()
{
InitializeComponent();
sitesMatched.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(items_CollectionChanged);
}
static void items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
foreach (INotifyPropertyChanged item in e.OldItems)
item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
foreach (INotifyPropertyChanged item in e.NewItems)
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
static void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//throw new NotImplementedException();
}
}
Am I missing something here? The items_CollectionChanged method doesn't get triggered when I change something on the datagrid. I have a feeling that I haven't subscribed to the event properly. The OnPropertyChanged method gets triggered correctly but nothing happens after that.
Any help will be greatly appreciated.
Thanks,
Edit: I should point out that I only have access to .Net 4
It is my understanding that the Collection Changed is only triggered on creation of the collection, or in addition or deletion, but not when you change something inside the collection.
What you want to accomplish can be done with the events on the Datagrid itself,
e.g. by using the DataGridView.CellValueChanged Event.
Another solution is for you to expand the ObservableCollection class.
Have a look here : https://stackoverflow.com/a/5256827/3042778
I have several properties that return values dependent on another property's value. What is the best way to update the UI bound to a dependent property? See example below, how would you update a UI element bound to the TotalQuantity property of the Parent object when a Quantity in the Children collection changes?
public class Child : INotifyPropertyChanged
{
private int quantity;
public int Quantity
{
get
{
return quantity;
}
set
{
quantity = value;
OnPropertyChanged("Quantity");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
if (this.PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, e);
}
}
}
public class ParentObject : INotifyPropertyChanged
{
private ObservableCollection<Child> children;
public ObservableCollection<Child> Children
{
get
{
return children;
}
set
{
children = value;
OnPropertyChanged("Children");
}
}
public int TotalQuantity
{
get
{
return Children.Sum(c => c.Quantity);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
if (this.PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, e);
}
}
}
When you instantiate your ObservableCollection, you need to subscribe to the CollectionChanged event.
Children.CollectionChanged += Children_CollectionChanged;
This will be called when an item is added/removed from the collection. You simply need to notify that TotalQuantity has changed.
void Children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged("TotalQuantity");
}
In the case where you need to update the TotalQuantity property on the UI whenever a child changes, then you simply need to subscribe to the child's PropertyChanged event.
So, when you add an item to the collection, subscribe to the event:
Child myChild = new Child(); //Just an example, but you get the idea
myChild.PropertyChanged += myChild_PropertyChanged;
And in your event handler, you can test to see what property has changed.
void myChild_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Quantity")
OnPropertyChanged("TotalQuantity");
}
Or, you could just call OnPropertyChanged without checking if you're a badass. But I wouldn't recommend it just in case your model gains more properties in future.
Don't forget to unsubscribe the event before you remove the child from the collection.
I am having trouble with getting a total count calculation to run when the value of one of the items in a collection is changed. I do have the INotifyPropertChanged event on the class and the collection is an ObservableCollection<T>. I have also tried a BindingList<T> without luck.
public class Inventory
{
public ObservableCollection<Item> Items { get; set; }
public int TotalCount {
get { return Items.Select(i => i.Count).Sum(); }
}
}
public class Item : INotifyPropertyChanged {
private int count;
public int Count {
get { return count; }
set {
count = value;
NotifyPropertyChanged("Count");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName) {
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
My next though was to register an event handler for each item that is added to the collection, but I am not quite sure how to manage the events that may have already been registered. It seems I am missing something simple here, can anyone point it out?
BTW, I am data binding in WPF to this class.
You need to handle CollectionChanged event, subscribe to newly added items and unsubscribe from deleted items. This is a tedious task to do it every time, so you can subclass ObservableCollection and override OnCollectionChanged.
For an example implementation, see GitHub/Alba.Framework/NotifyingCollection(T).cs (it has some dependencies within the project, so you won't be able to copy-paste it though). There's a simpler implementation on StackOverflow somewhere, but I can't find it at the moment.
Here is the code for that "tedious task" ... first in the Inventory constructor:
Items.CollectionChanged += Items_CollectionChanged;
Then in the body of the Inventory class:
void Items_CollectionChanged(object s, NotifyCollectionChangedEventArgs e) {
if (e.OldItems != null)
foreach (INotifyPropertyChanged item in e.OldItems)
item.PropertyChanged -= ItemPropertyChanged;
if (e.NewItems != null)
foreach (INotifyPropertyChanged item in e.NewItems)
item.PropertyChanged += ItemPropertyChanged;
}
void ItemPropertyChanged(object s, PropertyChangedEventArgs e) {
if (e.PropertyName == "Count")
NotifyPropertyChanged("TotalCount");
}
Here is my scenarion:
I have a GridControl bound to a BindingList. At first what I was doing was creating a worker thread and access the BindingList directly, but this was throwing a "Cross-thread operation detected", so I followed the guide here:
http://www.devexpress.com/Support/Center/p/AK2981.aspx
By cloning the original BindingList into the worker thread and changing that one, I got the desired effect. However, I recently implemeneted the INotifyPropertyChanged into the object that is held into the BindingList, and I started getting the error again.
My guess is that the GridView is still listening to the INotifyPropertyChanged from the object.
How can I fix this?
My class:
public class Proxy : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
If you are manipulating the UI from outside of the UI thread (such as from a worker thread), then you need to rejoin the UI thread. You can do this by calling Invoke on the UI control. You can test if this is required by using InvokeRequired.
The pattern typically used is this:
public void ChangeText(string text)
{
if(this.InvokeRequired)
{
this.Invoke(new Action(() => ChangeText(text)));
}
else
{
label.Text = text;
}
}
In your case the UI is being manipulated as a result of INotifyPropertyChanged, so you need to make sure that either you always modify your entity on the UI thread (using the above technique), or use a generic asynchronous INotifyPropertyChanged helper. This is a wrapper around the item being bound. It uses the above technique to ensure the ChangeProperty event fires on the UI thread.
Here's a very crude example of a proxy for an Entity class. This ensures that the property change event rejoins the UI thread, and keeps the entity itself unmodified. Obviously you'll probably want to implement this more generically using DynamicObject for instance.
public class NotificationHelper : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly ISynchronizeInvoke invokeDelegate;
private readonly Entity entity;
public NotificationHelper(ISynchronizeInvoke invokeDelegate, Entity entity)
{
this.invokeDelegate = invokeDelegate;
this.entity = entity;
entity.PropertyChanged += OnPropertyChanged;
}
public string Name
{
get { return entity.Name; }
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
if (invokeDelegate.InvokeRequired)
{
invokeDelegate.Invoke(new PropertyChangedEventHandler(OnPropertyChanged),
new[] { sender, e });
return;
}
PropertyChanged(this, e);
}
}
}
I took a similar approach to TheGateKeeper's eventual solution. However I was binding to many different objects. So I needed something a bit more generic. The solution was to create a wrapper that implemented also ICustomTypeDescriptor. In this way, I do not need to create wrapper properties for everything that can be displayed in the UI.
public class SynchronizedNotifyPropertyChanged<T> : INotifyPropertyChanged, ICustomTypeDescriptor
where T : INotifyPropertyChanged
{
private readonly T _source;
private readonly ISynchronizeInvoke _syncObject;
public SynchronizedNotifyPropertyChanged(T source, ISynchronizeInvoke syncObject)
{
_source = source;
_syncObject = syncObject;
_source.PropertyChanged += (sender, args) => OnPropertyChanged(args.PropertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged == null) return;
var handler = PropertyChanged;
_syncObject.BeginInvoke(handler, new object[] { this, new PropertyChangedEventArgs(propertyName) });
}
public T Source { get { return _source; }}
#region ICustomTypeDescriptor
public AttributeCollection GetAttributes()
{
return new AttributeCollection(null);
}
public string GetClassName()
{
return TypeDescriptor.GetClassName(typeof(T));
}
public string GetComponentName()
{
return TypeDescriptor.GetComponentName(typeof (T));
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(typeof (T));
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(typeof (T));
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(typeof(T));
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(typeof (T), editorBaseType);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(typeof(T));
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(typeof (T), attributes);
}
public PropertyDescriptorCollection GetProperties()
{
return TypeDescriptor.GetProperties(typeof (T));
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return TypeDescriptor.GetProperties(typeof(T), attributes);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return _source;
}
#endregion ICustomTypeDescriptor
}
Then in the Ui, I bind to this wrapper using something like:
private void CreateBindings()
{
if (_model == null) return;
var threadSafeModel = new SynchronizedNotifyPropertyChanged<MyViewModel>(_model, this);
directiveLabel.DataBindings.Add("Text", threadSafeModel, "DirectiveText", false, DataSourceUpdateMode.OnPropertyChanged);
}
MyViewModel has a "DirectiveText" property and implements INotifyPropertyChanged with no special consideration to threading or to the view classes.
I subclassed BindingList so I could check for a required Invoke. This way my business objects do not have a reference to the UI.
public class InvokingBindingList<T> : BindingList<T>
{
public InvokingBindingList(IList<T> list, Control control = null) : base(list)
{
this.Control = control;
}
public InvokingBindingList(Control control = null)
{
this.Control = control;
}
public Control Control { get; set; }
protected override void OnListChanged(ListChangedEventArgs e)
{
if (Control?.InvokeRequired == true)
Control.Invoke(new Action(() => base.OnListChanged(e)));
else
base.OnListChanged(e);
}
}
Just in case someone has run into the same problem... I managed to fix it after some hours. Here is what I did:
Basically the problem was that the object implementing INotifyPropertyChanged was living in a worker thread, and this causes problems when accessing the UI thread.
So what I did was pass a reference to the object that needs to be updated to the INotifyPropertyChanged object, and then use invoke on it.
Here is what it looks like:
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
//If the Proxy object is living in a non-UI thread, use invoke
if (c != null)
{
c.BeginInvoke(new Action(() => handler(this, new PropertyChangedEventArgs(name))));
}
//Otherwise update directly
else
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
//Use this to reference the object on the UI thread when there is need to
public Control C
{
set { c = value; }
}
From the thread, all I did was:
prox.c = this;
//Logic here
prox.c = null;
Hope this helps someone!!
This is an example code:
public class MyParent : INotifyPropertyChanged
{
List<MyChild> MyChildren;
public bool IsChanged
{
get
{
foreach (var child in MyChildren)
{
if (child.IsChanged) return true;
}
return false;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
public class MyChild : INotifyPropertyChanged
{
private int _Value;
public int Value
{
get
{
return _Value;
}
set
{
if (_Value == value)
return;
_Value = value;
RaiseChanged("Value");
RaiseChanged("IsChanged");
}
}
private int _DefaultValue;
public int DefaultValue
{
get
{
return _DefaultValue;
}
set
{
if (_DefaultValue == value)
return;
_DefaultValue = value;
RaiseChanged("DefaultValue");
RaiseChanged("IsChanged");
}
}
public bool IsChanged
{
get
{
return (Value != DefaultValue);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
Let's say I now have two instances of my classes, one as myParent, and the other as myChild.
I have two visual elements, that each have a property bound to the IsChnaged property of my instances; ElementA bound to myParent.IsChanged and ElementB bound to myChild.IsChanged.
When myChild.Value differs from its default value, the myChild.IsChanged is set to true and the ElementB is updated accordingly.
What I need is when either of the myParent children (which here is only one) have their IsChanged value set to true, its own (the parent's) IsChanged value be set to true and its corresponding
element (ElementA here) be updated accordingly.
The myParent.IsChanged is only read once (when the binding is set) and it has no sense about its children changing. Where should i put the RaiseChanged("IsChanged") for MyParent? How can I let the parent know when its children have changed?
Thanks in advance
INotifyPropertyChanged has already provided the mechanism for you: the PropertyChanged event. Just have the parent add a handler to its children's PropertyChanged, and then in that handler call RaiseChanged("IsChanged");
Also, you may want to put the INotifyPropertyChanged implementation in a base class, and have your (what appear to be) ViewModels inherit from that. Not required for this option, of course, but it will make the code a little cleaner.
Update: In the parent object:
// This list tracks the handlers, so you can
// remove them if you're no longer interested in receiving notifications.
// It can be ommitted if you prefer.
List<EventHandler<PropertyChangedEventArgs>> changedHandlers =
new List<EventHandler<PropertyChangedEventArgs>>();
// Call this method to add children to the parent
public void AddChild(MyChild newChild)
{
// Omitted: error checking, and ensuring newChild isn't already in the list
this.MyChildren.Add(newChild);
EventHandler<PropertyChangedEventArgs> eh =
new EventHandler<PropertyChangedEventArgs>(ChildChanged);
newChild.PropertyChanged += eh;
this.changedHandlers.Add(eh);
}
public void ChildChanged(object sender, PropertyChangedEventArgs e)
{
MyChild child = sender as MyChild;
if (this.MyChildren.Contains(child))
{
RaiseChanged("IsChanged");
}
}
You don't actually have to add anything to the child class, since it is already raising the correct event when it changes.
Doing this kind of communication can be tricky, especially if you want to avoid memory leaks due to the event handlers that you hook up. There is also the case of handling items that are added / removed from the collection.
I've really enjoyed the power and simplicity of the Continuous LINQ project on codeplex. It has some very rich features for setting up "Reactive Objects", "Continuous Values", and "Continuous Collections". These let you define your criteria as a Linq expression and then let the CLINQ library keep the underlying values up to date in real time.
In your case, you could set up the parent with a ContinuousFirstOrDefault() linq query that watched for any child where "IsChanged == true". As soon as a child sets the value to true and raises PropertyChanged, the continuous value will detect the change and raise a corresponding PropertyChanged in the parent.
The benefits:
Weak references and weak events are used to prevent the event handlers in the parent from locking the child in memory. It can get very messy to add / remove these handlers from all the children.
You can declare the dependency in the parent without need to make special changes in the child or make the child aware of the parent. Rather, the child just needs to properly implement INotifyPropertyChanged. This puts the "logic" close to the object that cares, rather than spreading event craziness and inter-dependencies all over the code.
Here's what the code might look like:
public class MyParent : INotifyPropertyChanged
{
private ObservableCollection<MyChild> _MyChildren;
private ContinuousValue<MyChild> _ContinuousIsChanged = null;
public MyParent()
{
_MyChildren = new ObservableCollection<MyChild>();
// Creat the ContinuousFirstOrDefault to watch the MyChildren collection.
// This will monitor for newly added instances,
// as well as changes to the "IsChanged" property on
// instances already in the collection.
_ContinuousIsChanged = MyChildren.ContinuousFirstOrDefault(child => child.IsChanged);
_ContinuousIsChanged.PropertyChanged += (s, e) => RaiseChanged("IsChanged");
}
public ObservableCollection<MyChild> MyChildren
{
get { return _MyChildren; }
}
public bool IsChanged
{
get
{
// If there is at least one child that matches the
// above expression, then something has changed.
if (_ContinuousIsChanged.Value != null)
return true;
return false;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
public class MyChild : INotifyPropertyChanged
{
private int _Value;
public int Value
{
get
{
return _Value;
}
set
{
if (_Value == value)
return;
_Value = value;
RaiseChanged("Value");
RaiseChanged("IsChanged");
}
}
private int _DefaultValue;
public int DefaultValue
{
get
{
return _DefaultValue;
}
set
{
if (_DefaultValue == value)
return;
_DefaultValue = value;
RaiseChanged("DefaultValue");
RaiseChanged("IsChanged");
}
}
public bool IsChanged
{
get
{
return (Value != DefaultValue);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
The above code sets up the ContinuousFirstOrDefault in the constructor so that it is always monitoring. However, in some cases you can optimize this by lazily instantiating the ContinuousFirstOrDefault only when the getter for "IsChanged" is called. That way you don't start monitoring for changes until you know that some other piece of code actually cares.
You can simplify things for yourself by storing your children in an ItemObservableCollection<T>, as discussed in this answer. That would allow you to do this:
private ItemObservableCollection<MyChild> children;
public MyParent()
{
this.children = new ItemObservableCollection<MyChild>();
this.children.ItemPropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
{
if (string.Equals("IsChanged", e.PropertyName, StringComparison.Ordinal))
{
this.RaisePropertyChanged("IsChanged");
}
};
}
Something I do not see in your code sample provide is an actually reference of parent to child. It is not enough to simply have interface to communicate through, but you must also create the reference. Something like myChild.parent = this; followed by the binding of the event handlers across the channel, in the "parent" property of the child object it would look like:
public INotifyPropertyChanged parent
{
get{return _parent;}
set
{
_parent = value;
this.PropertyChanged += _parent.RaiseChanged();
}
}
I don't have enough context to perfect this code for you but this should move you in the right direction.