In WPF & Silverlight, a Storyboard is a collection of animations running in parallel. Not everyone likes the name “Storyboard” though. The idea behind the name was that that list of timelines running in parallel are like a list of plot lines in the plan for a movie.
In any case, just like you can package up a tree of elements into a re-usable UserControl component, it’s nice to be able to package up a tree of timelines into a re-usable Storyboard component. Here’s an approach to do just that, using Xaml, and made simpler by using a new Xaml feature in WPF4.
For example, here’s a Storyboard that puts three animations on a Rectangle:
<Grid>
<Grid.Resources>
<BackEase Amplitude="0.5" x:Key="backEase" />
</Grid.Resources>
<Grid.Triggers>
<EventTrigger RoutedEvent="Grid.Loaded">
<BeginStoryboard>
<Storyboard TargetName="Rectangle1">
<DoubleAnimation Storyboard.TargetProperty="Width"
From="0"
EasingFunction="{StaticResource backEase}"
Duration="0:0:1" />
<DoubleAnimation Storyboard.TargetProperty="Height"
From="0"
EasingFunction="{StaticResource backEase}"
Duration="0:0:1" />
<ColorAnimation Storyboard.TargetProperty="Fill.Color"
From="LightGray"
Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
<Rectangle Name="Rectangle1" Width="100" Height="100" Fill="Red" />
</Grid>
The goal is to package up this Storyboard into a re-usable component that I can use on any Rectangle that has a SolidColorBrush Fill. To do that, I’ll just move that <Storyboard> markup into a separate Xaml file and parameterize it.
Unfortunately there’s no single menu item for this e.g. in Visual Studio, but it’s not hard to do. What I do is right click on the project, “Add”, “User Control”, name it something like “MyRectangleStoryboard.xaml”, and get something like this:
<UserControl x:Class="CustomStoryboard.MyRectangleStoryboard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Grid>
</Grid>
</UserControl>
… and this:
public partial class MyRectangleStoryboard : UserControl
{
public MyRectangleStoryboard()
{
InitializeComponent();
}
}
Now, just change “UserControl” to “Storyboard (and remove the <Grid> from the Xaml), and you’re all set:
<Storyboard x:Class="CustomStoryboard2.MyRectangleStoryboard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
</Storyboard> public partial class MyRectangleStoryboard : Storyboard
{
public MyRectangleStoryboard()
{
InitializeComponent();
}
}
The goal is to use this component instead of the storyboard/animation markup:
<Grid>
<Grid.Resources>
<BackEase Amplitude="0.5" x:Key="backEase" />
</Grid.Resources>
<Grid.Triggers>
<EventTrigger RoutedEvent="Grid.Loaded">
<BeginStoryboard>
<l:MyRectangleStoryboard TargetName="Rectangle1" Duration="0:0:1" EasingFunction="{StaticResource backEase}"/>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
<Rectangle Name="Rectangle1" Width="100" Height="100" Fill="Red" />
</Grid>
As for the implementation of the MyRectangleStoryboard here’s the markup, which is cut/paste from the original with a couple of modifications for parameterization (which I’ll explain):
<Storyboard x:Class="CustomStoryboard.MyRectangleStoryboard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:CustomStoryboard" >
<DoubleAnimation Storyboard.TargetProperty="Width"
From="0"
Duration="{Binding Duration, Source={l:XRoot}}"
EasingFunction="{Binding EasingFunction, Source={l:XRoot}}" />
<DoubleAnimation Storyboard.TargetProperty="Height"
From="0"
Duration="{Binding Duration, Source={l:XRoot}}"
EasingFunction="{Binding EasingFunction, Source={l:XRoot}}" />
<ColorAnimation Storyboard.TargetProperty="Fill.Color"
From="LightGray"
Duration="{Binding Duration, Source={l:XRoot}}" />
</Storyboard>
Notice that this custom storyboard is parameterized for the setting of the Duration and the EasingFunction properties on the animations. (Recall that these two properties were set when the MyRectangleStoryboard was used above).
Ordinarily, if you set the Duration on the storyboard it doesn’t inherit down to its children timelines, instead it clips its children timelines. But I want the MyRectangleStoryboard.Duration to actually be the Double/ColorAnimation.Duration, so I bound the Animation.Duration properties to the root’s Duration property.
The source of this binding is a custom “XRoot” markup extension that I wrote. You could equivalently establish this binding in code, and in fact you pretty much have to today. But in WPF4 (which is now in beta), you can write a custom MarkupExtension to access the root of a Xaml document, using the new IRootObjectProvider service. Here’s the implementation:
public class XRoot : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
var rootProvider = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider;
if (rootProvider == null)
return null;
return rootProvider.RootObject;
}
}
The other parameterization on this MyRectangleStoryboard component is the EasingFunction. This is a custom property on MyRectangleStoryboard so that I can set it any time I use it. The implementation is just a DependencyProperty:
public partial class MyRectangleStoryboard : Storyboard
{
public MyRectangleStoryboard()
{
InitializeComponent();
}
public EasingFunctionBase EasingFunction
{
get { return (EasingFunctionBase)GetValue(EasingFunctionProperty); }
set { SetValue(EasingFunctionProperty, value); }
}
public static readonly DependencyProperty EasingFunctionProperty =
DependencyProperty.Register("EasingFunction", typeof(EasingFunctionBase), typeof(MyRectangleStoryboard));
}
So in the end there’s three takeaways from this:
- You can create powerful custom animation sets by subclassing Storyboard.
- Xaml is a useful component definition language, it’s not just for controls.
- In WPF4, Xaml markup extensions can get explicit access to the lexical root of the Xaml document.
I’ve never found TreeView to be terribly confusing by itself. But usually I want to data bind a TreeView to a collection with some hierarchy, which leads me to HierarchicalDataTemplate, which didn’t always just write itself for me. If you look at it in steps, though, there really is a pretty nice progression from ListBox to TreeView. Like a lot of my posts, this goes way too deep if you just want to create a quick TreeView. But it’s useful to look at it if you want the HierarchicalDataTemplate to just write itself …
First, start with the example of a simple ListBox:
|
<ListBox>
<sys:String>Western Conference</sys:String>
<sys:String>Eastern Conference</sys:String>
</ListBox> |
|
We can add a trivial ItemTemplate to it:
|
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="Red" Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
<sys:String>Western Conference</sys:String>
<sys:String>Eastern Conference</sys:String>
</ListBox> |
|
… and now the list box items are red.
A ListBox is an ItemsControl, and what ItemsControls like to do is wrap their items in a container (more on that here). So the above essentially becomes:
|
<ListBox>
<ListBoxItem Content="Eastern Conference">
<ListBoxItem.ContentTemplate>
<DataTemplate x:Name="_template1">
<TextBlock Foreground="Red" Text="{Binding}" />
</DataTemplate>
</ListBoxItem.ContentTemplate>
</ListBoxItem>
<ListBoxItem Content="Western Conference"
ContentTemplate="{Binding ElementName=_template1}" />
</ListBox> |
|
That is, for each item in the ListBox, a ListBoxItem is created. The ListBoxItem’s Content property is bound to the item, and the ListBoxItem’s ContentTemplate property is bound to the ListBox’s ItemTemplate.
On to TreeView …
A TreeView is also an ItemsControl; it generates TreeViewItem controls for its items. And actually, if you just take the original example and change “ListBox” to “TreeView”, it looks almost the same:
|
<TreeView>
<sys:String>Eastern Conference</sys:String>
<sys:String>Western Conference</sys:String>
</TreeView> |
|
You can similarly set an explicit item template:
|
<TreeView>
<TreeView.ItemTemplate>
<DataTemplate>
<TextBlock Foreground='Red' Text='{Binding}' />
</DataTemplate>
</TreeView.ItemTemplate>
<sys:String>Western Conference</sys:String>
<sys:String>Eastern Conference</sys:String>
</TreeView> |
|
The next step here caught be by surprise when I first saw it. The TreeView is going to create a TreeViewItem for each of its items (the two strings). A TreeViewItem is a HeaderedItemsControl, which is an ItemsControl (just like ListBox and TreeView), but also has a Header (and HeaderTemplate) property. (The Header is the part of the tree view item that you always see, right next to the expand/collapse button.) The Header/HeaderTemplate of a HeaderedItemsControl is analogous to the Content/ContentTemplate of a ContentControl. So, whereas the ListBox items were bound to each ListBoxItem’s Content property, in the TreeView case the items are bound to the TreeViewItem’s Header property. Similarly, the TreeView’s ItemTemplate property is bound to the TreeViewItem’s HeaderTemplate property. And in the end we essentially have:
|
<TreeView>
<TreeViewItem Header='Western Conference'>
<TreeViewItem.HeaderTemplate>
<DataTemplate x:Name='_template2'>
<TextBlock Foreground='Red' Text='{Binding}' />
</DataTemplate>
</TreeViewItem.HeaderTemplate>
</TreeViewItem>
<TreeViewItem Header='Eastern Conference'
HeaderTemplate='{Binding ElementName=_template2}' />
</TreeView> |
|
Now what we need is some hierarchy. Those items are the MLS conferences, and there’s supposed to be teams in those conferences. Here’s the full data:
var western = new Conference("Western")
{
Teams =
{
new Team("Club Deportivo Chivas USA"),
new Team("Colorado Rapids"),
new Team("FC Dallas"),
new Team("Houston Dynamo"),
new Team("Los Angeles Galaxy"),
new Team("Real Salt Lake"),
new Team("San Jose Earthquakes"),
new Team("Seattle Sounders FC"),
new Team("Portland 2011"),
new Team("Vancouver 2011")
}
};
var eastern = new Conference("Eastern")
{
Teams =
{
new Team("Chicago Fire"),
new Team("Columbus Crew"),
new Team("D.C. United"),
new Team("Kansas City Wizards"),
new Team("New York Red Bulls"),
new Team("New England Revolution"),
new Team("Toronto FC"),
new Team("Philadelphia Union 2010")
}
};
var league = new Collection<Conference>() { western, eastern };
DataContext = new
{
WesternConference = western,
EasternConference = eastern,
League = league
};
(Note that the DataContext now has this sample data.)
And now we can show all the teams with some explicit hierarchy:
|
<TreeView>
<TreeViewItem Header='Western Conference'
ItemsSource="{Binding WesternConference.Teams}">
<TreeViewItem.ItemTemplate>
<DataTemplate>
<!-- Team name -->
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
<TreeViewItem Header='Eastern Conference'
ItemsSource="{Binding EasternConference.Teams}">
<TreeViewItem.ItemTemplate>
<DataTemplate>
<!-- Team name -->
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
</TreeView>
|
|
Of course, what you really want to do is bind the TreeView itself to the hierarchical collection (the League of the DataContext). I.e.:
|
<TreeView ItemsSource="{Binding League}" /> |
|
As you can see, there’s two items, as you’d expect, just like a ListBox. Also just like a ListBox, it doesn’t show anything except for the ToString() of the Conference object. So we need to give it an ItemTemplate to show the conference Name:
|
<TreeView ItemsSource="{Binding League}">
<TreeView.ItemTemplate>
<DataTemplate>
<TextBlock Foreground="Red" Text="{Binding Name}" />
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView> |
|
Now, recall that the TreeView here is creating two TreeViewItems, binding the TreeViewItem’s Header to the Conference object, and setting the TreeViewItem’s HeaderTemplate to the TreeView’s ItemTemplate. Next question is, how do we get the TreeViewItem’s ItemsSource bound to Conference.Teams? That’s where the HierarchicalDataTemplate comes in.
A HierarchicalDataTemplate is a DataTemplate with some extra properties. But if you don’t use the extra properties, it’s no different than DataTemplate. For example, change the last markup from DataTemplate to HierarchicalDataTemplate, and nothing changes:
|
<TreeView ItemsSource="{Binding League}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate >
<TextBlock Foreground="Red" Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView> |
|
But HierarchicalDataTemplate adds two key properties: ItemsSource and ItemTemplate. The ItemsSource gets mapped to the TreeViewItem.ItemsSource, and the ItemTemplate gets mapped to the TreeViewItem.ItemTemplate. So now we can show the conferences and the teams:
|
<TreeView ItemsSource="{Binding League}">
<!-- Conference teamplate -->
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Teams}">
<TextBlock Foreground="Red" Text="{Binding Name}" />
<!-- Team template -->
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView> |
|
And if you want to show one level deeper in the hierarchy, you can change that team DataTemplate to a HierarchicalDataTemplate:
|
<TreeView ItemsSource="{Binding League}">
<!-- Conference template -->
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Teams}">
<TextBlock Foreground="Red" Text="{Binding Name}" />
<!-- Team template -->
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Players}">
<TextBlock Text="{Binding Name}" />
<!-- Player template -->
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
|
|
In the end, the bottom line that I keep in mind when I’m writing a HierarchicalDataTemplate, is that it’s the template for the TreeViewItem.Header, and it lets me set the TreeViewItem’s ItemsSource and ItemTemplate properties.
Attachment(s): TreeViewStepByStep.zip
When we create new classes and members we spend a lot of time and effort to make them as usable, understandable, and discoverable as possible. We follow the .Net Design Guidelines in general, and in particular we constantly look at how this new class relates to other classes, future plans, etc.
So we went through all that when naming DependencyProperty and DependencyObject. All told we probably spent hours just on the name. What dependency properties (DPs) boil down to in the end is property calculation and dependency tracking. Property calculation isn’t terribly distinctive, lots of properties do that, so the essence of a DP is really the dependency tracking, thus “Dependency” properties.
Here’s an example of that, actually one piece of sample code that shows several examples of dependency tracking:
<StackPanel TextBlock.FontSize="22" DataContext="Hello, world">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontWeight" Value="Bold" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock Text="{Binding}" />
</StackPanel>
The properties of this TextBlock have quite a few dependencies:
· TextBlock.Text is dependent on the Binding, and the Binding is dependent on the DataContext in this case. The DataContext is inheriting down from the StackPanel, so the Text property is therefore also dependent on the shape of the tree; if the TextBlock is removed from the StackPanel, it’s Text property will update.
· TextBlock.FontSize is dependent on the tree as well. Here, you can see it’s inheriting from the StackPanel.
· All of the TextBlock properties depend on TextBlock.Style. For example, here TextBlock.FontWeight is coming from the Style.
· Similarly, TextBlock.Background depends on the Style, but here it’s being set in trigger. So TextBlock.Background in this case also depends on TextBlock.IsMouseOver.
Sometimes, if you write your own DP, you need to help with the dependency tracking. You do this by calling InvalidateProperty when a property needs to be re-calculated, usually because you reference it in a CoerceValueCallback.
For example, here’s a DP Foo and a companion (read-only) DP named FooPlus1. FooPlus1 just has a CoerceValueCallback that calculates “Foo+1”. Foo therefore has a PropertyChangedCallback that invalidates FooPlus1 whenever Foo changes.
//
// Foo property
//
public int Foo
{
get { return (int)GetValue(FooProperty); }
set { SetValue(FooProperty, value); }
}
public static readonly DependencyProperty FooProperty =
DependencyProperty.Register("Foo", typeof(int), typeof(Window1),
new PropertyMetadata(FooChangedCallback));
static void FooChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
// Whenever Foo changes, we need to invalidate FooPlus1, so that
// the DependencyProperty system knows to update it (call its
// CoerceValueCallback again).
(d as Window1).InvalidateProperty(Window1.FooPlus1Property);
}
//
// FooPlus1 Property
//
public int FooPlus1
{
get { return (int)GetValue(FooPlus1Property); }
}
static readonly DependencyPropertyKey FooPlus1PropertyKey =
DependencyProperty.RegisterReadOnly("FooPlus1", typeof(int), typeof(Window1),
new PropertyMetadata(0, null, CoerceFooPlus1Callback));
static readonly DependencyProperty FooPlus1Property = FooPlus1PropertyKey.DependencyProperty;
static object CoerceFooPlus1Callback(DependencyObject d, object baseValue)
{
return (d as Window1).Foo + 1;
}
Here’s the scenario … You have a Customers collection and an Orders collection. In the Orders collection, an Order has a CustomerID property; this is the key to an item in the Customers collection. Your goal is a ComboBox that updates the CustomerID property of a Customer, but interacts with the end user in terms of customer names. If that doesn’t make sense, code & pictures help …
Here’s how you can accomplish this in markup in WPF (the l:Collection here is just a Collection<object>):
<Grid>
<Grid.Resources>
<l:Collection x:Key="Customers"> <!-- Collection<object> -->
<l:Customer ID="1" CustomerName="Wilma" />
<l:Customer ID="2" CustomerName="Betty" />
<l:Customer ID="3" CustomerName="Fred" />
<l:Customer ID="4" CustomerName="Barney" />
</l:Collection>
<l:Collection x:Key="Orders"> <!-- Collection<object> -->
<l:Order Description="Magic carpet" CustomerID="1" />
<l:Order Description="Blue suede shoes" CustomerID="4" />
<l:Order Description="Hanna Montana wig" CustomerID="1" />
</l:Collection>
</Grid.Resources>
<StackPanel>
<!-- Show a customer ID -->
<StackPanel Orientation="Horizontal">
<TextBlock Text="Customer ID:" Margin="5"/>
<TextBlock Name="TextBlock1" Margin="5" Text="1"/>
</StackPanel>
<!-- Pick the customer ID that will be shown above -->
<ComboBox ItemsSource="{StaticResource Customers}"
SelectedValuePath="ID"
SelectedValue="{Binding Text, ElementName=TextBlock1}"
DisplayMemberPath="CustomerName" />
</StackPanel>
</Grid>
More on that ComboBox after these pictures,
Initially we get this:
… and here I am opening the ComboBox, and changing the customer to Betty (her CustomerID is 2):
So that changed the “Customer ID” from 1 to 2, but I (the end user) interacted only with customer names via the ComboBox.
Now let’s analyze that ComboBox markup:
<ComboBox ItemsSource="{StaticResource Customers}"
SelectedValuePath="ID"
SelectedValue="{Binding Text, ElementName=TextBlock1}"
DisplayMemberPath="CustomerName" />
The ItemsSource is set to the Customers list. So when you open the ComboBox, you’ll see customers. Specifically, for each customer in that list, you’ll see the Customer.CustomerName, because the DisplayMemberPath is set to the “CustomerName” property.
Now whatever is selected in the ComboBox, I want it to show up in TextBlock1. If I just bind SelectedItem to TextBlock1, I’d be sending the whole Customer object over, but really I just want the TextBlock.Text to show the Customer.CustomerID. So, instead of binding SelectedItem to the TextBlock, I bind SelectedValue. SelectedValue behaves the same as SelectedItem, though, until you set a SelectedValuePath. When you set SelectedValuePath, then SelectedValue becomes that path into the SelectedItem. For example, if your SelectedItem is a Customer, and your SelectedValuePath is “ID”, then SelectedValue is going to be Customer.ID, which is just what we want.
(Note in this example I defaulted TextBlock1.Text to ‘1’. Otherwise the SelectedValue binding gets confused. Alternatively, I could have set the Binding.Mode to ‘OneWayToSource’.)
Next let’s do this with the Silverlight ComboBox, because it doesn’t have the SelectedValue or SelectedValuePath properties. But we can still get it to work with a little bit of code. Change the markup to this:
<ComboBox ItemsSource="{StaticResource Customers}"
DisplayMemberPath="CustomerName"
SelectionChanged="ComboBox_SelectionChanged" />
… and write a little code:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var comboBox = sender as ComboBox;
TextBlock1.Text = (comboBox.SelectedItem as Customer).ID.ToString();
}
It’d be nice if we could do this all in markup, though, and in fact there have been some posts on the Silverlight forums to do just that with a Binding value converter. Also, I wish I could have this feature on a TextBlock too, not just on ComboBox. So below is a handy value converter that can be used with a Binding. (I created this on the Silverlight 3 beta, but most of this is applicable to Silverlight 2 as well.) The idea of this is to mimic the ComboBox mechanism. First, here’s an example of the converter being created:
<Grid.Resources>
<l:Collection x:Key="Customers"> <!-- Collection<object> -->
...
</l:Collection>
<l:Collection x:Key="Orders"> <!-- Collection<object> -->
...
</l:Collection>
<l:ValueToItemConverter x:Key="CustomerID2Name"
ItemsSource="{StaticResource Customers}"
ValuePath="ID"
DisplayMemberPath="CustomerName" />
</Grid.Resources>
… notice that it looks much like the ComboBox. The only thing it doesn’t have is a counterpart to the SelectedValue property, because the value is what comes into the IValueConverter methods. Now let’s use it in a trivial master/detail:
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- List of Orders -->
<ListBox ItemsSource="{StaticResource Orders}" Name="ListBox1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- Detail for the selected Order -->
<StackPanel Grid.Column="1"
DataContext="{Binding SelectedItem, ElementName=ListBox1}">
<TextBlock Text="{Binding Description}" />
<!-- Order.CustomerID convertered to a Customer.CustomerName -->
<TextBlock Text="{Binding CustomerID,
Converter={StaticResource CustomerID2Name}}" />
</StackPanel>
</Grid>
… which gives us this (here I’ve selected the first order):
The key here is that in the detail, rather than showing CustomerID 1 from the Order, we see “Wilma”.
And finally, here’s an example using this value converter in the column of a DataGrid. Again, the items are Orders, and the column shows a CustomerID as a CustomerName, using a TextBlock when the cell isn’t being edited, and as a ComboBox when it is.
First, create a version of the value converter that doesn’t have DisplayMemberPath set (because ComboBox has its own DisplayMemberPath property):
<l:ValueToItemConverter x:Key="CustomerID2Customer"
ItemsSource="{StaticResource Customers}"
ValuePath="ID" />
… and then the DataGrid itself:
<d:DataGrid AutoGenerateColumns="False"
ItemsSource="{StaticResource Orders}">
<d:DataGrid.Columns>
<d:DataGridTextColumn Binding="{Binding Description}" />
<d:DataGridTemplateColumn>
<d:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding CustomerID,
Converter={StaticResource CustomerID2Name}}"
Margin="4"/>
</DataTemplate>
</d:DataGridTemplateColumn.CellTemplate>
<d:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox
DisplayMemberPath="CustomerName"
ItemsSource="{StaticResource Customers}"
SelectedItem="{Binding CustomerID, Mode=TwoWay,
Converter={StaticResource CustomerID2Customer}}" />
</DataTemplate>
</d:DataGridTemplateColumn.CellEditingTemplate>
</d:DataGridTemplateColumn>
</d:DataGrid.Columns>
</d:DataGrid>
Attachment(s): SelectedValue.zip
ICommand is a simple interface with three members – Execute, CanExecute, and CanExecuteChanged (more on those here). You can write your own implementations of that interface, one for each command, but that gets a bit heavyweight. So there are several implementations of ICommand that are pluggable and re-usable, like DelegateCommand, RelayCommand, and RoutedCommand.
One tricky part of implementing ICommand is the CanExecuteChanged event, because you have to know when to raise it. (The easy alternative is to raise it whenever the CommandManager.RequerySuggested event is raised, but you need to use that with caution, because it can happen frequently and get expensive.)
Really, though, CanExecute and CanExecuteChanged look like a typical Foo/FooChanged property/event pair that we use in data binding. The reason that CanExecute isn’t actually a property, though, is that it takes an optional parameter. But if you’re not using that parameter, a property would work just as well.
So I’ve been playing with an ICommand implementation that takes a delegate for the Execute method (just like DelegateCommand and RelayCommand), but also has an IsEnabled property that wraps CanExecute/CanExecuteChanged. And since it’s all a DependencyObject, you can put a binding on IsEnabled.
I wasn’t sure what to call it though. The key is that it has an IsEnabled property, but DelegateCommandWithIsEnabled didn’t sound good. And it turns out that Enabled-able isn’t a word. The closest thing I could come up with that had “able” in it was AbilityCommand. Good enough for a blog post.
Anyway, here’s what the public members look like:
public class AbilityCommand : DependencyObject, ICommand
{
public AbilityCommand(
Action<object> executeDelegate,
Binding isEnabledBinding );
public AbilityCommand( Action<object> executeDelegate )
: this( executeDelegate, null ) { }
public bool IsEnabled {get; set; }
public static readonly DependencyProperty IsEnabledProperty ...;
}
Note that you can set IsEnabled however you want, including with a binding, and in fact the constructor lets you pass in the Binding to use. AbilityCommand’s ICommand.CanExecute simply returns IsEnabled. And any time IsEnabled’s value changes, AbilityCommand raises ICommand.CanExecuteChanged.
And here it is in practice. In this snippet, the (View)Model object is the first page of a wizard-type view (with a “Next” button on it). The SubmitCommand property binds the command’s IsEnabled to the object’s IsValid property:
public class FirstPage : INotifyPropertyChanged
{
public FirstPage()
{
SubmitCommand = new AbilityCommand(
(x) => OnSubmit(),
new Binding("IsValid") { Source=this } );
}
public ICommand SubmitCommand { get; private set; }
private void OnSubmit()
{
// ...
}
// INotifyPropertyChanged fires for this IsValid property if
// either the "FirstProperty" or "SecondProperty" property is updated
public bool IsValid
{
get
{
return !String.IsNullOrEmpty(FirstProperty)
&& !String.IsNullOrEmpty(SecondProperty);
}
}
And finally, here’s the whole AbilityCommand:
public class AbilityCommand : DependencyObject, ICommand
{
Action<object> _executeDelegate;
public AbilityCommand( Action<object> executeDelegate )
: this( executeDelegate, null ) { }
public AbilityCommand(
Action<object> executeDelegate,
Binding isEnabledBinding )
{
_executeDelegate = executeDelegate;
if( isEnabledBinding != null )
BindingOperations.SetBinding(this, IsEnabledProperty, isEnabledBinding);
}
void ICommand.Execute(object parameter)
{
_executeDelegate(parameter);
}
bool ICommand.CanExecute(object parameter)
{
return IsEnabled;
}
public event EventHandler CanExecuteChanged;
private void RaiseCanExecuteChanged()
{
if( CanExecuteChanged != null )
CanExecuteChanged( this, new EventArgs() );
}
public bool IsEnabled
{
get { return (bool)GetValue(IsEnabledProperty); }
set { SetValue(IsEnabledProperty, value); }
}
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.Register("IsEnabled", typeof(bool), typeof(AbilityCommand),
new PropertyMetadata(OnIsEnabledChanged));
static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
(d as AbilityCommand).RaiseCanExecuteChanged();
}
}
(This has been updated with some information about the origin of a routed command’s route, and of focus scopes.)
ICommand in WPF is a pretty simple thing at its core. But it gets more interesting and complicated as you build up functionality on top of it, and integrate it into the higher layers of the UI. So it’s either like a layer cake, or layers of an onion. But onion is an outside-in metaphor, and layer cake is a bottom-up metaphor, and ICommand is easier to think of bottom up, so I’m declaring ICommand to be a layer cake.
For example, commands provide a mechanism to abstract input (so “navigate back” means “navigate back”, whether it came from the keyboard’s back button or the mouse’s X1 button or anywhere else). And commands provide a mechanism for the View to update the Model in a Model/View separated application. And commands provide a way to search the element tree for a command handler, as well as a way for a command handler to say that it doesn’t want to be executed at the moment.
The mechanism for all those scenarios is all very similar, but the scenarios themselves seem so different that it all can get confusing. So here’s a step-by-step description of how it all fits together …
Start with ICommand
ICommand itself is very straightforward:
public interface ICommand
{
void Execute(object parameter);
bool CanExecute(object parameter);
event EventHandler CanExecuteChanged;
}
Given an instance of an ICommand, you just call Execute, and it does whatever it’s supposed to do. Except you shouldn’t call it if it’s CanExecute is false. If you want to know when CanExecute might be willing to give you a different answer, listen to the CanExecuteChanged event.
For example, here’s a super simple command:
public class HelloWorldCommand : ICommand
{
public void Execute(object parameter)
{
Debug.WriteLine("Hello, world");
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
If you use that like this:
new HelloWorldCommand().Execute(null);
… you’ll see “Hello, world” in the debug output window.
You can make it more interesting with a parameter and by checking CanExecute. First change the command like this:
public class HelloWorldCommand : ICommand
{
public void Execute(object parameter)
{
Debug.WriteLine(parameter);
}
public bool CanExecute(object parameter)
{
return parameter != null;
}
public event EventHandler CanExecuteChanged;
}
… and call it like this:
var hwc = new HelloWorldCommand();
if (hwc.CanExecute(this))
hwc.Execute(this);
… and in my case I see “CommandCake.Window1” in the debugger output window.
Piece o’ cake.
Button (heart) ICommand
Once you have an ICommand instance handy, you can give it to a Button (on the Button.Command property), and Button knows what to do with it. As the simplest example, you can do this with the previous command:
<Grid>
<Grid.Resources>
<local:HelloWorldCommand x:Key="hwc"/>
</Grid.Resources>
<Button Command="{StaticResource hwc}">
Click
</Button>
</Grid>
But if you do that, you’ll notice that the Button is disabled. That’s because Button knows to call CanExecute, but we haven’t specified a parameter, and recall from above that if you pass null as an argument to CanExecute it returns false. So Button has a CommandParameter property that lets you specify what will be passed to CanExecute and Execute:
<Grid>
<Grid.Resources>
<local:HelloWorldCommand x:Key="hwc"/>
</Grid.Resources>
<Button CommandParameter="Hello, world" Command="{StaticResource hwc}" >
Click
</Button>
</Grid>
Now the button is enabled, and if you click on it, you’ll see “Hello, world” in the debug output window.
This actually isn’t just a Button feature, it’s actually in the base class ButtonBase. And MenuItem is a ButtonBase. So MenuItem (heart) ICommand too.
Update the Model from the View
Now we’ve got enough for the classic Model/View usage of ICommand. In the Model/View practice, your View is a bunch of elements, which is data-bound to your Model, which has your actual content. The View can modify the model with two-way bindings and with commands.
First, before showing an example of this, let’s make it easier to implement ICommand, by introducing a helper class (a more complete DelegateCommand helper can be found in Prism). This just creates an ICommand instance that takes a delegate which will be called by ICommand.Execute:
public class SimpleDelegateCommand : ICommand
{
Action<object> _executeDelegate;
public SimpleDelegateCommand(Action<object> executeDelegate)
{
_executeDelegate = executeDelegate;
}
public void Execute(object parameter)
{
_executeDelegate(parameter);
}
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
}
Now let’s define a Model of a simple Debug writer:
public class DebugWriter
{
ICommand _indentCommand = new SimpleDelegateCommand( (x) => Debug.Indent() );
ICommand _unindentCommand = new SimpleDelegateCommand( (x) => Debug.Unindent() );
ICommand _writeLineCommand = new SimpleDelegateCommand( (x) => Debug.WriteLine(x) );
public ICommand IndentCommand { get { return _indentCommand; } }
public ICommand UnindentCommand { get { return _unindentCommand; } }
public ICommand WriteLineCommand { get { return _writeLineCommand; } }
public int IndentSize
{
get { return Debug.IndentSize; }
set { Debug.IndentSize = value; }
}
}
… and use it from a View:
<StackPanel>
<StackPanel.DataContext>
<local:DebugWriter />
</StackPanel.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/><ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/><RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Margin="3">Indent size:</TextBlock>
<TextBlock Margin="4" Grid.Column="1" Text="{Binding IndentSize}" />
<TextBlock Grid.Row="1" Margin="3">Output string:</TextBlock>
<TextBox Text="s" Grid.Row="1" Grid.Column="1" Name="OutputString" />
</Grid>
<Button Command="{Binding IndentCommand}">Indent</Button>
<Button Command="{Binding UnindentCommand}">Unindent</Button>
<Button CommandParameter="{Binding Text,ElementName=OutputString}"
Command="{Binding WriteLineCommand}">WriteLine</Button>
</StackPanel>
Notice here that the Button bound to the WriteLine command (the third Button) has its CommandParameter bound to a TextBox.Text.
Routed commands: an ICommand.Execute implementation that searches for an execute handler
For the above example I used the SimpleDelegateCommand as my implementation of ICommand, which maps ICommand.Execute to a delegate call.
WPF similarly has a built-in ICommand implementation called RoutedCommand. You don’t give a delegate directly to the RoutedCommand, though. Instead, the RoutedCommand walks up the tree, looking for a delegate. It’s similar to a routed event, in fact it’s implemented as a routed event internally. So you can put your delegate anywhere higher in the tree, in the form of an event handler, using the CommandBinding class, and it will be called by Execute. You specify your delegate with a CommandBinding object.
So I add this to my Window1.xaml.cs:
public static RoutedCommand HelloWorldRoutedCommand = new RoutedCommand();
… and this to my Xaml:
<Window.CommandBindings>
<CommandBinding
Command="{x:Static local:Window1.HelloWorldRoutedCommand}"
Executed="CommandBinding_Executed" />
</Window.CommandBindings>
<Grid>
...
<Button Command="{x:Static local:Window1.HelloWorldRoutedCommand}">Hello, world </Button>
...
</Grid>
… and then when I click the Button, my CommandBinding_Executed method gets called:
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
if( e.Source is Button )
Debug.WriteLine((e.Source as Button).Content);
else
Debug.WriteLine("Hello, world");
}
… and once again I see “Hello, world” in the debug output.
Note that the event handler of the CommandBinding.Executed event can look at the Source property of the event args to see where the RoutedCommand started. You can also get the command itself, and the CommandParameter from the args.
In this example I put the CommandBinding in the tree ancestry of the Button. Alternatively, you can also register your CommandBinding globally, using the CommandManager.RegisterClassCommandBinding method. That CommandBinding’s CanExecute then gets called no matter where in the tree the routed command’s route is starting.
RoutedCommand also has support for CanExecute and CanExecuteChanged. That’s the most complicated part of routed commands, so I’m saving that for the end.
Mapping input to an ICommand (keyboard accelerators)
Beyond Button and MenuItem, there’s another way to get an ICommand to Execute, this one based on user input. As an example, with the following Xaml, pressing <Control>H on the keyboard executes our HelloWorldCommand, again showing “Hello, world” in the debugger:
<Grid>
<Grid.Resources>
<local:HelloWorldCommand x:Key="hwc"/>
</Grid.Resources>
<Grid.InputBindings>
<KeyBinding Gesture="Control+H" Command="{StaticResource hwc}" CommandParameter="Hello, world"/>
</Grid.InputBindings>
...
Note that this key binding only takes effect if the keyboard focus is currently somewhere under that Grid, e.g. on a TextBox; otherwise the KeyBinding doesn’t see the <Control>H.
RoutedCommand also has an InputGestures property on it, where you can set the default gestures for a command. So you can get this same behavior for the HelloWorldRoutedCommand we created earlier by defining it like this:
public static RoutedCommand HelloWorldRoutedCommand = new RoutedCommand()
{
InputGestures = { new KeyGesture(Key.H, ModifierKeys.Control) }
};
… and then it will Execute no matter where your keyboard focus is.
Built-in routed commands in WPF
Anyone can create a RoutedCommand. But WPF has a set of them built-in. These are all defined as static fields in the ApplicationCommands, EditingCommands, MediaCommands, and NavigationCommands classes. E.g. NavigationCommands has the BrowseBack command, ApplicationCommands has cut/copy/paste, EditingCommands has ToggleBold, MoveRightByWord, etc. The Windows WM_APPCOMMAND commands get converted into these built-in routed commands automatically by the WPF input system.
So, for example, this markup:
<Window.CommandBindings>
<CommandBinding Command="{x:Static NavigationCommands.BrowseBack}"
Executed="BrowseBackExecuted" />
</Window.CommandBindings>
… will cause the BrowseBackExecuted method to be called when the NavigationCommands.BrowseBack command is executed, for example if you click the mouse X1 button (this is the button that makes a web browser navigate back).
From whence the command routes
If you call RoutedCommand’s ICommand.Execute, the route will start from the element that currently has keyboard focus. But RoutedCommand itself has a special overload of Execute that lets you pick where the route starts:
public class RoutedCommand
{
...
public void Execute( Object parameter, IInputElement target);
...
}
And in fact, RoutedCommand’s implementation of ICommand.Execute just calls this Execute, looking roughly like this:
void ICommand.Execute(object parameter)
{
this.Execute(parameter, Keyboard.FocusedElement );
}
Button (and MenuItem) actually understands RoutedCommand in particular, in addition to ICommand in general. So if Button.Command is a RoutedCommand, Button by default calls this RoutedCommand.Execute method, passing itself as the target. You can override this behavior, though, with the Button.CommandTarget property. For example, here is a Button that sends the Cut command to a TextBox:
<Button Command="{x:Static ApplicationCommands.Cut}"
CommandTarget="{Binding ElementName=TextBox1}">Click</Button>
<TextBox Name="TextBox1" />
This gets more complicated if you’re implementing a toolbar. Say you have a toolbar with cut/copy/paste buttons. You want that toolbar to work against whatever the currently focused text box is, so you don’t want to have to keep setting CommandTarget. E.g., in this:
<StackPanel>
<ToolBar>
<Button Command="{x:Static ApplicationCommands.Cut}">Cut</Button>
<Button Command="{x:Static ApplicationCommands.Copy}">Copy</Button>
<Button Command="{x:Static ApplicationCommands.Paste}">Paste</Button>
</ToolBar>
<TextBox />
<TextBox />
</StackPanel>
… the Cut/Copy/Paste should route to whichever TextBox has focus.
It doesn’t look like this should work, but it actually does, and here’s why … ToolBar is a “focus scope”. Focus scopes is a whole other post, but the important thing here is what happens with a routed command that’s executing and looking for a CommandBinding. As the execute request bubbles up to the ToolBar, it sees that it’s leaving a focus scope. At that point, rather than just continuing to walk up the visual tree, it goes next to the focused element in that focus scope. In this example, that means that when you click on the Cut button, the search for a handler starts at the Button, then the ToolBar, then the currently-focused TextBox, which is what we want.
Menu is also a focus scope, so what works for ToolBar works for Menu as well.
CanExecute and CanExecuteChanged for routed commands
Just like RoutedCommand allows you to define a command that walks up the tree, looking for someone to handle Execute, you also want to find someone to handle CanExecute. So RoutedCommand’s CanExecute implementation does that, and you can listen for it on CommandBinding, as you’d expect, e.g.:
<CommandBinding
Command="{x:Static local:Window1.HelloWorldRoutedCommand}"
CanExecute="CommandBinding_CanExecute"
Executed="CommandBinding_Executed" />
But here’s the trick: RoutedCommand similarly needs to raise CanExecuteChanged. But how does it know when to raise that event, when it doesn’t know what you’re going to do in CommandBinding_CanExecute?
The solution in WPF is a global event named CommandManager.RequerySuggested. This event is fired whenever the state of the routed command world might have changed. In fact, the implementation of ICommand.CanExecuteChanged in RoutedCommand is just a forwarder:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
Next problem: When should the RequerySuggested event fire? You make that happen by calling CommandManager.InvalidateRequerySuggested. But usually you don’t have to call it, it’s called automatically in several places, mostly during keyboard/mouse input.
That all creates a couple of interesting implications.
First of all, if you’re using routed commands, you’ve got some work taking place on every keystroke. On the one hand, even for a fast typist (I clocked in the other day at 88 words/minute on the high-difficulty test!), user input is very infrequent in CPU timeframes. On the other hand, it still takes time to find those CanExecute handlers, and if those handlers do anything non-trivial, and you have a lot of them, it can add up. We’ve seen that be a problem in some larger applications. You can mitigate that by reducing use of routed commands (just use ICommand instead), and by keeping the CanExecute implementations fast.
The second interesting implication is that CommandManager.RequerySuggested is a static (global) event. Usually such events can lead to leaks, because they hold the event handler delegate forever. But RequerySuggested instead only keeps weak references to its handler delegates. But now you’ve got a new problem; now the delegate can be garbage collected. You probably don’t have to worry about that, because most applications don’t listen to RequerySuggested; it’s really the Button doing this on your behalf when you set Button.Command. But Button (and MenuItem) deal with this problem by keeping their own strong reference on that delegate, so that it doesn’t get collected.
In summary
So the key points to remember here:
· ICommand is a simple definition with Execute, CanExecute, and CanExecuteChanged.
· You can point a Button or MenuItem at any ICommand with the Command property. The Button/MouseItem will then Execute that command if you click it, will disable itself if CanExecute is false, and will automatically listen for CanExecuteChanged.
· Routed commands are an ICommand implementation that searches the tree (usually starting with the focused element) for a CommandBinding that provides Execute/CanExecute handlers.
· You can also invoke an ICommand from input, e.g. using a KeyBinding.
· WPF pre-defines a set of routed commands in ApplicationCommands, EditingCommands, NavigationCommands, and MediaCommands.
· Be aware that routed commands can impact perf if you use a lot of them and/or your CanExecute handlers do non-trivial work.
I don’t remember what got me thinking about it, but somewhere along the line I wanted a master/detail view with a navigation bar. E.g., when you change selection in the master view, you can navigate back to the previous selection.
Here’s an example (here my selection started on “Wilma”, then I changed it to “Fred”, which is why the Back button of the navigation bar is enabled):
It looks like a Frame, and it is, but I just used the Frame for its navigation UI and its journaling functionality; I didn’t put anything (visible anyway) into the Content of the Frame.
Here’s the basic markup for the above Window sample:
<StackPanel>
<navbar:NavigationBar
JournaledValue="{Binding SelectedItem, ElementName=_listBox, Mode=TwoWay}"
NavigationUIVisibility="Visible"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Master view -->
<ListBox ItemsSource="{Binding}" Name='_listBox' Margin="5">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}" Margin="2"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- Details view (more on this later) -->
...
</Grid>
</StackPanel>
What does this do? The NavigationBar control has the navigation UI (forward/backward button). It also journals whatever you put in the JournaledValue property. That means:
-
If you change NavigationBar.JournaledValue, the old value will be added to the navigation service’s “BackStack”, and the Back button will become enabled.
-
If you then click on the Back button, NavigationBar.JournaledValue will be restored to its previous value.
So the way the properties are bound in this example, if the ListBox.SelectedItem changes, the old value gets journaled by the NavigationBar. If you then click the Back button, the ListBox.SelectedItem gets restored to that previous value. And of course, in the full example, the details view is similarly bound to the ListBox.SelectedItem, so it updates as well.
The NavigationBar control just has a couple of properties and a default ControlTemplate. The properties are JournaledValue (which drives, and is driven by, the navigation bar UI), and NavigationUIVisibility (Automatic, Visible, or Hidden). Here’s the control code:
public class NavigationBar : Control
{
static NavigationBar()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(NavigationBar),
new FrameworkPropertyMetadata(typeof(NavigationBar)));
}
// The JournaledValue property is the value that controls the navigation bar UI.
public object JournaledValue
{
get { return (object)GetValue(JournaledValueProperty); }
set { SetValue(JournaledValueProperty, value); }
}
public static readonly DependencyProperty JournaledValueProperty =
DependencyProperty.Register("JournaledValue", typeof(object), typeof(NavigationBar));
// The NavigationUIVisibility property is aliased to the
// Frame.NavigationUIVisibility property.
public NavigationUIVisibility NavigationUIVisibility
{
get { return (NavigationUIVisibility)GetValue(NavigationUIVisibilityProperty); }
set { SetValue(NavigationUIVisibilityProperty, value); }
}
public static readonly DependencyProperty NavigationUIVisibilityProperty =
Frame.NavigationUIVisibilityProperty.AddOwner(typeof(NavigationBar));
}
The NavigationBar’s ControlTemplate simplify forwards everything to a Frame. Frame has the UI for the navigation controls, and journals its Content property. So for journaling, I just bound the NavigationBar.JournaledValue to Frame.Content, and Frame takes care of the rest. (Note that NavigationBar.NavigationUIVisibility is also bound to the corresponding property on Frame.) The only trick is, I didn’t want the JournaledValue to actually get displayed by the Frame, so I put an empty ContentTemplate on it. This leads to:
<ControlTemplate TargetType="{x:Type local:NavigationBar}">
<Frame NavigationUIVisibility
='{TemplateBinding local:NavigationBar.NavigationUIVisibility}'
Content='{Binding JournaledValue,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay}'
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" >
<!-- Don't show the frame content, we're just using Frame
for it's navigation UI and journaling -->
<Frame.ContentTemplate>
<DataTemplate />
</Frame.ContentTemplate>
</Frame>
</ControlTemplate>
That’s it for the NavigationBar control. Here’s the rest of the sample app too. For the details view, I used an ItemsControl to list the “fields” of the selected object, where each field is a HeaderedContentControl. To get the right look, I then templated HeaderedContentControl. Easier to show the markup. First, the sample data:
<Window.DataContext>
<x:Array Type="local:Person">
<local:Person FirstName="Wilma" LastName="Flintstone" Age="41" />
<local:Person FirstName="Fred" LastName="Flintstone" Age="42" />
<local:Person FirstName="Betty" LastName="Rubble" Age="36" />
<local:Person FirstName="Barney" LastName="Rubble" Age="35" />
</x:Array>
</Window.DataContext>
… and then the details view:
<!-- Details view -->
<ItemsControl
DataContext="{Binding SelectedItem, ElementName=_detailsView}"
Grid.IsSharedSizeScope="True"
Grid.Column="1"
Margin="5" >
<!-- For the selected item, show the first name, last name, and age -->
<HeaderedContentControl Header="First name:" Content="{Binding FirstName}" />
<HeaderedContentControl Header="Last name:" Content="{Binding LastName}" />
<HeaderedContentControl Header="Age:" Content="{Binding Age}" />
<!-- Create a HeaderedContentControl template to display each item
as "Label: Value", e.g. "Age: 42" -->
<ItemsControl.Resources>
<Style TargetType="HeaderedContentControl">
<Setter Property="Template">