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).
Just download, unzip, open and run!
You'll also need the Silverlight 4 SDK for the TreeView, which only comes from the SDK.
If you use a previous version of the SDK, just change the reference in the project.
The problem with the TreeView is that the SelectedItem is a readonly property. In silverlight, you don't even have OneWayToSource, so binding two way to SelectedItem (which does exist in XAML, even if intelisense 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 it's 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 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" 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 I hope you find this when you need it.
using
System.Windows;
System.Windows.Controls;
namespace
SilverlightTreeview
{
public
class
Attached
static
object
GetTreeViewSelectedItem(DependencyObject obj)
return
(
)obj.GetValue(TreeViewSelectedItemProperty);
}
void
SetTreeViewSelectedItem(DependencyObject obj,
value)
obj.SetValue(TreeViewSelectedItemProperty, value);
readonly
DependencyProperty TreeViewSelectedItemProperty =
DependencyProperty.RegisterAttached(
"TreeViewSelectedItem"
,
typeof
),
(Attached),
new
PropertyMetadata(
(), TreeViewSelectedItemChanged));
TreeViewSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
TreeView treeView = sender
as
TreeView;
if
(treeView ==
null
)
;
treeView.SelectedItemChanged -=
RoutedPropertyChangedEventHandler<
>(treeView_SelectedItemChanged);
treeView.SelectedItemChanged +=
TreeViewItem thisItem = treeView.ItemContainerGenerator.ContainerFromItem(e.NewValue)
TreeViewItem;
(thisItem !=
thisItem.IsSelected =
true
for
int
i = 0; i < treeView.Items.Count; i++)
SelectItem(e.NewValue, treeView.ItemContainerGenerator.ContainerFromIndex(i)
TreeViewItem);
treeView_SelectedItemChanged(
sender, RoutedPropertyChangedEventArgs<
> e)
SetTreeViewSelectedItem(treeView, e.NewValue);
private
bool
SelectItem(
o, TreeViewItem parentItem)
(parentItem ==
false
isExpanded = parentItem.IsExpanded;
(!isExpanded)
parentItem.IsExpanded =
parentItem.UpdateLayout();
TreeViewItem item = parentItem.ItemContainerGenerator.ContainerFromItem(o)
(item !=
item.IsSelected =
wasFound =
i = 0; i < parentItem.Items.Count; i++)
TreeViewItem itm = parentItem.ItemContainerGenerator.ContainerFromIndex(i)
var found = SelectItem(o, itm);
(!found)
itm.IsExpanded =
else
wasFound;
<
UserControl
xmlns
=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
"http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sdk
"http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
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}"
/>
</
"NameTemplate"
ItemTemplate
"{StaticResource ChildTemplate}"
FontWeight
"Bold"
sdk:TreeView
Width
"400"
Height
"300"
"{Binding HierarchicalAreas}"
"{StaticResource NameTemplate}"
"myTreeView"
local:Attached.TreeViewSelectedItem
"{Binding SelectedArea, Mode=TwoWay}"