One of the cool new features in Silverlight 5 are implicit data templates. They allow developers to associate a set of visuals encapsulated inside a data template with a specific class. You can find a cool demo of this feature on Tim Heuer;s blog. One of the links in this article shows how to use the feature inside a list box.
I would like to demonstrate ultimate power of this feature when it comes to creating views dynamically based on a specific type. Let me elaborate. I want to create an entry screen for a person class using MVVM pattern. First, I am creating a simple Person class:
namespace SL5Features.Models
{
public class Person : ModelBase
{
private string firstName;
public string FirstName
{
get { return firstName; }
set { firstName = value; OnPropertyChanged("FirstName"); }
}
private string lastName;
public string LastName
{
get { return lastName; }
set { lastName = value; OnPropertyChanged("LastName"); }
}
}
}
My base class does nothing more than implements INotifyPropertyChanged interface:
using System.ComponentModel;
namespace SL5Features.Models
{
public class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Now, I want to create a default template for this class. Unlike other demos you saw so far, I am going to add this template to a resource dictionary. I am going to create a new folder in my project called Templates. Then, I am going to right-click, select Add New Item, then select resource dictionary type. Once dictionary is created, I am going to add a data template to it, and associate it with the person type. I have to add namespace to my models of course. Here is what my dictionary looks like:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:models="clr-namespace:SL5Features.Models">
<DataTemplate DataType="models:Person">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="7"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="7"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="First Name:"
Grid.Column="0" Grid.Row="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"/>
<TextBlock Text="Last Name:"
Grid.Column="0" Grid.Row="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"/>
<TextBox Text="{Binding Path=FirstName, Mode=TwoWay}"
Grid.Column="2" Grid.Row="0"/>
<TextBox Text="{Binding Path=LastName, Mode=TwoWay}"
Grid.Column="2" Grid.Row="2"/>
</Grid>
</DataTemplate>
</ResourceDictionary>
Pretty simple screen. Now for giggles I am going to create new dictionary with slightly different implementation for the same UI:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:models="clr-namespace:SL5Features.Models">
<DataTemplate DataType="models:Person">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="7"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="7"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="First Name:"
Grid.Column="0" Grid.Row="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Foreground="Red"/>
<TextBlock Text="Last Name:"
Grid.Column="0" Grid.Row="2"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Foreground="Red"/>
<TextBox Text="{Binding Path=FirstName, Mode=TwoWay}"
Grid.Column="2" Grid.Row="0"
Background="Yellow"/>
<TextBox Text="{Binding Path=LastName, Mode=TwoWay}"
Grid.Column="2" Grid.Row="2"
Background="Yellow"/>
</Grid>
</DataTemplate>
</ResourceDictionary>
Now, the fun part of injecting the visuals into my view. I am going to inject them into MainPage, but ordinarily you would create a new user control. All I do is add ContentControl and set Content to my view model’s property called Model that will contain my Person instance:
<UserControl
x:Class="SL5Features.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="clr-namespace:SL5Features.Models"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot">
<ContentControl
x:Name="Host"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Content="{Binding Path=Model}"
/>
</Grid>
</UserControl>
Oh, I did not show you my view models structure. I have a base class with Model property and INotifyPropertyChanged implementation. Simple and basic:
using System.ComponentModel;
namespace SL5Features.ViewModels
{
public class ViewModelBase<T> : INotifyPropertyChanged
{
private T model;
public T Model
{
get { return model; }
set { model = value; OnPropertyChanged("Model"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
In my person view model I am going to cheat and just create an instance of a person:
using SL5Features.Models;
namespace SL5Features.ViewModels
{
public class PersonViewModel : ViewModelBase<Person>
{
public PersonViewModel()
{
Model = new Person() { FirstName = "Sergey", LastName = "Barskiy" };
}
}
}
Now, in App.Xaml.cs I am going to inject the view model into my main page:
private void Application_Startup(object sender, StartupEventArgs e)
{
AddDictionaries();
this.RootVisual = new MainPage() { DataContext = new PersonViewModel() };
}
Now, if you run the application you will not get the desired result. Last step is to show you what magic my AddDictionaries contains:
private void AddDictionaries()
{
// do we have a template query parameter
var hasTempalteKey = HtmlPage.Document.QueryString.ContainsKey("Template");
// create Url for default visuals dictionary
var uri = new Uri("/SL5Features;component/Templates/Templates.xaml", UriKind.Relative);
// now if I have query parameter called Template set to "ALT", I am going to use differnt Url
if (hasTempalteKey && HtmlPage.Document.QueryString["Template"].ToUpper().Contains("ALT"))
{
// alternate tempalte Url
uri = new Uri("/SL5Features;component/Templates/AltTemplates.xaml", UriKind.Relative);
}
// now creat new dictionary and inject it into Application resources
ResourceDictionary dictionary = new ResourceDictionary();
dictionary.MergedDictionaries.Add(
new ResourceDictionary() { Source = uri });
this.Resources.MergedDictionaries.Add(dictionary);
}
I commented the code to demonstrate what I am doing. What my goal is to swap visuals based on query string containing Template=Alt. Now, run the application . Here is what it looks like:
Now, the cool part. Just alter the Url and whatch the magic:
As you can see, I have swapped the visuals by dynamically swapping templates. You can imagine that you can design template and use then as a replacement for your views, injecting them into pre-defined shells. You can see how you can decrease the amount of code needed for view / view model construction in a typical MVVM application, but also dynamically inject different visuals without changing any code.
You can download my sample application here.
Enjoy Silverlight 5! You can find the download links here.