Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Silverlight 5: Working with Implicit Templates

Pete Brown - 13 April 2011

One great feature that has, until now, been exclusive to WPF is the ability to use implicit data templates. You may be familiar with data templates, probably having used them with content controls or, more likely, list boxes. Data Templates make it possible to template (typically) a non-UI item and reuse that template in one or more places. Without templates, list boxes would be pretty boring, having only the ToString value or the display value from an item.

Please note that this article and the attached sample code was written using the Silverlight 5 Beta, available at MIX11 in April 2011, and updated for the Silverlight 5 RC.

Implicit templates are data templates that are associated with a specific data type. They will be applied to any control in the scope of the template which has templatable content and which is trying to display the data type you've specified.

In more concrete terms, let's say that every time you want to display a twitter user, you want it to be formatted using the twitter name followed by a slash and the user's real name. You also want that to display in a blue color. Given that, you could write a template that looks like this:

<!-- User name template -->
<DataTemplate DataType="model:TwitterUser">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NickName}"
Style="{StaticResource NameHeadingStyle}" />
<TextBlock Text=" / " />
<TextBlock Text="{Binding FullName}"
Style="{StaticResource NameHeadingStyle}" />
</StackPanel>
</DataTemplate>

That template, would then work for any content control within the scope of the template's definition where the content is an instance of the TwitterUser class. For example, assuming our data context has a Fromuser property of type TwitterUser:

<ContentControl Content="{Binding FromUser}"
Grid.Row="0"/>

The end result is formatted text that (assuming you're using the same NameHeadingStyle I'm using) looks like this:

image or

image etc.

Because we're using a control that supports templating its content, the DataTemplate we created kicks in. This wouldn't work if the user was bound to a TextBlock, since that doesn't support content templating, but it would work with a Label control. By the way, that little ContentControl actually exists inside a larger data template, as we'll see in the next example.

A Twitter Example

I can't help myself. I often use Twitter in my examples. In this case, it's a great match, as the Twitter timelines often include a mixture of different types of events. You could manually handle formatting them, or you could simply set up different templates for each type of Twitter event, and let Silverlight do the heavy lifting for you.

The Model

In this project, I'm not connecting to Twitter itself. Instead, I have built out a model of the Twitter classes, and mocked up some data in a TwitterService class. I'm simplifying the Twitter model to support this example. It looks like this:

public class TwitterUser
{
public string NickName { get; set; }
public string FullName { get; set; }
public Uri UserImageUrl { get; set; }
}

public abstract class TwitterItem
{
public int ID { get; set; }
public DateTime TimeStamp { get; set; }
public TwitterUser FromUser { get; set; }
}

public class Tweet : TwitterItem
{
public string Message { get; set; }
public string PostedFrom { get; set; }
public int InReplyTo { get; set; }
}

public class DirectMessage : TwitterItem
{
public string Message { get; set; }
public TwitterUser ToUser { get; set; }
}

public class FollowerAnnouncement : TwitterItem
{
// Yes, this is really empty
}

The Service

With the model in place, I mocked up some data using real image URLs from Twitter. The TwitterService with all of that in place looks like this:

public class TwitterService
{
public IList<TwitterItem> GetLatestData()
{
// mocked data

var pete = new TwitterUser() { NickName = "pete_brown",
FullName = "Pete Brown",
UserImageUrl = new Uri("http://a3.twimg.com/profile_images/954496714/pmb_square_headandshouldershot_200x200_c64_reasonably_small.png") };
var joe = new TwitterUser() { NickName = "MisfitGeek",
FullName = "Joe Stagner",
UserImageUrl = new Uri("http://a3.twimg.com/profile_images/1073330120/image_0713_reasonably_small.jpg") };
var jon = new TwitterUser() { NickName = "jongalloway",
FullName = "Jon Galloway",
UserImageUrl = new Uri("http://a2.twimg.com/profile_images/329131438/JonFace420px_reasonably_small.jpg") };
var jesse = new TwitterUser() { NickName = "Jesse Liberty",
FullName = "Jesse Liberty",
UserImageUrl = new Uri("http://a0.twimg.com/profile_images/504007257/twitterProfilePhoto.jpg") };


var items = new List<TwitterItem>()
{
new Tweet() {ID=102, TimeStamp=new DateTime(2011,04,05, 23,05,33),
FromUser=joe, PostedFrom="web",
Message="I just posted a 437 part series on converting from from Ajax toolkit to jQuery"},
new Tweet() {ID=207, TimeStamp=new DateTime(2011,04,05, 23,15,21),
FromUser=joe, PostedFrom="web",
Message="@pete_brown Have I mentioned that you're the best boss ever?"},
new FollowerAnnouncement() {ID=280, TimeStamp=new DateTime(2011,04,06, 00,05,47), FromUser=jon},
new Tweet() {ID=304, TimeStamp=new DateTime(2011,04,06, 04,05,44),
FromUser=jesse, PostedFrom="a number of phones",
Message="Will you be at MIX? Come talk to me on my podcast."},
new Tweet() {ID=490, TimeStamp=new DateTime(2011,04,06, 04,18,45),
FromUser=pete, PostedFrom="c64twit",
Message="@pete_brown I'm talking to myself." },
new DirectMessage() {ID=547, TimeStamp=new DateTime(2011,06,23, 10,05,12),
FromUser=jon, ToUser=pete,
Message="Hey, I need ur help with working on implicit data templates in Silverlight 5. Send me teh codez pls."},
new Tweet() {ID=605, TimeStamp=new DateTime(2011,04,06, 06,56,57),
FromUser=jesse, PostedFrom="a number of phones",
Message="I have 99 phones, but they're all on ignore :)"},
};

return items;
}
}

The View Model

Finally, I have a simple view model which surfaces this information to the view on the main page

public class TweetViewModel
{
private ObservableCollection<TwitterItem> _twitterItems = new ObservableCollection<TwitterItem>();
public ObservableCollection<TwitterItem> TwitterItems
{
get { return _twitterItems; }
}


public void LoadTwitterItems()
{
TwitterService service = new TwitterService();

var items = service.GetLatestData();

foreach (TwitterItem item in items)
{
TwitterItems.Add(item);
}
}
}

The entire solution should resemble this

image

 

The User Interface

Now, to get to the UI. First, I wired up the DataContext, the ViewModel and the Load method in the code-behind. You can, of course, use whatever method of View/ViewModel wire-up you prefer.

public partial class MainPage : UserControl
{
private TweetViewModel _vm = new TweetViewModel();

public MainPage()
{
InitializeComponent();

Loaded += new RoutedEventHandler(MainPage_Loaded);
}

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
DataContext = _vm;

_vm.LoadTwitterItems();
}
}

The next step is to create a simple Xaml UI that we'll use for our templates. Here's the xaml:

<Grid x:Name="LayoutRoot" Background="White" Margin="15">
<TextBlock Text="Implicit Templates in Silverlight 5" FontSize="20" />

<ListBox Margin="12,42,12,12"
ItemsSource="{Binding TwitterItems}"
HorizontalContentAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
</ListBox>
</Grid>

If you run the application now, you'll see the typical result of binding a listbox to a collection of CLR objects: the type names will appear in the listbox. Note that I have a mixture of objects here, not just a single object type.

image

The usual step at this point is to create a single in-line data template in the ListBox.ItemTemplate property. However, we won't need to do that this time. With everything now in place, we can turn our attention to the implicit templates specifically.

Simple Implicit Data Templates

First, we want to test that implicit data templates work with our listbox. Here's a simple set of templates that display the class of message without getting into the meat of the message itself.

<UserControl.Resources>
<DataTemplate DataType="model:FollowerAnnouncement">
<TextBlock Text="Follower Announcement" />
</DataTemplate>
<DataTemplate DataType="model:DirectMessage">
<TextBlock Text="Direct Message" />
</DataTemplate>
<DataTemplate DataType="model:Tweet">
<TextBlock Text="Tweet" />
</DataTemplate>
</UserControl.Resources>

The key thing to notice here is that these have no key, but do have a specific DataType associated with each one. This is very similar to how implicit styles work. With those placed in the Resources section, run the application. You'll see something like this:

image

Now that we've verified that our implicit templates are working (we didn't have to change a thing on the ListBox!) let's take a look at some more robust templates.

More Complex Implicit Data Templates

Next, replace the follower announcement template with this one:

<DataTemplate DataType="model:FollowerAnnouncement">
<Grid Background="BlanchedAlmond"
Width="350"
Margin="2">
<StackPanel Orientation="Horizontal"
Margin="3">
<TextBlock Text="{Binding FromUser.FullName}" />
<TextBlock Text=" started following you " />
<TextBlock Text="{Binding TimeStamp, StringFormat='dddd hh:mm'}" />
</StackPanel>

</Grid>
</DataTemplate>

That's the simplest of the implicit templates; it simply shows the user's name and the timestamp, glued together with a piece of copy. The background for a follower announcement is set to "BlancedAlmond" to differentiate it from the other types of events showing up in the timeline.

Run it, and you'll see the following:

image

Note that the single follower announcement is now using a different template from the others. Let's wrap this up with a full set of templates that show the messages and images as well.

A Full Set of Implicit Data Templates

To make the application's data really pop, we'll need to express the Direct Messages and Tweets differently from each other and from the follower announcement. It would be nice if the direct messages showed the images for both users, perhaps using an overlapping image approach. In addition, the tweets should show what client the twitter user used when posting the tweet.

Here's the set of data templates for the three types of events we're showing in the application.

<!-- Tweet Data Template -->
<DataTemplate DataType="model:Tweet">
<Grid Margin="2"
Width="350"
HorizontalAlignment="Left"
Background="#FFEFEFEF">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="56" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<Image Source="{Binding FromUser.UserImageUrl}"
Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="4"
Width="48"
Height="48" />

<Grid Grid.Column="1" Margin="3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<ContentControl Content="{Binding FromUser}"
Grid.Row="0"/>

<TextBlock Text="{Binding Message}"
TextWrapping="Wrap"
Grid.Row="1" />

<StackPanel Grid.Row="2"
Orientation="Horizontal">
<TextBlock Text="{Binding TimeStamp, StringFormat='dddd hh:mm'}" />
<TextBlock Text=" via " />
<TextBlock Text="{Binding PostedFrom}" />
</StackPanel>

</Grid>
</Grid>
</DataTemplate>


<!-- Follower Announcement Data Template -->
<DataTemplate DataType="model:FollowerAnnouncement">
<Grid Background="BlanchedAlmond"
Width="350"
Margin="2">
<StackPanel Orientation="Horizontal"
Margin="3">
<TextBlock Text="{Binding FromUser.FullName}" />
<TextBlock Text=" started following you " />
<TextBlock Text="{Binding TimeStamp, StringFormat='dddd hh:mm'}" />
</StackPanel>

</Grid>
</DataTemplate>


<!-- Direct Message Data Template -->
<DataTemplate DataType="model:DirectMessage">
<Grid Margin="2"
Width="350"
HorizontalAlignment="Left"
Background="Cornsilk">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="56" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<Image Source="{Binding ToUser.UserImageUrl}"
Grid.Column="0"
Margin="3"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="35"
Height="35" />

<Image Source="{Binding FromUser.UserImageUrl}"
Grid.Column="0"
Margin="3"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="35"
Height="35" />

<Grid Grid.Column="1"
Margin="3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<ContentControl Content="{Binding FromUser}"
Grid.Row="0" />

<TextBlock Text="{Binding Message}"
TextWrapping="Wrap"
Grid.Row="1" />

<StackPanel Grid.Row="2"
Orientation="Horizontal">
<TextBlock Text="{Binding TimeStamp, StringFormat='dddd hh:mm'}" />
</StackPanel>

</Grid>
</Grid>
</DataTemplate>

If you run it, this is what you'll see:

image

But wait! There's something wrong here! What's up with those names? Ahh, well, we simply need to add in that implicit template from the beginning of this post. That template is used by the content control that is used to display the names. Without it, the default ToString is called. With it, we get this:

image

Here's the markup for that implicit template, plus the style it uses.

<Style x:Key="NameHeadingStyle" TargetType="TextBlock">
<Setter Property="FontSize"
Value="12" />
<Setter Property="Foreground"
Value="#FF6495ed" />
<Setter Property="FontWeight"
Value="Bold" />
</Style>


<!-- User name template -->
<DataTemplate DataType="model:TwitterUser">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NickName}"
Style="{StaticResource NameHeadingStyle}" />
<TextBlock Text=" / " />
<TextBlock Text="{Binding FullName}"
Style="{StaticResource NameHeadingStyle}" />
</StackPanel>
</DataTemplate>

That's it!

Data Templates are a great way to standardize the formatting of classes in your application. With them, you'll likely find yourself rethinking how you go about reusing styles and formatting, perhaps using more content controls and fewer directly applied styles.

Source code for the Silverlight 5 Beta version of this application may be downloaded via the link below.

A video version of this tutorial will be on Silverlight.net shortly.

       

Source Code and Related Media

Download /media/76820/implicittemplatedemo.zip
posted by Pete Brown on Wednesday, April 13, 2011
filed under:        

13 comments for “Silverlight 5: Working with Implicit Templates”

  1. Howardsays:
    I think Silverlight 5 is nearer and nearer to WPF. Just like browser trust application, and this feature is already existing in WPF. How do you think. If Silverlight can be ran on any browser, it will attact most of the developers.
  2. Petesays:
    @Fallon

    No, it's a feature WPF has had for a long time, and we're not bringing it to Silverlight.

    So it's more a "Catch up to WPF" feature :)

    And, in any case, why does it matter if something else had it first?

    Pete
  3. Petesays:
    @Hobbydotnet

    Nice seagull :)

    How exactly does this make it not MVVM-friendly? In MVVM, you have two primary ways of interacting with the model:
    1. You create a viewmodel representation of each and every model object you will use.
    2. You surface model objects through the viewmodel.

    #1 has some advantages: you can make sure you only expose what the view needs, for example. You can also transform that to combine multiple model objects together (think a customer and its address, which may be separate model objects). However, for what it gives you in most typical scenarios, it is too much work. You have to have additional mapping layers, and in many cases, end up with VMs that simply duplicate model objects. Most folks who have to deliver actual applications use this approach only when they need to. If you think "well, I can automate it with templates" ask yourself why exactly you're going through that effort.

    #2 has the advantage of ease of implementation and reduced lines of code. On the down side, you can have some model ugliness come through to the view. Your tolerance level for that ugliness helps decide when you use approach #1 instead of this approach.

    That all said, nothing in this post requires you to surface model objects to the view. You can very easily use viewmodel objects instead.of model objects. In fact, due to the simplicity of my example here, just change the namespace and suddenly you can, without guilt, pretend those objects are viewmodel objects :)

    Pete

Comment on this Post

Remember me