Fluent Silverlight – Part 2 – Binding Properties
Introduction
After having introduced the new Fluent Silverlight framework in part 1 it is now time to dig a little bit deeper and discuss some implementation details. This time I’ll have a look into the details how we bind a property of a Silverlight control to a view model property.
As a reaction on our first post we have been asked why we prefer to define the binding in code rather than in XAML. We have different reasons for doing so. First of all we want to get rid of XAML as much as possible. Our application has a rather specific UI. The UI is hierarchical and highly dynamical. It’s not the type of UI one typically designs with tools like Microsoft Blend. Another reason is that XAML is not really wrist friendly and one defines the binding in XAML then one has to use a lot of “magic strings” and thus we have no compile time support regarding the correctness of the binding.
Binding Properties
We want to be able to bind specific properties of a Silverlight control to a property exposed by the view model. The binding shall be defined with a lambda expression and NOT with “magic strings”. We are using C# as our language which is a static typed language and thus want to leverage as much as possible the possibilities that a statically typed language offers. Typically we bind the Text property of a TextBox or a TextBlock to a corresponding property of the view model. This binding is defined in the view and is similar to this
this.WithTextBox(txtUsername)
.Text(m => m.UserName)
To give a few other examples we can also e.g. bind the IsChecked property of a CheckBox or the Source property of an Image in a similar way to a respective view model property.
But not only business relevant data can or will be bound to controls like this but also other properties defining the visibility and interactability of a control can be bound. As an example take this code snippet
public override void Initialize()
{
plainText = this.TextBoxFor(m => m.TextAnswer)
.Interactable(m => m.TextAnswerInteractable)
.Visible(m => m.TextAnswerVisible)
.Watermark(m => m.TextAnswerWatermark)
.VerticalAlignment.Top()
.HorizontalAlignment.Stretch();
Content = plainText;
}</p>
In this sample besides the Text property we also bound the IsReadOnly and the Visibility properties of the TextBox to respective model properties. We even have something called Watermark which defines the text that is displayed in the TextBox if its content is empty and it does not currently have the focus.
The Visibility property of a Silverlight control is special in the sense that it is not of type boolean (that is visible = true or false) as one would naively expect but rather of type Visibility which is an enum defining the two values Visible and Collapsed. In a (view) model we rather prefer a property of type boolean to represent the fact whether a control displaying a certain business value is visible or not. Thus we need some kind of conversion happening when binding the control to the model. Fortunately the data binding used by Silverlight offers the possibility to defined so called value converters. The details of such a value converter are discussed in the next section.
Note: the visibility and interactability of a control displaying a business value is not a pure UI concern but rather determined by the given context. Part of the context are (among others) the current users rights.
Defining a custom value converter</p>
To create a custom value converter we have to implement a class that implements the interface IValueConverter. This interface has two methods Convert(…) and ConvertBack(…). We want to convert a value of type boolean to a value of type Visibility and vice versa. So the implementation is very simple and is given below
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value == null) throw new ArgumentException("Cannot convert a null value to a visibility!");
var sourceType = value.GetType();
if (!(sourceType.Equals(typeof(bool)) && targetType.Equals(typeof(Visibility))))
throw new ArgumentException(
string.Format("Cannot convert type '{0}' to '{1}'. Only can convert boolean to Visibility.",
sourceType.Name, targetType.Name));
return (bool) value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value == null) throw new ArgumentException("Cannot back convert a null value to a boolean!");
var sourceType = value.GetType();
if (!(sourceType.Equals(typeof(Visibility)) && targetType.Equals(typeof(bool))))
throw new ArgumentException(
string.Format("Cannot convert type '{0}' to '{1}'. Only can convert Visibility to boolean.",
sourceType.Name, targetType.Name));
return (Visibility) value == Visibility.Visible;
}
}</p>
most of the above code is actually just there to prevent errors. The real meat is in the line
return (bool) value ? Visibility.Visible : Visibility.Collapsed;</p>
that converts a boolean value to a corresponding Visibility; and in this line
return (Visibility) value == Visibility.Visible;</p>
which converts a Visibility back to a boolean value.
The above class can now used to e.g. bind the property UserNameIsVisible (bool) of the view model to the Visibility property of a Silverlight control (element) as follows</p>
var binding = new Binding("UserNameIsVisible") { Mode = BindingMode.OneTime };
binding.Converter = new BooleanToVisibilityConverter();
element.SetBinding(UIElement.Visibility, binding);</p>
The PropertyBinder helper class
Since we want to bind different properties of controls to the various properties of the view model we wanted to handle the intrinsic of the binding in one single place. Born was the PropertyBinder class. To define a binding we need 3 elements:
- the name of the property of the view model to which we want to bind,
- the property of the control which shall be bound and
- the binding mode (OneTime, OneWay or TwoWay).
This leads us to the following method signature
public void SetBinding(Expression<Func<TModel, object>> expression,
DependencyProperty property,
BindingMode bindingMode)
{...}</p>
The first parameter is a lambda expression and defines the name of the view model property to which we want to bind our control. From time to time we also need a custom value converter when binding a property. As a sample take the Visibility property discussed above. Thus we define an overload of the SetBinding method
public void SetBinding(Expression<Func<TModel, object>> expression,
DependencyProperty property,
BindingMode bindingMode,
IValueConverter valueConverter)
{...}</p>
The former method will call the latter one and just pass null at the place of the value converter. This leads us to the following implementation
public class PropertyBinder<TModel> where TModel : class
{
private readonly FrameworkElement element;
public PropertyBinder(FrameworkElement element)
{
this.element = element;
}
public void SetBinding(Expression<Func<TModel, object>> expression, DependencyProperty property)
{
SetBinding(expression, property, BindingMode.OneWay);
}
public void SetBinding(Expression<Func<TModel, object>> expression, DependencyProperty property, BindingMode bindingMode)
{
SetBinding(expression, property, bindingMode, null);
}
public void SetBinding(Expression<Func<TModel, object>> expression, DependencyProperty property,
BindingMode bindingMode, IValueConverter valueConverter)
{
var accessor = ReflectionHelper.GetAccessor(expression);
var binding = new Binding(accessor.Name) { Mode = bindingMode };
if (valueConverter != null) binding.Converter = valueConverter;
element.SetBinding(property, binding);
}
}</p>
Note that the name of the view model property which is described by the lambda expression is extracted from the expression with the aid of the ReflectionHelper utility class. This class we have “stolen” from the FuBu MVC project.
The above class will be heavily used by the rest of the framework.
The TextBoxBinder class – or – how to bind a property
Now that we have discussed the goals and paved the ground by defining the necessary utility or helper classes we finally want to define the logic needed to make this nice fluent interface possible that I showed above as well as in part 1 of this article series.
public class TextBoxBinder<TModel> : ControlBinder<TextBoxBinder<TModel>, TextBox, TModel>
where TModel : class
{
public TextBoxBinder(TModel model, TextBox control)
: base(model, control)
{
}
public TextBoxBinder<TModel> Text(Expression<Func<TModel, object>> expression)
{
propertyBinder.SetBinding(expression, TextBox.TextProperty, BindingMode.TwoWay);
return this;
}
// rest of code omitted for brevity...
}</p>
In the constructor of the above class I have to pass the instance of the view model I want to bind to as well as the control which shall be bound. The class inherits from a ControlBinder which in turn inherits from a FrameworkElementBinder. The binder classes plus or minus copy the hierarchy of the controls. But I will discuss this in more detail in a subsequent post.
For the moment lets concentrate on the Text(…) method of the above class. The only parameter of the method is the lambda expression which defines to which model property I want to bind. I use the PropertyBinder class discussed above to define the binding. And then I just return this to make the fluent interface possible.
The view
The code behind of a XAML based user control looks similar to this
public partial class SampleView : IOpinionatedControl<SampleViewModel>
{
public SampleView()
{
InitializeComponent();
}
public SampleViewModel Model
{
get { return DataContext as SampleViewModel; }
}
public void SetModel(IViewModel viewModel)
{
DataContext = viewModel;
}
public void Initialize()
{...}
}</p>
In the Initialize() method one would normally implement the code to bind the controls to the view model. That is code like this
this.WithTextBox(txtUsername)
.Text(m => m.UserName)
Note that the user control (or let’s just call it view in the future) implements the generic interface **IOpinionatedControl
public interface IOpinionatedControl<TModel> : IOpinionatedControl
where TModel : class
{
TModel Model { get; }
}</p>
and it inherits from this interface
public interface IOpinionatedControl
{
void SetModel(IViewModel viewModel);
void Initialize();
}</p>
which is its non generic variant.
Extension methods for the view
To permit us to easily create a TextBoxBinder instance for a given TextBox we now define extension methods for our views. Since all of our views implement the generic interface **IOpinionatedControl
public static class ControlBinderViewExtensions
{
public static TextBoxBinder<TModel> WithTextBox<TModel>(this IOpinionatedControl<TModel> view, TextBox control)
where TModel : class
{
return new TextBoxBinder<TModel>(view.Model, control);
}
}</p>
Note that the TextBoxBinder constructor expects the model as a first parameter. We have access to the view model via the view. We can then implement an additional extension method for each type of control we want to use.</p>
Summary
In this post I have discussed the details how we bind a property of a Silverlight control to a property of the view model. The binding is declared via a lambda expression. A binding can optionally also contain a value converter to adapt UI specific types to more (view) model friendly types. As an example I have shown how we adapt the Visibility data type which is specific to Silverlight (and WPF) to the data type boolean which makes more sense in a model.