Introduction
This sample shows how to two-way-bind to the SelectedItem property of a WPF or Silverlight TreeView.
It also shows how to expand nodes down to the selected item, and collapse all other nodes (to keep it tidy).
Building the Sample
Just download, unzip, open and run!
You will also need the Silverlight 4 SDK for the TreeView, or the
Silverlight 5 SDK if you wish to work on your own Silverlight 5 project.
The TreeView is not in the standard Silverlight framework. Remember to add an xmlns reference to the SDK if you wish to use this in your own project.
If you use a previous version of the SDK, just change the reference in the project.
Description
The problem with the TreeView is that the SelectedItem is a read-only property.
In Silverlight, you don't even have OneWayToSource, so binding two way to SelectedItem (which does exist in XAML, even if IntelliSense doesn't show it) causes binding errors.
The solution is to add an Attached Property, which
1) Taps into the SelectedItemChanged event for target->source
2) Searches down the TreeViewItems for the object to select, for source->target.
In the process of searching down for the selected item, it has to expand the parent node to get its children.
If it isn't in any of the descendants for a node, then the node gets closed again.
Behind all this is the following class:
using
System.Windows;
using
System.Windows.Controls;
namespace
SilverlightTreeview
{
public
class
Attached
{
public
static
object
GetTreeViewSelectedItem(DependencyObject obj)
{
return
(
object
)obj.GetValue(TreeViewSelectedItemProperty);
}
public
static
void
SetTreeViewSelectedItem(DependencyObject obj,
object
value)
{
obj.SetValue(TreeViewSelectedItemProperty, value);
}
public
static
readonly
DependencyProperty TreeViewSelectedItemProperty =
DependencyProperty.RegisterAttached(
"TreeViewSelectedItem"
,
typeof
(
object
),
typeof
(Attached),
new
PropertyMetadata(
new
object
(), TreeViewSelectedItemChanged));
static
void
TreeViewSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
TreeView treeView = sender
as
TreeView;
if
(treeView ==
null
)
{
return
;
}
treeView.SelectedItemChanged -=
new
RoutedPropertyChangedEventHandler<
object
>(treeView_SelectedItemChanged);
treeView.SelectedItemChanged +=
new
RoutedPropertyChangedEventHandler<
object
>(treeView_SelectedItemChanged);
TreeViewItem thisItem = treeView.ItemContainerGenerator.ContainerFromItem(e.NewValue)
as
TreeViewItem;
if
(thisItem !=
null
)
{
thisItem.IsSelected =
true
;
return
;
}
for
(
int
i = 0; i < treeView.Items.Count; i++)
SelectItem(e.NewValue, treeView.ItemContainerGenerator.ContainerFromIndex(i)
as
TreeViewItem);
}
static
void
treeView_SelectedItemChanged(
object
sender, RoutedPropertyChangedEventArgs<
object
> e)
{
TreeView treeView = sender
as
TreeView;
SetTreeViewSelectedItem(treeView, e.NewValue);
}
private
static
bool
SelectItem(
object
o, TreeViewItem parentItem)
{
if
(parentItem ==
null
)
return
false
;
bool
isExpanded = parentItem.IsExpanded;
if
(!isExpanded)
{
parentItem.IsExpanded =
true
;
parentItem.UpdateLayout();
}
TreeViewItem item = parentItem.ItemContainerGenerator.ContainerFromItem(o)
as
TreeViewItem;
if
(item !=
null
)
{
item.IsSelected =
true
;
return
true
;
}
bool
wasFound =
false
;
for
(
int
i = 0; i < parentItem.Items.Count; i++)
{
TreeViewItem itm = parentItem.ItemContainerGenerator.ContainerFromIndex(i)
as
TreeViewItem;
var found = SelectItem(o, itm);
if
(!found)
itm.IsExpanded =
false
;
else
wasFound =
true
;
}
return
wasFound;
}
}
}
using
System.Windows;
using
System.Windows.Controls;
namespace
SilverlightTreeview
{
public
class
Attached
{
public
static
object
GetTreeViewSelectedItem(DependencyObject obj)
{
return
(
object
)obj.GetValue(TreeViewSelectedItemProperty);
}
public
static
void
SetTreeViewSelectedItem(DependencyObject obj,
object
value)
{
obj.SetValue(TreeViewSelectedItemProperty, value);
}
public
static
readonly
DependencyProperty TreeViewSelectedItemProperty =
DependencyProperty.RegisterAttached(
"TreeViewSelectedItem"
,
typeof
(
object
),
typeof
(Attached),
new
PropertyMetadata(
new
object
(), TreeViewSelectedItemChanged));
static
void
TreeViewSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
TreeView treeView = sender
as
TreeView;
if
(treeView ==
null
)
{
return
;
}
treeView.SelectedItemChanged -=
new
RoutedPropertyChangedEventHandler<
object
>(treeView_SelectedItemChanged);
treeView.SelectedItemChanged +=
new
RoutedPropertyChangedEventHandler<
object
>(treeView_SelectedItemChanged);
TreeViewItem thisItem = treeView.ItemContainerGenerator.ContainerFromItem(e.NewValue)
as
TreeViewItem;
if
(thisItem !=
null
)
{
thisItem.IsSelected =
true
;
return
;
}
for
(
int
i = 0; i < treeView.Items.Count; i++)
SelectItem(e.NewValue, treeView.ItemContainerGenerator.ContainerFromIndex(i)
as
TreeViewItem);
}
static
void
treeView_SelectedItemChanged(
object
sender, RoutedPropertyChangedEventArgs<
object
> e)
{
TreeView treeView = sender
as
TreeView;
SetTreeViewSelectedItem(treeView, e.NewValue);
}
private
static
bool
SelectItem(
object
o, TreeViewItem parentItem)
{
if
(parentItem ==
null
)
return
false
;
bool
isExpanded = parentItem.IsExpanded;
if
(!isExpanded)
{
parentItem.IsExpanded =
true
;
parentItem.UpdateLayout();
}
TreeViewItem item = parentItem.ItemContainerGenerator.ContainerFromItem(o)
as
TreeViewItem;
if
(item !=
null
)
{
item.IsSelected =
true
;
return
true
;
}
bool
wasFound =
false
;
for
(
int
i = 0; i < parentItem.Items.Count; i++)
{
TreeViewItem itm = parentItem.ItemContainerGenerator.ContainerFromIndex(i)
as
TreeViewItem;
var found = SelectItem(o, itm);
if
(!found)
itm.IsExpanded =
false
;
else
wasFound =
true
;
}
return
wasFound;
}
}
}
And below is an example of how to implement it:
<
UserControl
x:Class
=
"SilverlightApplication2.MainPage"
xmlns:local
=
"clr-namespace:SilverlightApplication2.Helpers"
>
<
StackPanel
x:Name
=
"LayoutRoot"
>
<
StackPanel.Resources
>
<
sdk:HierarchicalDataTemplate
x:Key
=
"ChildTemplate"
ItemsSource
=
"{Binding Path=Children}"
>
<
TextBlock
Text
=
"{Binding Path=Name}"
/>
</
sdk:HierarchicalDataTemplate
>
<
sdk:HierarchicalDataTemplate
x:Key
=
"NameTemplate"
ItemsSource
=
"{Binding Path=Children}"
ItemTemplate
=
"{StaticResource ChildTemplate}"
>
<
TextBlock
Text
=
"{Binding Path=Name}"
FontWeight
=
"Bold"
/>
</
sdk:HierarchicalDataTemplate
>
</
StackPanel.Resources
>
<
sdk:TreeView
Width
=
"400"
Height
=
"300"
ItemsSource
=
"{Binding HierarchicalAreas}"
ItemTemplate
=
"{StaticResource NameTemplate}"
x:Name
=
"myTreeView"
local:Attached.TreeViewSelectedItem
=
"{Binding SelectedArea, Mode=TwoWay}"
/>
</
StackPanel
>
</
UserControl
>
This is a common request on MSDN WPF forum, so hope you find this when you need it.