Fluent Silverlight – Binding dependency properties to model properties
Please view the table of content of this series for reference.
In my previous posts (here and here) I discussed how one can build a fluent API for the definition of objects. In the context of Fluent Silverlight these objects are Silverlight controls. With the aid of the expressions we want to define and instantiate controls. But we also want to show some data in the controls like text box, text block, combo box, etc. The data that we want to display is defined in what we call “view model”. We want to use data binding to achieve this goal.
Silverlight defines the necessary infrastructure for data binding that we want to use. To bind a dependency property of a control (e.g. the TextProperty of a text box) to a property of the data context of the view in which the textbox is defined we use the following code
var firstName = new TextBox{ Width = 100 };
var binding = new Binding("FirstName") {Mode = BindingMode.TwoWay};
firstName.SetBinding(TextBox.TextProperty, binding);</p>
In the above sample we bind the text property of the textbox to the property named FirstName of the data context of the view. The binding is two way, that is if either side of the link is changed then the other side is automatically updated.
Now I’d prefer to have a fluent API to create textbox controls for me where I can express my intent to bind certain dependency properties to properties on the view model with the aid of lambda expressions. I want to have something like this
TextBox lastName = new TextBoxExpression<ViewModel>(m => m.LastName);
</p>
the result will be the same as in the first sample but the syntax is much cleaner and concise. We use a lambda expression m => m.LastName to define the binding in an intellisense-friendly and type-safe manner. The model is defined as
public class ViewModel
{
public string LastName { get; set; }
}</p>
and the expression itself is defined like this
public class TextBoxExpression<TModel>
{
private Expression<Func<TModel, object>> textExpression;
public TextBoxExpression(Expression<Func<TModel, object>> expressions)
{
textExpression = expressions;
}
protected virtual void BuildUp(TextBox element)
{
var accessor = textExpression.GetAccessor();
var binding = new Binding(accessor.Name);
element.SetBinding(TextBox.TextProperty, binding);
}
public static implicit operator TextBox(TextBoxExpression<TModel> expression)
{
var element = new TextBox();
expression.BuildUp(element);
return element;
}
}</p>
Please note that we use LINQ expressions and not just lambda functions to define the binding. This is necessary such as that we can use the meta data of the expression to define the binding. The method GetAccessor is an extension method for our LINQ expressions. The function uses the expression to retrieve important type information. The path for the binding is then just the name of the accessor returned by the extension method. I will talk more about this helper method in my next post.
We can add more methods to our fluent API to define bindings for more dependency properties. The usage will then be like this
TextBox lastName = new TextBoxExpression<ViewModel>(m => m.LastName)
.IsReadOnly(m => m.LastNameIsReadOnly)
.Visibility(m => m.LastNameVisibility);</p>
the view model has two new properties
public class ViewModel
{
public string LastName { get; set; }
public bool LastNameIsReadOnly { get; set; }
public Visibility LastNameVisibility { get; set; }
}</p>
Remarks: the view model, as it is defined above cannot participate in a two-way data binding since it is not implementing the INotifyPropertyChanged interface. However this topic I will address in a subsequent post where I will show you how we provide our view model this necessary functionality in a AOP way.
The textbox expression contains these new and extended methods
public TextBoxExpression<TModel> IsReadOnly(Expression<Func<TModel, object>> expression)
{
isReadOnlyExpression = expression;
return this;
}
public TextBoxExpression<TModel> Visibility(Expression<Func<TModel, object>> expression)
{
visibilityExpression = expression;
return this;
}
protected virtual void BuildUp(TextBox element)
{
var accessor = textExpression.GetAccessor();
var binding = new Binding(accessor.Name);
element.SetBinding(TextBox.TextProperty, binding);
var accessor2 = isReadOnlyExpression.GetAccessor();
var binding2 = new Binding(accessor2.Name);
element.SetBinding(TextBox.IsReadOnlyProperty, binding2);
var accessor3 = visibilityExpression.GetAccessor();
var binding3 = new Binding(accessor3.Name);
element.SetBinding(TextBox.VisibilityProperty, binding3);
}</p>
One thing that should be evident is that in the BuildUp method we have a lot of repetitive code. It would be nice if we could simplify this code and make it more concise. Why not create a fluent API to define a binding? The result could be this</p> </p> </p> </p>
using (var binder = new PropertyBinder<TModel>(element))
{
binder.DependencyProperty(TextBox.TextProperty).BindTo(textExpression).Mode(BindingMode.TwoWay);
binder.DependencyProperty(TextBox.IsReadOnlyProperty).BindTo(isReadOnlyExpression);
binder.DependencyProperty(TextBox.VisibilityProperty).BindTo(visibilityExpression);
}</p>
again a step in the right direction towards better readability! The definition of this new fluent API is shown below.
public class PropertyBinder<TModel> : IPropertyBinderExpression<TModel>, IDisposable
{
private bool elementHasBeenBound;
private readonly FrameworkElement element;
private BindingInfo current;
private readonly List<BindingInfo> bindingInfos;
public PropertyBinder(FrameworkElement element)
{
this.element = element;
bindingInfos = new List<BindingInfo>();
}
public IPropertyBinderExpression<TModel> DependencyProperty(DependencyProperty dp)
{
current = new BindingInfo { DependencyProperty = dp };
bindingInfos.Add(current);
return this;
}
IPropertyBinderExpression<TModel> IPropertyBinderExpression<TModel>.BindTo(Expression<Func<TModel, object>> expression)
{
current.Expression = expression;
return this;
}
IPropertyBinderExpression<TModel> IPropertyBinderExpression<TModel>.Mode(BindingMode mode)
{
current.Mode = mode;
return this;
}
public void Bind()
{
if(elementHasBeenBound)
throw new InvalidOperationException("Binding for this control has already been setup.");
foreach (var info in bindingInfos)
{
var binding = new Binding(info.Expression.GetAccessor().Name);
if (info.Mode.HasValue) binding.Mode = info.Mode.Value;
element.SetBinding(info.DependencyProperty, binding);
}
elementHasBeenBound = true;
}
public void Dispose()
{
Bind();
}
private class BindingInfo
{
public DependencyProperty DependencyProperty;
public Expression<Func<TModel, object>> Expression;
public BindingMode? Mode { get; set; }
}
}</p>
and the interface implemented by the above class is
public interface IPropertyBinderExpression<TModel>
{
IPropertyBinderExpression<TModel> BindTo(Expression<Func<TModel, object>> expression);
IPropertyBinderExpression<TModel> Mode(BindingMode mode);
}</p>
To be able to use the nice using-syntax the property binder class implements the IDisposable interface. The implementation of the Dispose method then just calls the Bind method of the class. In the Bind method the whole bindings are executed. This method has been guarded such as that it can only be called once otherwise an exception is thrown.
Note that in my private class BindingInfo I have defined the field Mode as BindingMode? and NOT as BindingMode. This makes it easy for me to detect whether the user has set the binding mode or not when defining the binding for a dependency property.
Now if we need more functionality regarding binding we can just extend our fluent API, that is our property binder class. One scenario that comes to mind is the usage of value converters. For this case we can just add another method to the fluent API
public IPropertyBinderExpression<TModel> WithValueConverter<TConverter>()
where TConverter : IValueConverter, new()
{
current.ValueConverter = new TConverter();
return this;
}</p>
the BindingInfo class has an additional field
public IValueConverter ValueConverter;
</p>
and the Bind method contains this additional statement
if (info.ValueConverter != null) binding.Converter = info.ValueConverter;</p>
In my next post I will explain the code we use to extract the needed meta information from a given lambda expression.