Silverlight Toolkit: TreeView, TreeViewItem & HierarchalDataTemplate
2. Add a reference to the Silverlight Controls assembly (Microsoft.Windows.Controls.dll) which can be downloaded at http://codeplex.com/Silverlight.
3. Look under "Custom Controls" In the Blend Asset Library.
4. Add a TreeView to the Page.
And here's the XAML Blend generated for us:
<UserControl
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"
mc:Ignorable="d"
x:Class="SilverlightControlsNovember2008.TreeViewPage"
d:DesignWidth="640" d:DesignHeight="480"
xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"
xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
<Grid x:Name="LayoutRoot" Background="#FFFFFFFF">
<controls:TreeView Margin="79,98,0,0" VerticalAlignment="Top" Height="200" Width="120" HorizontalAlignment="Left"/>
</Grid>
</UserControl>
Manually Adding New Textual TreeViewItems to a TreeView
Let’s add some TreeViewItems to our TreeView that will reflect a family’s genealogy tree. like so:
We’ll right click on TreeView –> Add TreeViewItem.
And we can see that we indeed got a new TreeViewItem nested under or TreeView:

Next, we’ll go to the TreeViewItem’s properties and set Header to “Sally” and IsExpanded to true.
Here’s the XAML blend generated for us up until now:
<controls:TreeView Height="200" Width="120">
<controls:TreeViewItem IsExpanded="True" Header="Sally"/>
</controls:TreeView>
Now we’d like to add “John” as a child TreeViewItem of “Sally”.
We’ll Right click on “Sally” TreeViewItem –> Add TreeViewItem.
And we got another nested note inside the previous TreeViewItem:
We’ll edit it’s Header to “John” and we’ll keep IsExpanded to false, because it has no nested nodes.
We’ll keep it up until we get the following TreeView:
Let’s run our sample:
And we can start collapsing, expanding & Selecting TreeViewItems:
(In the above sample, Greg is collapsed, and John is selected)
And here’s the XAML Blend generated for us:
<controls:TreeView Height="200" Width="120">
<controls:TreeViewItem IsExpanded="True" Header="Sally">
<controls:TreeViewItem Header="John"/>
<controls:TreeViewItem Header="Greg" IsExpanded="True">
<controls:TreeViewItem Header="Linda"/>
<controls:TreeViewItem Header="Darren"/>
</controls:TreeViewItem>
<controls:TreeViewItem Header="Allice" IsExpanded="True">
<controls:TreeViewItem Header="Neil"/>
</controls:TreeViewItem>
</controls:TreeViewItem>
</controls:TreeView>
Editing the TreeViewItem’s Header property in Visual Studio XAML Editor
Now it’s time to uncover the horrible truth – Sally and her children are all aliens. The green “blip blip” kind.
This is how our TreeView should look like when we’re done:
Blend currently does not support editing the Header property in a visual way. So we’ll open up Visual Studio’s XAML editor for that.
Right click on “TreeViewPage.xaml” (which is our page) in the Blend project tab –> Edit In Visual Studio.
And in a few second we’ll see this screen:
We’ll start off by changing the first TreeViewItem:
<controls:TreeViewItem IsExpanded="True" Header="Sally">
First, we’ll expand the Header property to allow XAML content:
<controls:TreeViewItem IsExpanded="True">
<controls:TreeViewItem.Header></controls:TreeViewItem.Header>
We’ll fill in the “Sally” content:
<controls:TreeViewItem IsExpanded="True">
<controls:TreeViewItem.Header>
<TextBlock Text="Sally" />
</controls:TreeViewItem.Header>
And our TreeView still looks exactly the same:
Now we’d like to add the image of our Alien, but the Header can only contain a single element.
So we’ll group the <Image> and the <TextBlock> in a horizontal <StackPanel>.
<controls:TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sally" />
</StackPanel>
</controls:TreeViewItem.Header>
Now that we have a container in the Header, we’ll add the <Image> tag.
<controls:TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Image Source="Alien1.png" />
<TextBlock Text="Sally" />
</StackPanel>
</controls:TreeViewItem.Header>
In our Visual Studio preview windows we can see the following image:
Well, The text is too small for our picture. So let’s change the FontSize for the TreeView to 22.
<controls:TreeView FontSize="22" Height="200" Width="120">
We can see the Horizontal and Vertical Scrollbars appear because now our TreeViewItems are bigger than the TreeView. We’ll change the TreeView Width & Height accordingly.
<controls:TreeView FontSize="22" Height="350" Width="250">
Now our TreeView looks much better:
here’s our XAML up until now:
<controls:TreeView FontSize="22" Height="350" Width="250" Margin="84,101,0,0" VerticalAlignment="Top" HorizontalAlignment="Left">
<controls:TreeViewItem IsExpanded="True">
<controls:TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Image Source="Alien1.png" />
<TextBlock Text="Sally" />
</StackPanel>
</controls:TreeViewItem.Header>
<controls:TreeViewItem Header="John"/>
<controls:TreeViewItem Header="Greg" IsExpanded="True">
<controls:TreeViewItem Header="Linda"/>
<controls:TreeViewItem Header="Darren"/>
</controls:TreeViewItem>
<controls:TreeViewItem Header="Allice" IsExpanded="True">
<controls:TreeViewItem Header="Neil"/>
</controls:TreeViewItem>
</controls:TreeViewItem>
</controls:TreeView>
We’ll repeat the process of editing an Image for the other 6 TreeViewItems. Than we’ll run our application.
here’s the XAML we wrote:
<controls:TreeView FontSize="22" Height="350" Width="250" Margin="84,101,0,0" VerticalAlignment="Top" HorizontalAlignment="Left">
<controls:TreeViewItem IsExpanded="True">
<controls:TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Image Source="Alien1.png" />
<TextBlock Text="Sally" />
</StackPanel>
</controls:TreeViewItem.Header>
<controls:TreeViewItem>
<controls:TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Image Source="Alien2.png" />
<TextBlock Text="John" />
</StackPanel>
</controls:TreeViewItem.Header>
</controls:TreeViewItem>
<controls:TreeViewItem IsExpanded="True">
<controls:TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Image Source="Alien3.png" />
<TextBlock Text="Greg" />
</StackPanel>
</controls:TreeViewItem.Header>
<controls:TreeViewItem>
<controls:TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Image Source="Alien4.png" />
<TextBlock Text="Linda" />
</StackPanel>
</controls:TreeViewItem.Header>
</controls:TreeViewItem>
<controls:TreeViewItem>
<controls:TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Image Source="Alien5.png" />
<TextBlock Text="Darren" />
</StackPanel>
</controls:TreeViewItem.Header>
</controls:TreeViewItem>
</controls:TreeViewItem>
<controls:TreeViewItem IsExpanded="True">
<controls:TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Image Source="Alien6.png" />
<TextBlock Text="Allice" />
</StackPanel>
</controls:TreeViewItem.Header>
<controls:TreeViewItem>
<controls:TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Image Source="Alien7.png" />
<TextBlock Text="Neil" />
</StackPanel>
</controls:TreeViewItem.Header>
</controls:TreeViewItem>
</controls:TreeViewItem>
</controls:TreeViewItem>
</controls:TreeView>
We can definitely see that we’ve did some copy & paste here. Next we’ll see how we can use DataBinding to remove this repeatable code.
Specifying a DataTemplate as the TreeView’s ItemTemplate
Well, now we’d like to remove that duplicated code by using some DataBinding.
We’ve got this Alien class:
public class Alien
{
public Alien(string name, string pictureUrl)
{
Name = name;
Picture = new BitmapImage(new Uri(pictureUrl, UriKind.Relative));
}
public string Name { get; set; }
public BitmapImage Picture { get; set; }
}
Pretty simple, we’ve got a Name property, and a Picture property the we’re settings with way Silverlight uses for Image.Source databinding.
We’ll jump back to Blend.
And clear all the Items from the TreeViewItem by selecting the first TreeViewItem and deleting it.
Now that we have an empty TreeView, we’d like to set a x:Name so we can setup the TreeView‘s DataContext from our Code-behind.
We’ll set the TreeView ItemSource to run on a collection of aliens.
void TreeViewPage_Loaded(object sender, RoutedEventArgs e)
{
trvAliens.ItemsSource = new List<Alien>()
{
new Alien("Sally", "Alien1.png"),
new Alien("John", "Alien2.png"),
new Alien("Greg", "Alien3.png"),
new Alien("Linda", "Alien4.png"),
new Alien("Darren", "Alien5.png"),
new Alien("Alice", "Alien6.png"),
new Alien("Neil", "Alien7.png"),
};
}
Now, we can finally get down to business – changing the TreeView’s ItemTemplate.
Right Click on TreeView –> Edit Other Templates –> Edit Generated Items (ItemTemplate) –> Create Empty.
We’ll call our new Template AlienTemplate.
We can see that we have an empty DataTemplate:
First, we’ll Change the Grid to a Horizontal StackPanel.
Right Click Grid –> Change Layout Type –> StackPanel.
Next we’ll add an Image control.
And we’ll want to DataBind it’s Source Property to Alien.Picture property.
Click Advanced Property options next to Source.
Select “Custom Expression”.
And put in “{Binding Picture}”.
Next we’ll add a TextBlock and Bind it’s Text property to “{Binding Name}”.
-->
–>
-->
Let’s run this sample:
Here’s the XAML Blend Generated for our TreeView:
<controls:TreeView FontSize="22" Height="350" Width="250" x:Name="trvAliens" ItemTemplate="{StaticResource AlienTemplate}"/>
Here’s the XAML Blend generated for our ItemTemplate:
<UserControl.Resources>
<DataTemplate x:Key="AlienTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=Picture}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
Here’s our Code-behind:
void TreeViewPage_Loaded(object sender, RoutedEventArgs e)
{
trvAliens.ItemsSource = new List<Alien>()
{
new Alien("Sally", "Alien1.png"),
new Alien("John", "Alien2.png"),
new Alien("Greg", "Alien3.png"),
new Alien("Linda", "Alien4.png"),
new Alien("Darren", "Alien5.png"),
new Alien("Alice", "Alien6.png"),
new Alien("Neil", "Alien7.png"),
};
}
And our current Alien Class:
public class Alien
{
public Alien(string name, string pictureUrl)
{
Name = name;
Picture = new BitmapImage(new Uri(pictureUrl, UriKind.Relative));
}
public string Name { get; set; }
public BitmapImage Picture { get; set; }
}
Specifying a HierarchalDataTemplate as a TreeView’s ItemTemplate in Visual Studio XAML Editor
In case you haven’t noticed, our current TreeView is pretty flat. It only has 1 level. And we’d like to get a similar TreeView to the one we previously had, with Nested TreeViewItems.
We’ll start by changing our CLR Alien Type:
public class Alien
{
public Alien(string name, string pictureUrl, params Alien[] children)
{
Name = name;
Picture = new BitmapImage(new Uri(pictureUrl, UriKind.Relative));
Children = children;
}
public string Name { get; set; }
public BitmapImage Picture { get; set; }
public Alien[] Children { get; set; }
}
All we did is add a property that is collection of Aliens that are the Children of that Alien.
Now that we’ve got a Hierarchical Alien class, we’ll change our code behind to use it:
void TreeViewPage_Loaded(object sender, RoutedEventArgs e)
{
trvAliens.ItemsSource = new List<Alien>()
{
new Alien("Sally", "Alien1.png",
new Alien("John", "Alien2.png"),
new Alien("Greg", "Alien3.png",
new Alien("Linda", "Alien4.png"),
new Alien("Darren", "Alien5.png")
),
new Alien("Alice", "Alien6.png",
new Alien("Neil", "Alien7.png")
)
)
};
}
The next part of changing our ItemTemplate is not supported by Blend. so we’re back at editing XAML in Visual Studio XAML Editor.
This is our current DataTemplate:
<DataTemplate x:Key="AlienTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=Picture}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
Let’s change it to a HierarchicalDataTemplateL:
<controls:HierarchicalDataTemplate x:Key="AlienTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=Picture}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</controls:HierarchicalDataTemplate>
And we’ll need to point to our new Hierarchical CLR property – Children.
We’ll do that by Setting HierarchicalDataTemplate.ItemsSource to “{Binding Children}”.
<controls:HierarchicalDataTemplate x:Key="AlienTemplate" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=Picture}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</controls:HierarchicalDataTemplate>
Let’s run the sample:
Here’s our XAML code for the TreeView (hasn’t changed during this sample):
<controls:TreeView FontSize="22" Height="350" Width="250" x:Name="trvAliens" ItemTemplate="{StaticResource AlienTemplate}"/>
Here’s our updated ItemTemplate:
<UserControl.Resources>
<controls:HierarchicalDataTemplate x:Key="AlienTemplate" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=Picture}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</controls:HierarchicalDataTemplate>
</UserControl.Resources>
Here’s our updated Alien class:
public class Alien
{
public Alien(string name, string pictureUrl, params Alien[] children)
{
Name = name;
Picture = new BitmapImage(new Uri(pictureUrl, UriKind.Relative));
Children = children;
}
public string Name { get; set; }
public BitmapImage Picture { get; set; }
public Alien[] Children { get; set; }
}
And our updated Code-behind:
void TreeViewPage_Loaded(object sender, RoutedEventArgs e)
{
trvAliens.ItemsSource = new List<Alien>()
{
new Alien("Sally", "Alien1.png",
new Alien("John", "Alien2.png"),
new Alien("Greg", "Alien3.png",
new Alien("Linda", "Alien4.png"),
new Alien("Darren", "Alien5.png")
),
new Alien("Alice", "Alien6.png",
new Alien("Neil", "Alien7.png")
)
)
};
}
Syncing a TreeView SelectedItem with an External Control
We’d like to have a big bold TextBlock on top of our page which says “Selected Alien: <Name of Alien>”. like so:
So, first, we’ll add a big TextBlock saying “Selected Alien:” in big bold letter.
And another empty TextBlock called “txtSelectedAlienName” with the same font properties.
Next we’d like to register to the SelectedItemChanged event.
public TreeViewPage()
{
// Required to initialize variables
InitializeComponent();
this.Loaded += new RoutedEventHandler(TreeViewPage_Loaded);
trvAliens.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(trvAliens_SelectedItemChanged);
}
void trvAliens_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
}
Here are the EventArgs for this event:
We’ll convert e.NewValue to Alien CLR type, and use it’s Name to fill our TextBlock.
void trvAliens_SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e)
{
txtSelectedAlienName.Text = ((Alien) e.NewValue).Name;
}
Let’s run our sample:
And if we select “John”:
Here’s our updated XAML:
<controls:TreeView FontSize="22" Height="350" Width="250" Margin="84,101,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" x:Name="trvAliens" ItemTemplate="{StaticResource AlienTemplate}"/>
<TextBlock Height="46" Margin="22,38,0,0" VerticalAlignment="Top" FontWeight="Bold" TextWrapping="Wrap" Width="257" HorizontalAlignment="Left"><Run FontSize="30" Text="Selected Alien:"/></TextBlock>
<TextBlock Height="46" Margin="279,38,204,0" VerticalAlignment="Top" TextWrapping="Wrap" FontSize="30" FontWeight="Bold" FontFamily="Portable User Interface" TextDecorations="Underline" x:Name="txtSelectedAlienName"/>
And our updated code-behind:
public TreeViewPage()
{
// Required to initialize variables
InitializeComponent();
this.Loaded += new RoutedEventHandler(TreeViewPage_Loaded);
trvAliens.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(trvAliens_SelectedItemChanged);
}
void trvAliens_SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e)
{
txtSelectedAlienName.Text = ((Alien)e.NewValue).Name;
}
Using the TreeView SelectedValue and SelectedValueMember
Let’s refactor our previous code sample so instead of using e.NewValue it would use the TreeView’s SelectedValue.
SelectedValue reflects a specific property on the current SelectedItem based on SelectedValuePath. complicated? not really.
this handler:
void trvAliens_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
txtSelectedAlienName.Text = ((Alien)e.NewValue).Name;
}
is equivalent to this code:
void trvAliens_SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e)
{
txtSelectedAlienName.Text = ((Alien)trvAliens.SelectedValue).Name;
}
in stead of asking the what’s the new selected Item, we can just ask the TreeView what’s the currently selected item.
By default, SelectedValue is the CLR type behind the currently selected TreeViewItem.
Instead of converting our SelectedValue back to Alien and getting Name, we can set SelectedValuePath to “Name”.
And now with this change, SelectedValue isn’t Alien anymore, but the value of Alien.Name.
so, we can re-write our handler to:
void trvAliens_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
txtSelectedAlienName.Text = trvAliens.SelectedValue.ToString();
}
And we get the exact same UI:
Here’s the XAML Blend generated for us:
<controls:TreeView FontSize="22" Height="350" Width="250" x:Name="trvAliens" ItemTemplate="{StaticResource AlienTemplate}" SelectedValuePath="Name"/>
And here’s our updated Event Handler:
void trvAliens_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
txtSelectedAlienName.Text = trvAliens.SelectedValue.ToString();
}
Changing Visual States – Changing the Collapsed and Extended Visual States
Looking at this last Print screen a bit more closely we can see It uses to types of Icons:
- Expanded Icon
- Collapsed Icon
We’d like to change those to:
- Collapsed Icon
- Expanded Icon
In order to do that, we’ll have to edit the Template of the TreeViewItem generated by the TreeView.
To do that, we’ll change the TreeView ItemContainerStyle that gets applied on to each generated TreeViewItem.
We’ll select the TreeView, go to “Object –> Edit Other Styles –> Edit ItemContainerStyle –> Edit Copy”.
And we’ll call the new Style “AlienItemStyle”.
Next, we’ll need to drill into editing the template for the TreeViewItems.
We’ll do that through “Edit Template –> Edit Controls Parts (Template) –> Edit Template”.
Here’s what we see:
There are a few VisualStateManager states here. And we’ve got a template that has a few visual elements in it.
One of those is the Expander button.
In order to change the TreeViewItem Icons we’ll need to edit the Template for the ExpanderButotn.
Right Click on the ExpanderButton –> Edit Template –> Edit Template.
And here’s what we see:
Apparently, the TreeViewItem ExpanderButton has two visuals: “CheckedVisual”
and “UncheckedVisual”
.
We’ll need to replace those with our new Visuals.
First, I’ll draw a whole ellipse.
And on top of it I’ll draw a rectangle.
I’ll select the Ellipse with the Rectangle. Right Click –> Combine –> Subtract.
And we’ll get this path:
I’ll repeat the process and we’ll also get this path:
Now, In XAML we’ll cut & paste the names of “CheckedVisual” and “UncheckedVisual” to these new elements.

becomes
Next, we’ll delete the old CheckVisual and UncheckedVisual and place our new ones into the correct position.
Now, one last thing we have to change before this runs, is making sure the “UncheckedVisual” is hidden during the “Checked” state.
We’ll go the the “Checked” state.
Select the “UncheckedVisual” and set it’s opacity to 0.
Now if we run our sample:
Here’s the XAML Blend generated for our ListBox:
<controls:TreeView FontSize="22" :Name="trvAliens" ItemTemplate="{StaticResource AlienTemplate}" SelectedValuePath="Name" ItemContainerStyle="{StaticResource AlienItemStyle}" />
Here’s the the Style that was generated for us:
(it’s quite verbose, so the important parts are highlighted)
<Style x:Key="AlienItemStyle" TargetType="controls:TreeViewItem">
<Setter Property="IsTabStop" Value="True"/>
<Setter Property="Padding" Value="3"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Cursor" Value="Arrow"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:TreeViewItem">
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualState x:Name="Normal"/>
<vsm:VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="Header" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<SolidColorBrush Color="#FF999999"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup x:Name="SelectionStates">
<vsm:VisualState x:Name="Unselected"/>
<vsm:VisualState x:Name="Selected">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="select" Storyboard.TargetProperty="Opacity" To=".75"/>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="SelectedInactive">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="inactive" Storyboard.TargetProperty="Opacity" To=".2"/>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup x:Name="HasItemsStates">
<vsm:VisualState x:Name="HasItems"/>
<vsm:VisualState x:Name="NoItems">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpanderButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup x:Name="ExpansionStates">
<vsm:VisualState x:Name="Collapsed"/>
<vsm:VisualState x:Name="Expanded">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ItemsHost" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
<ToggleButton HorizontalAlignment="Stretch" x:Name="ExpanderButton" VerticalAlignment="Stretch">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Grid x:Name="Root" Background="Transparent">
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualState x:Name="Normal"/>
<vsm:VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0" Storyboard.TargetName="UncheckedBrush" Storyboard.TargetProperty="Color" To="#FF1BBBFA"/>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="Root" Storyboard.TargetProperty="Opacity" To=".7"/>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup x:Name="CheckStates">
<vsm:VisualState x:Name="Unchecked">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="CheckedVisual" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="UncheckedVisual" Storyboard.TargetProperty="Opacity" To="0"/>
<DoubleAnimation Duration="0" Storyboard.TargetName="CheckedVisual" Storyboard.TargetProperty="Opacity" To="1"/>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
<Grid HorizontalAlignment="Right" Margin="2 2 5 2">
<Path x:Name="CheckedVisual" Margin="-10.409,15.842,-5.159,6.095" Width="15.568" Fill="#FFFFF6F6" Stretch="Fill" Stroke="#FF000000" StrokeLineJoin="Miter" Data="M0.5,0.5 L15.067377,0.5 L14.694302,2.2247162 C13.935259,3.899652 12.497602,5.241466 10.703027,5.9499054 C9.8057394,6.3041248 8.8192225,6.5 7.7836885,6.5 C5.7126212,6.5 3.8376207,5.7164989 2.4803879,4.449748 C1.8017712,3.8163719 1.2525964,3.0621843 0.87307578,2.2247162 z"/>
<Path x:Name="UncheckedVisual" Margin="-2.875,9.312,-5.125,6.688" Fill="#FFFFF6F6" Stretch="Fill" StrokeLineJoin="Miter" Data="M0.5,0.5 C4.3659902,0.50000048 7.5,3.634006 7.5,7.5 C7.5,11.365994 4.3659887,14.5 0.5,14.5 z">
<Path.Stroke>
<SolidColorBrush Color="#FF989898" x:Name="UncheckedBrush"/>
</Path.Stroke>
</Path>
</Grid>
</Grid>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
<Rectangle x:Name="select" Grid.Column="1" IsHitTestVisible="False" Opacity="0" Fill="#FFBADDE9" Stroke="#FF6DBDD1" StrokeThickness="1" RadiusX="2" RadiusY="2"/>
<Rectangle x:Name="inactive" Grid.Column="1" IsHitTestVisible="False" Opacity="0" Fill="#FF999999" Stroke="#FF333333" StrokeThickness="1" RadiusX="2" RadiusY="2"/>
<Button Background="{TemplateBinding Background}" Foreground="{TemplateBinding Foreground}" Cursor="{TemplateBinding Cursor}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" x:Name="Header" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Grid.Column="1" Content="{TemplateBinding Header}" ContentTemplate="{TemplateBinding HeaderTemplate}" ClickMode="Hover">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid Background="{TemplateBinding Background}">
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualState x:Name="Normal"/>
<vsm:VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="hover" Storyboard.TargetProperty="Opacity" To=".5"/>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="content" Storyboard.TargetProperty="Opacity" To=".55"/>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
<Rectangle x:Name="hover" IsHitTestVisible="False" Opacity="0" Fill="#FFBADDE9" Stroke="#FF6DBDD1" StrokeThickness="1" RadiusX="2" RadiusY="2"/>
<ContentPresenter Cursor="{TemplateBinding Cursor}" HorizontalAlignment="Left" Margin="{TemplateBinding Padding}" x:Name="content" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
<ItemsPresenter x:Name="ItemsHost" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="1" Visibility="Collapsed"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I think it’s an important exercise for us to see how much Expression Blend helps us in not dealing with complex XAML syntaxes like this one.
Changing Visual States – Changing the Disabled Visual State
We’re going to a CheckBox to the form that says '”Allow Alien Selection?” and if it’s not Checked than we’ll disable our TreeView.
And here’s the XAML blend generated for this Checkbox:
<CheckBox Content="Enable Alien Selection?" x:Name="cbxEnableTreeView" IsChecked="True"/>
In our code-behind, we’ll change the TreeView IsEnabled (which drills down to TreeView IsEnabled).
public TreeViewPage()
{
// Required to initialize variables
InitializeComponent();
…
cbxEnableTreeView.Checked += new RoutedEventHandler(cbxEnableTreeView_Checked);
cbxEnableTreeView.Unchecked += new RoutedEventHandler(cbxEnableTreeView_Checked);
}
void cbxEnableTreeView_Checked(object sender, RoutedEventArgs e)
{
trvAliens.IsEnabled = cbxEnableTreeView.IsChecked.Value;
}
Let’s run our sample:
Well, I personally don’t like this IsEnabled=False display for our TreeViewItems.
Our aliens are very bright and still take a lot of attention.
Let’s bring down the TreeViewItem opacity to 50% while it’s Disabled.
Select the Disabled VSM state.
And change the Grid’s opacity to 70%.
Let’s run our sample:
Side-by-side with the original disabled state:

Here’s the XAML Blend generated for our Disabled VSM state:
<vsm:VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="Header" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<SolidColorBrush Color="#FF999999"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.7"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
Changing Visual States – Changing the HasItems and No Items Visual States
We’d like to change the Size of TreeViewItems based on whether or not they have children.
We’ll Select the NoItems state.
Select the Header and change it’s scaling to 0.7.
We’ll select the HasItems VSM State and set the Header scaling to 1.1.
Let’s run on our sample:
Here’s a side-by-side with the TreeView from the previous sample:
Here’s the XAML Blend generated for our VSM states:
<vsm:VisualStateGroup x:Name="HasItemsStates">
<vsm:VisualState x:Name="HasItems">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="Header" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1.1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="Header" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1.1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="NoItems">
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="ExpanderButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="Header" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.7"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="Header" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.7"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
Changing Visual States – Changing the Selected and SelectedInactive Visual State
Our aliens don’t like that the selected Alien has blue background. Being aliens, they asked us to change the background color to green and we’ll oblige.
Here’s the new Select background side by side with the old select background.

To achieve this effect, we’ll start editing the “Selected” VSM State.
We’ll select the “select” element.
And change it’s Fill from Blue
to green
Let’s run our sample:
Here’s he XAML blend generated for our VSM state:
<vsm:VisualState x:Name="Selected">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="select" Storyboard.TargetProperty="Opacity" To=".75"/>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="select" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#FFBFE9BA"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
Virtualizing the TreeView with TreeViewItem’s Expanded & Collapsed Events
Let’s start from adding a new TreeView.
We want this TreeView to display all combinations of letters in the world.
Problem is, it’s quite a few Items and TreeView has no built in virtualization. So let’s start from scratch here.
We’ll start from displaying a TreeView of all single charcters.
public TreeViewCharsPage()
{
// Required to initialize variables
InitializeComponent();
this.Loaded +=new RoutedEventHandler(TreeViewCharsPage_Loaded);
}
private void TreeViewCharsPage_Loaded(object sender, RoutedEventArgs e)
{
for (int i = 97; i < 122; i++)
{
TreeViewItem newItem = new TreeViewItem() {Header = (char) i};
trvChars.Items.Add(newItem);
}
}
Let’s run our sample:
OK, that’s a good start. Now, we’d like to “pretend” that all combinations of “a” + all letters between A to Z are under “a”. Same goes to the rest of the characters.
So, at this point, we can start nesting loops. So the first loop would create 26 characters between A-Z. And the next one would create another 26*26 TreeViewItems, and the one in that would create 26*26*26 ad so on.
We don’t want to waste that much memory because that would burden our application and make it sluggish.
So we’ll use a different strategy – we’ll pretend all TreeViewItems are already there and when the user Expends a TreeViewItem we’ll create them.
First move here is create a mock-TreeViewItem to show the TreeViewItem Icon.
private void TreeViewCharsPage_Loaded(object sender, RoutedEventArgs e)
{
for (int i = 97; i < 122; i++)
{
TreeViewItem newItem = new TreeViewItem() {Header = (char) i};
newItem.Items.Add(new TreeViewItem());
trvChars.Items.Add(newItem);
}
}
Let’s run our sample:
But when we expand our TreeViewItem, it’s still empty:
So let’s use the Expanded event to add the new TreeViewItems.
private void TreeViewCharsPage_Loaded(object sender, RoutedEventArgs e)
{
for (int i = 97; i < 122; i++)
{
TreeViewItem newItem = new TreeViewItem() {Header = (char) i};
newItem.Items.Add(new TreeViewItem());
newItem.Expanded += new RoutedEventHandler(newItem_Expanded);
trvChars.Items.Add(newItem);
}
}
void newItem_Expanded(object sender, RoutedEventArgs e)
{
}
The first thing we need to do here is convert sender to a TreeViewItem (which is always the sender of this event)
void newItem_Expanded(object sender, RoutedEventArgs e)
{
TreeViewItem expanded = (TreeViewItem) sender;
}
Next, we’ll loop through the letters in the ABC and add them to the Expanded TreeViewItem:
void newItem_Expanded(object sender, RoutedEventArgs e)
{
TreeViewItem expanded = (TreeViewItem) sender;
for (int i = 97; i < 122; i++)
{
TreeViewItem newItem = new TreeViewItem() { Header = expanded.Header.ToString() + (char)i };
}
}
And we’ll make sure to add this new TreeViewItem to the expanded TreeViewItem.
void newItem_Expanded(object sender, RoutedEventArgs e)
{
TreeViewItem expanded = (TreeViewItem) sender;
for (int i = 97; i < 122; i++)
{
TreeViewItem newItem = new TreeViewItem() { Header = expanded.Header.ToString() + (char)i };
expanded.Items.Add(newItem);
}
}
Let’s run our sample:
And now let’s add the final piece of puzzle which is a demi-object and link the new TreeViewItem’s Expanded event to the same handler.
void newItem_Expanded(object sender, RoutedEventArgs e)
{
TreeViewItem expanded = (TreeViewItem) sender;
for (int i = 97; i < 122; i++)
{
TreeViewItem newItem = new TreeViewItem() { Header = expanded.Header.ToString() + (char)i };
newItem.Items.Add(new TreeViewItem());
newItem.Expanded += new RoutedEventHandler(newItem_Expanded);
expanded.Items.Add(newItem);
}
}
Let’s run our sample and navigate all the way down to “Jhonny”.
If we were to create all this TreeViewItems up front we’ll need to create than 25^1+25^2+25^3+25^4+25^5+25^5 items = 254,313,150 TreeViewItems.
So through using the Expanded event (and the Collapsed event) we can delay creating TreeViewItems until their necessary.
Here’s the XAML Blend generated for us:
<controls:TreeView x:Name="trvChars" />
Here’s the code we wrote:
private void TreeViewCharsPage_Loaded(object sender, RoutedEventArgs e)
{
for (int i = 97; i < 122; i++)
{
TreeViewItem newItem = new TreeViewItem() {Header = (char) i};
newItem.Items.Add(new TreeViewItem());
newItem.Expanded += new RoutedEventHandler(newItem_Expanded);
trvChars.Items.Add(newItem);
}
}
void newItem_Expanded(object sender, RoutedEventArgs e)
{
TreeViewItem expanded = (TreeViewItem) sender;
for (int i = 97; i < 122; i++)
{
TreeViewItem newItem = new TreeViewItem() { Header = expanded.Header.ToString() + (char)i };
newItem.Items.Add(new TreeViewItem());
newItem.Expanded += new RoutedEventHandler(newItem_Expanded);
expanded.Items.Add(newItem);
}
}
(I’m tempted to refactor this code, but let’s leave it like this for example sake)
--- Justin-Josef Angel
Microsoft Silverlight Program Manager