The biggest problem with data-binding is the requirement to implement the INotifyPropertyChange interface. There are dozens of solutions out there that try to simplify the process with techniques ranging from parsing lambda expressions and walking the stack frame to using IL weaving to modify classes at compile time. The most popular approach is to derive from a base class and call a base method to handle the event.
The frustration often comes from mapping data objects that don't implement the interface to view models that do. Wouldn't it be nice to have a simple, straightforward way to manage this without duplicating properties and writing tedious mapping code? It turns out there is.
For this particular problem, I started with the solution. Given a model, say, a ContactModel, I wanted to be able to do this:
public PropertyNotifier<ContactModel> Contact { get; set; }
public void SetContact(ContactModel contact)
{
Contact = new PropertyNotifier(contact);
}
In other words, a nice type would wrap the object and expose it with full property change glory, and little effort on my part.
So, where to start? To begin with I created a simple base class that allows for property change notification. For now I'm going to ignore some of the interesting ways to actually call the notification.
public abstract class BaseNotify : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChange(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This class is really all you need to have your own MVVM framework. Next is some heavy lifting. Because the solution uses dynamic types and heavy reflection, it will not work on Windows Phone 7. It will, however, work with Silverlight 4, and there is perhaps an even more elegant solution to be derived from this work in Silverlight 5 by adding ICustomTypeProvider to the mix.
How can this create a bindable object in Silverlight 4 or 5? First, create the shell of the view model. It should create the proxy class with property change notification. It should allow you to pass in a template and have that template mirrored by the proxy. Ideally, it should be easy to get the template back out (i.e. yank out the original model to send on its way after it has been modified). Here's the start:
public class PropertyNotifier<TTemplate> : BaseNotify where TTemplate : class
{
public TTemplate Instance { get; set; }
public INotifyPropertyChanged NotifyInstance { get; set; }
}
Simple enough. Not sure if the notifier instance really deserves a public setter... but it is there for now. Now comes the fun part!
The type must be created on the fly, so it needs a dynamic assembly and module to host the type. There is no sense in creating a new one for each type, so these can be static properties that live on the notifier. There should also be a type dictionary to map the source type to the proxy type (to avoid recreating the proxy type) and a mutex to avoid collisions with the dictionary (thread safety).
private static readonly ModuleBuilder _builder;
private static readonly Dictionary<Type, Type> _types = new Dictionary<Type, Type>();
private static readonly object _mutex = new object();
static PropertyNotifier()
{
var assemblyName = new AssemblyName("PropertyNotifier");
var currentDomain = AppDomain.CurrentDomain;
var builder = currentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
_builder = builder.DefineDynamicModule("PropertyChangeModels");
}
If you are afraid of collisions you can give the assembly a creative name like a GUID or append random text and strings if you like. This makes it nice and readable in the debugger. The assembly is created in the current domain and the module defined to host dynamic types.
Without understanding the details of how the type is actually built, you can still wire in the constructor and put in a placeholder, like this:
public PropertyNotifier()
{
Monitor.Enter(_mutex);
try
{
if (!_types.ContainsKey(typeof (TTemplate)))
{
_types.Add(typeof(TTemplate), _BuildType());
}
}
finally
{
Monitor.Exit(_mutex);
}
var type = _types[typeof (TTemplate)];
NotifyInstance = (INotifyPropertyChanged)Activator.CreateInstance(type);
}
public PropertyNotifier(TTemplate instance) : this()
{
Instance = instance;
}
If the type has not been created, it is built. An overloaded constructor will take in an instance and then set it.
Next, assuming the type is built (we'll get into the gory details later), a few methods will help with mapping properties. First, define a delegate for the getter and setter. Then, define a dictionary of dictionaries. The key to the outer dictionary will be the type, and the inner dictionary will map the property name to the getter or setter method.
private delegate void Setter(object target, object value);
private delegate object Getter(object target);
private static readonly Dictionary<Type, Dictionary<string,Setter>> _setterCache = new Dictionary<Type, Dictionary<string,Setter>>();
private static readonly Dictionary<Type, Dictionary<string,Getter>> _getterCache = new Dictionary<Type, Dictionary<string, Getter>>();
The helper methods will inspect the type for the property information and use reflection to grab the getter or setter. They will then store these in the cache for future look ups:
private static object _GetValue(object target, string property)
{
Monitor.Enter(_mutex);
try
{
if (!_getterCache[target.GetType()].ContainsKey(property))
{
var method = target.GetType().GetProperty(property).GetGetMethod();
_getterCache[target.GetType()].Add(property, obj => method.Invoke(obj, new object[] {}));
}
}
finally
{
Monitor.Exit(_mutex);
}
return _getterCache[target.GetType()][property](target);
}
private static void _SetValue(object target, string property, object value)
{
Monitor.Enter(_mutex);
try
{
if (!_setterCache[target.GetType()].ContainsKey(property))
{
var method = target.GetType().GetProperty(property).GetSetMethod();
_setterCache[target.GetType()].Add(property, (obj,val) => method.Invoke(obj, new[] { val }));
}
}
finally
{
Monitor.Exit(_mutex);
}
_setterCache[target.GetType()][property](target, value);
}
You can call the first with an object and the property name to get the value. Call the second with the object, the property name, and the property value to set it. Subsequent calls will not require inspection of the properties as the methods will be cached to call directly.
So the proxy still hasn't been built yet, but that's more complicated. First, get the simple stuff out of the way. When the instance is passed in, automatically wire the properties to the proxy. When the proxy is created, hook into the property change notificaton to automatically push changes back to the original instance:
private TTemplate _instance;
// original object
public TTemplate Instance
{
get { return _instance; }
set
{
_instance = value;
NotifyInstance = (INotifyPropertyChanged)Activator.CreateInstance(_types[typeof (TTemplate)]);
foreach(var p in typeof(TTemplate).GetProperties())
{
var sourceValue = _GetValue(value, p.Name);
_SetValue(NotifyInstance, p.Name, sourceValue);
}
RaisePropertyChange("Instance");
}
}
// proxy object
private INotifyPropertyChanged _notifyInstance;
public INotifyPropertyChanged NotifyInstance
{
get { return _notifyInstance; }
set
{
if (_notifyInstance != null)
{
_notifyInstance.PropertyChanged -= _NotifyInstancePropertyChanged;
}
_notifyInstance = value;
_notifyInstance.PropertyChanged += _NotifyInstancePropertyChanged;
RaisePropertyChange("NotifyInstance");
}
}
void _NotifyInstancePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (Instance == null)
{
return;
}
if (_setterCache[typeof (TTemplate)].ContainsKey(e.PropertyName))
{
_SetValue(Instance, e.PropertyName, _GetValue(NotifyInstance, e.PropertyName));
}
}
OK, all of the proxy and marshalling is in place. Now it's time to build the type! First step is to define the type name and set the parent so it derives from the BaseNotify object:
private static Type _BuildType()
{
var typeBuilder =
_builder.DefineType(string.Format("{0}Notifier", typeof (TTemplate).Name), TypeAttributes.Class | TypeAttributes.Public);
typeBuilder.SetParent(typeof(BaseNotify));
}
Next, grab a handle to the property change method from the base class and set up a dictionary to cache the getters and setters on the template type:
var propertyChange = typeof(BaseNotify).GetMethod("RaisePropertyChange", new[] { typeof(string)});
_getterCache.Add(typeof(TTemplate), new Dictionary<string, Getter>());
_setterCache.Add(typeof(TTemplate), new Dictionary<string, Setter>());
Now comes the fun part, looping through the properties and caching the getters/setters (this is from the template):
foreach(var p in typeof(TTemplate).GetProperties())
{
var getterInfo = p.GetGetMethod();
_getterCache[typeof(TTemplate)].Add(p.Name, obj=>getterInfo.Invoke(obj, new object[]{}));
var setterInfo = p.GetSetMethod();
_setterCache[typeof(TTemplate)].Add(p.Name, (obj,value)=>setterInfo.Invoke(obj, new[]{value}));
}
Each property has a private backing field, so create the field on the proxy type:
var field = typeBuilder.DefineField(string.Format("_{0}", p.Name), p.PropertyType, FieldAttributes.Private);
Next, define the property.
var property = typeBuilder.DefineProperty(p.Name, PropertyAttributes.HasDefault, p.PropertyType,null);
The property needs a getter. This is where the code is a little more interesting becaues it requires emitting IL code. Fortunately, you can build a sample class and use ILDASM.EXE to disassemble it and learn what the proper op codes are. Here is the getter method:
var getter = typeBuilder.DefineMethod(string.Format("get_{0}", p.Name),
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
p.PropertyType, Type.EmptyTypes);
var getterCode = getter.GetILGenerator();
getterCode.Emit(OpCodes.Ldarg_0);
getterCode.Emit(OpCodes.Ldfld, field);
getterCode.Emit(OpCodes.Ret);
Next is the setter method. The setter method has some extra code that loads the property name and then calls the property change method. That is why the handle to the method was captured earlier.
var setter = typeBuilder.DefineMethod(string.Format("set_{0}", p.Name),
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig, null,
new[] { p.PropertyType });
var setterCode = setter.GetILGenerator();
setterCode.Emit(OpCodes.Ldarg_0);
setterCode.Emit(OpCodes.Ldarg_1);
setterCode.Emit(OpCodes.Stfld, field);
// property change
// put the property name on the stack
setterCode.Emit(OpCodes.Nop);
setterCode.Emit(OpCodes.Ldarg_0);
setterCode.Emit(OpCodes.Ldstr, p.Name);
setterCode.Emit(OpCodes.Call, propertyChange);
setterCode.Emit(OpCodes.Nop);
setterCode.Emit(OpCodes.Ret);
Now that the methods have been generated, they must be attached to the property:
property.SetGetMethod(getter);
property.SetSetMethod(setter);
That's the hard part! The easy part is to define a default constructor (calls down to the base) and create the actual type. Remember, this is the method called in the constructor so the type is returned and stored in the dictionary, then the activator is used to create the instance. Also, go ahead and set up the getter and setter cache:
typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);
var type = typeBuilder.CreateType();
_getterCache.Add(type,new Dictionary<string, Getter>());
_setterCache.Add(type,new Dictionary<string, Setter>());
return type;
Believe it or not, that's what it takes to build a proxy, assuming the base class contains simple properties and no complex nested types or structures. Here's a simple template to test the proxy with:
public class ContactTemplate
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Here's a view model that is based on the template. It uses the property notifier to wrap the properties with property change notification. It also creates a default template in the constructor just to give you some information to work with when the application runs:
public class ContactViewModel : PropertyNotifier<ContactTemplate>
{
public ContactViewModel()
{
var template = new ContactTemplate
{
Id = 1,
FirstName = "Jeremy",
LastName = "Likness"
};
Instance = template;
}
}
Now some XAML to bind it all together:
<Grid x:Name="LayoutRoot" Background="White">
<Grid.DataContext>
<ViewModels:ContactViewModel/>
</Grid.DataContext>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="First Name: "/>
<TextBlock Text="Last Name: " Grid.Row="1"/>
<TextBlock Text="Edit First Name: " Grid.Row="2"/>
<TextBlock Text="{Binding NotifyInstance.FirstName}" Grid.Column="1"/>
<TextBlock Text="{Binding NotifyInstance.LastName}" Grid.Row="1" Grid.Column="1"/>
<TextBox Text="{Binding NotifyInstance.FirstName,Mode=TwoWay}" Grid.Row="2" TextChanged="TextBox_TextChanged" Grid.Column="1" Width="200"/>
</Grid>
When you run the application, you'll find the property change works just fine. Now, with this helper class, anytime you need to take a simple data object and implement property change, you can just wrap it in the property notifier and bind to the InstanceNotifier property. This works perfectly well in Silverlight 4.