I have a object that is created through deserialization of some XML. I used Visual Studio’s tool to generate the XSD from the vendor mockup XML. Then using the XSD tool I got classes from them. I set the XSD tool so that it would make the generated classes be INotifyPropertyChanged.
Now I’m trying to display this object in a “tab” on my WPF application. I want a “dirty” indicator whenever someone makes a change. The issue is that this object, being generated classes from generated XSD is not the prettiest structure. The display for this object is does not mimic its data structure. I thought of creating display objects (not using MMVM ATM) and using those to bind my changes to and then persist those changes to the object as a data object. I would essentially just throw out my current display to do this since I’m now just adding a check for if there were edits.
My thought is to reflect across the object and set the PropertyChanged event for every property I come across (walking through the graph of objects and properties). My reflection fu is failing me and I’m also probably making some short-sighted mistakes.
Here is the code I’ve written thus far:
void SetupPropertyChanged(INotifyPropertyChanged component)
{
component.PropertyChanged += CAMConfig_PropertyChanged;
Type componentType = component.GetType();
foreach (PropertyInfo info in componentType.GetProperties())
{
Type[] types =
info.PropertyType.FindInterfaces((a, b) => { return a.ToString() == b.ToString(); }, typeof(INotifyPropertyChanged));
bool isINotify = types.Contains(typeof(INotifyPropertyChanged));
if (isINotify)
this.SetupPropertyChanged((INotifyPropertyChanged)info.GetValue(component, new object[] { }));
}
}
I think I’m running into Observable collection property type issues as its throwing an exception when I’m traversing my object. It also just hit me that I don’t know if this object structure would have circular references.
Can someone help me work out this code so that I can traverse the object graph. Right now I’m not too concerned about the possibility of circular references, but if a solution presents itself that prevents that situation it would be very, very helpful!
Based on Karel’s answer I’ve created this “helper” class:
public static class NotifyPropertyChangedHelper
{
public delegate void ChangeOccuredHandler(object sender);
public static void SetupPropertyChanged(INotifyPropertyChanged component, ChangeOccuredHandler changedHandler)
{
SetupPropertyChanged(new List<object>(), component, changedHandler);
}
static void SetupPropertyChanged(IList<object> closed, INotifyPropertyChanged component, ChangeOccuredHandler changedHandler)
{
if (closed.Contains(component)) return; // event was already registered
closed.Add(component); //adds the property that is to be processed
//sets the property changed event if the property isn't a collection
if (!(component is INotifyCollectionChanged))
component.PropertyChanged += (sender, e) =>
{
changedHandler(sender);
};
/*
* If the component is an enumerable there are two steps. First check to see if it supports the INotifyCollectionChanged event.
* If it supports it add and handler on to this object to support notification. Next iterate through the collection of objects
* to add hook up their PropertyChangedEvent.
*
* If the component isn't a collection then iterate through its properties and attach the changed handler to the properties.
*/
if (component is IEnumerable<object>)
{
if (component is INotifyCollectionChanged)
{
//((INotifyCollectionChanged)component).CollectionChanged += collectionHandler;
((INotifyCollectionChanged)component).CollectionChanged += (sender, e) =>
{
changedHandler(sender);
};
}
foreach (object obj in component as IEnumerable<object>)
{
if (obj is INotifyPropertyChanged)
SetupPropertyChanged(closed, (INotifyPropertyChanged)obj, changedHandler);
}
}
else
{
foreach (PropertyInfo info in component.GetType().GetProperties())
{
var propertyValue = info.GetValue(component, new object[] { });
var inpc = propertyValue as INotifyPropertyChanged;
if (inpc == null) continue;
SetupPropertyChanged(closed, inpc, changedHandler);
}
}
}
}
Keep a list of properties (components) for which you’ve already registered the event and make your function recursive. And you can cast component to
INotifyPropertyChangeddirectly.