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:
or
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
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.
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:
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:
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:
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:
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.