Monday, August 16, 2010

Silverlight - Property Value Synchronization between Two View Models

Recently I had been confronted with a design issue to keep two property values in two different View Models in Sync.



It is a simple issue, where you can subscribe to PropertyChange notifications and detect whenever interested property changes in Object1, set AnotherProperty's value in Another Object. And vice versa. To avoid recursive infinite loop situation, you can keep a flag which tracks direction of change and avoids this infinite loop.

But I wanted to solve this by keeping following constratints

1. I need to reuse this syncing logic wherever required in future.
2. Loosely Coupled.
3. No reflection, I want compile time support to detect any errors or most of them.
4. Should support FluentAPI for better usability.

Few assumption I made are
4. Both objects implements INotifyPropertyChanged event which raises event change notifications whenever property is changed.

Before discussing code, once developed you can use it like following to keep both objects in Sync.


//ViewModel which has a property
Class1 orgViewModel = new Class1();
//Another ViewModel which has another property.
Class2 anotherViewModel = new Class2();
//Sync API will be exposed by this SyncClass Which takes ViewModel types and property type.
SyncClass SyncClass = new SyncClass();

//Configure ViewModel with predicate to detect when property is changed and provide functions to read and write property values.
SyncClass.MonitorClass(orgViewModel, (propname) => { return propname == "MyVal"; })
.SetGetValue(()=>orgViewModel.MyVal)
.SetSetValue((i)=>orgViewModel.MyVal=i);
//Configure another ViewModel with predicate to detect when property is changed and provide functions to read and write property values.
SyncClass.MonitorAnotherClass(anotherViewModel, (propname) => { return propname == "AnotherVal"; })
.SetGetValue(()=>anotherViewModel.AnotherVal)
.SetSetValue((i1)=>anotherViewModel.AnotherVal=i1);
//Start Synchronization.
SyncClass.StartSync();

Once you configure you view models and property in above manner SynClass will auto sync two properties.

Code Listing

delegate void ValueChanged(object sender,EventArgs e);

class UsingClass
{
bool IsOriginator = false;
public event ValueChanged ObjectValueChanged;

T _MonitorObject;
Func _GetValue;
Action _SetValue;
Func<string,bool> _IsPropChanged;
public UsingClass(T MonitorObject,Func<string,bool> IsPropertyChanged)
{
_IsPropChanged = IsPropertyChanged;
_MonitorObject = MonitorObject;
INotifyPropertyChanged propChanged = _MonitorObject as INotifyPropertyChanged;
if (propChanged != null)
{
propChanged.PropertyChanged += (o1, e1) =>
{
if (_IsPropChanged(e1.Proeprtyname))
{
this.IsOriginator = true;
if (this.ObjectValueChanged != null)
{
this.ObjectValueChanged(this, new EventArgs());
}
}
};
}

}
public UsingClass SetGetValue(Func GetValue)
{
_GetValue=GetValue;
return this;
}
public T1 GetValue()
{
return _GetValue();
}
public void SetSetValue(Action SetValue)
{
this._SetValue = SetValue;
}
public void SetValue(T1 val1)
{
if (!IsOriginator)
{
_SetValue(val1);
}
else
{
IsOriginator =
false;
}
}
}

class SyncClass
{
UsingClass Object1;
UsingClass Object2;
public UsingClass MonitorClass(T MonitorObject,Func<string,bool> CheckPropertyChanged)
{
Object1 =
new UsingClass(MonitorObject,CheckPropertyChanged);
return Object1;

}
public UsingClass MonitorAnotherClass(T1 MonitorAnotherObject, Func<string, bool> CheckPropertyChanged)
{
Object2 =
new UsingClass(MonitorAnotherObject, CheckPropertyChanged);
return Object2;
}
public void StartSync()
{
Object1.ObjectValueChanged += (o, e) =>
{
Object2.SetValue(Object1.GetValue());
};
Object2.ObjectValueChanged += (o1, e1) =>
{
Object1.SetValue(Object2.GetValue());
};
}

}

I prepared a sample in Console Application which will be easy to execute and see how its working.

Click here to download sample application. You need VS 2010 to open this solution.

Hope you enjoyed this code crunch..

7 comments:

Michael said...

If Both View Models are on the screen at the same time I use this method:

http://www.codeproject.com/KB/silverlight/VMCommunication.aspx

Is there a situation that your solution covers that is not covered that that method?

Kiran Kumar G said...

You wrote an intresting article. We are using some sort of similar method you had explained. We ran into a situation that few property values in two viewmodels(in general two classes implementing INotifyPropertyChanged) event need to be in sync. I am addressing scenario of synching two properties in two view models and those are not necessarily master and child instead they even can be siblings, and both might need to exist without another.
Thanks for your comment.

Anonymous said...

sample application

download link 404 not found

Kiran Kumar G said...

I updated link location in article.

"http://cid-b345d108a9d3464d.office.live.com/self.aspx/.Public/SyncEngine.zip"

Thank you for informing this glitch. I Appreciate it.

Kiran Kumar G said...
This comment has been removed by the author.
Leo said...

Hi Kumar, what happens if I have more than 1 viewmodel listening to the Subject (Observer pattern)?

You're only implementing T,T1. Do we have to create our own events still or we could just add more T2, T3 and modify your stuff? I think if we modified your existing code then that would take more code to do rather than creating own event subscribers

Kiran Kumar G said...

Hi Leo, appreciate your comment.

Observer pattern is ideal when you want to notify changes of subject (Originator) to Observers ( targets). Upon what events notification need to be raised is left to discretion of subject and upon occurence of those notifications what needs to be done by Observers is left to Observers code. To implement this observer pattern subject & observer both need to have code implementing this pattern.

But the scenario I tried to address here is specific to syncing properties between classes, i.e I know before hand that when a property is changed I need to get notifications, INotifyPropertyChanged supports those notifications (Subject in observer pattern is already supported/implemented by all classes). And also I know that Observer(target) need to sync it's respective property with new value.
As this is common pattern I see across classes I dont want to repeat same code across observers. I abstracted this to a separate class which listens to property change notifications and updates target classes properties in a generic way. Instead of setting properties, you can also do other things, as Sync class takes functions which need to be called when property changes occur in subjects.

Current design supports multiple target classes scenario(syncing multiple view models). All you need to do is to create Multiple sync objects for each source(subject/viewmodel) and target (observer/viewmodel) combination. Think of this as analogus to Binding object which sync two properties. At the time of writing this blog, Binding object supports only Dependency properties as targets. Otherwise sync class would have used Binding object internally to sync properties. But I think now i.e SL 5.0, Binding object supports any properties, I didn't verified this for sure.