In his article First Look at Using Expression Blend with Silverlight 2, Scott Guthrie shows a method for populating a user control with fake data for a better design time experience in blend. This method requires binding the controls to a local resource.
While this is really helpful for designing custom user controls, it may create some problems with the final application. Let’s take the digg client presented in this article as an example. For a better design time experience with Expression Blend I’ve modified the DiggStory class’ constructor to feed the control some fake data on design time:
public DiggStory()
{
if (!HtmlPage.IsEnabled)
{
this.Title = "La página de Juan Agüí";
this.Description = "Lorem ipsum ...";
this.NumDiggs = 1000;
this.UserName = "Juan";
this.Thumbnail = "foto.jpg";
this.HrefLink = new Uri("http://juanagui.com");
}
}
For this trick to work, we have to add a local resource to our custom user control StoryDetailsView.xaml:
And bind the controls to the local resource, for instance
Now if you try to set the DataContext property of an instance of the StoryDetailsView.xaml user control with an instance of the DiggStory class, you will get a NotImplemented exception, because the DiggStory class does not implement the INotifyPropertyChanged interface.
If we’re going to implement that interface in our class, we’ll have to call the PropertyChange event whenever we set any property of our class, see Jesse Liberty’s tutorial on data binding. I’m showing the implementation of the UserName property here:
public string UserName
{
get
{
return this.username;
}
set
{
this.username = value;
NotifyChanges("UserName");
}
}
void NotifyChanges(string propertyName)
{
if (null != this.PropertyChanged)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
If you have many properties in your class, writing all the notification code can clearly becomes pretty tiresome and repetitive. We could do best with a base class for bindable objects.
The idea is pretty simple:
- all your bindable classes shall inherit from the BindableBase class
- mark all your bindable properties with the [Bindable] attribute
- when you have to change the data source of your control, use the methods SetAllValues and SetValue provided by the BindableBase class
The code of the BindingBase class follows:
using System;
using System.ComponentModel;
using System.Reflection;
[System.AttributeUsage(AttributeTargets.Property,AllowMultiple=true,Inherited=true)]
public class BindableAttribute : System.Attribute {}
public class BindableBase : INotifyPropertyChanged
{
public void SetAllValues(BindableBase classInstance)
{
Type classType = classInstance.GetType();
PropertyInfo[] properties = classType.GetProperties();
foreach (PropertyInfo propertyInfo in properties)
{
this.SetValueInternal(propertyInfo, propertyInfo.GetValue(classInstance, null));
}
}
public void SetValue(String propertyName, object value)
{
Type classType = this.GetType();
PropertyInfo propertyInfo = classType.GetProperty(propertyName);
this.SetValueInternal(propertyInfo, value);
}
private void SetValueInternal(PropertyInfo propertyInfo, object value)
{
object[] customAttributes = propertyInfo.GetCustomAttributes(typeof(BindableAttribute), true);
if ( customAttributes.Length > 0 )
{
if (propertyInfo.CanWrite)
{
propertyInfo.SetValue(this, value, null);
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyInfo.Name));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
And here’s an example on how to use the class: when the user clicks an item on the digg stories list, our user control pops up and is filled with new data:
void StoriesList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DiggStory story = (DiggStory)this.StoriesList.SelectedItem;
((DiggStory)this.StoryDetail.Resources["DiggStory"]).SetAllValues(story);
this.StoryDetail.Visibility = Visibility.Visible;
}
The SetAllValues method uses reflection for copying all the properties’ values from the new object to the user control’s data source and will trigger the PropertyChanged event for each property change.
Additionally the SetValue method will set only a property:
((DiggStory)StoryDetail.Resources["DiggStory"]).SetValue("UserName", "juan agui");