Simple Tree Part 1
For those of you that have been enjoying some of the features in the Silverlight Toolkit over on codeplex like I have been you may have noticed the same things that I have about it. First it works but thats a given after all its made by the same company that brought you Songsmith, and since its from Microsoft you know its easy to use.
But other than that it does way more with visual states then the average developer can probably understand given our limited understanding of the ever so complicated art of making things look pretty. More over it works like a champ if you want to use it with a limited number of levels of depth but as far as I can see with out looking at source for it there is no way for it to automatically go to the n level of depth on its own. So for us data driven guys it wets the appetite but leaves us feeling totally unsatisfied much like running any version of 7 other than Ultimate.
I would like to present a simple solution to give those of you looking for a simple solution to avoid defining a template for each level of depth in you tree. This post is part 1 of a hopeful 3 part look at how to make a slick DynamicTreeView. In part 1 I'll show you how to make the basic tree saving the styling for part 2. In part 3 we will roll the entire thing in to nice reusable UserControl that allows you easily make this fit into your application.
For this demo go ahead an make yourself a new Silverlight Application or Silverlight Class Library in Visual Studio 2008 either will do. I am going to use Silverlight Application so that I can just hit F5 to preview at any time but the Solution that I'll post with this will be a Silverlight Class Library. Now go ahead and make 2 User Controls AutoNode.xaml and DynamicTreeView.xaml. AutoNode is where all the magic happens and DynamicTreeView is the finished product that will work like the current TreeView in the Silverlight Toolkit.
At this point you should have a solution similar to the one on the left.
Lets start with DynamicTreeView.xaml and put an ItemsControl in LayoutRoot like this.
x:Class="SilverlightApplication1.DynamicTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="200" Height="400">
<Grid x:Name="LayoutRoot" Background="Black">
<ItemsControl />
</Grid>
</UserControl>
Go ahead an build you solution so that everything will be ready to work with Blend when we open it.
For those of you with Expression Blend Lets right click on the DynamicTreeView.xaml in our Solution Explorer and open it with Expression Blend (you can do this with out Expression Blend but for god sakes use Expression Blend cause you don't get extra points for doing it the hard way) on the Objects and Timelines area lets right click on our ItemsControl then go to
Edit Additional Templates -> Edit Generated Items Template (ItemTemplate) -> Create Empty
like shown below.


This is creating the template that will house all of the top level tree items for our tree view. You'll be presented with the dialog shown above enter the name you want to call this template I am using RootTreeViewItem. Once you click ok you will be presented with a empty template that has a grid and nothing else. At the far left toolbar select the assets icon and type "auto", you should be able to select the AutoNode UserControl and add it to the grid and clear any attributes for the AutoNode in the XAML.

At this point your XAML should now have a pretty simple data template added to it like the following
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SilverlightApplication1" x:Class="SilverlightApplication1.DynamicTreeView"
Width="200" Height="400">
<UserControl.Resources>
<DataTemplate x:Key="RootTreeViewItem">
<Grid>
<local:AutoNode/>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="Black">
<ItemsControl ItemTemplate="{StaticResource RootTreeViewItem}" />
</Grid>
</UserControl>
OK next up lets open up AutoNode.xaml and finish the basic function of this tutorial. I am not going to go over the details of the layout as teaching basics of layouts in Silverlight is beyond the scope of this tutorial so lets use this XAML. Interesting note I was seemingly unable to add a instance of AutoNode to a template that lived on AutoNode using drag and drop with Expression Blend (guessing that this is a safe guard against accidental recursion. If anyone can confirm this I would appreciate it.)
x:Class="SilverlightApplication1.AutoNode"
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"
d:DesignWidth="273"
d:DesignHeight="87"
VerticalContentAlignment="Top">
<UserControl.Resources>
<DataTemplate x:Key="Children">
<auto:AutoNode VerticalAlignment="Top" />
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="21"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="1" />
<ItemsControl Grid.Column="1" Grid.Row="1" ItemTemplate="{StaticResource Children}" />
</Grid>
</UserControl>
But the above is somewhat simular to one of the templates used for the TreeViewItem in the Silverlight Toolkit. With just a little bit of data binding this will populate a Tree of nodes that conforms to the same convention for each level with no limit of depth. (short of running out of memory in theory)
I used the following Simple class that would mimic a file system recursive directory listing. If your running a x64 system the paths used should map to real paths on your system. The FileNode class is intended to eventually provide the needed data so that this tree view by the end of part 3 will be able to visually identify if a node is a file or a directory with a different icon.
{
public string DisplayValue { get; set; }
public string Path { get; set; }
public bool IsFolder { get; set; }
private ObservableCollection _children = new ObservableCollection();
public ObservableCollection Children
{
get { return _children; }
set { _children = value; }
}
public FileNode() {}
}
public class FileSystemHelper
{
private ObservableCollection nodes = new ObservableCollection();
public FileSystemHelper()
{
Nodes.Add(new FileNode()
{
DisplayValue = "Windows",
Path = "C:\\Windows",
IsFolder = true
});
Nodes[0].Children.Add(new FileNode()
{
DisplayValue = "Microsoft.NET",
Path = "C:\\Windows\\Microsoft.NET",
IsFolder = true
});
Nodes[0].Children[0].Children.Add(new FileNode()
{
DisplayValue = "Framework64",
Path = "C:\\Windows\\Microsoft.NET\\Framework64",
IsFolder = true
});
Nodes[0].Children[0].Children.Add(new FileNode()
{
DisplayValue = "Framework",
Path = "C:\\Windows\\Microsoft.NET\\Framework",
IsFolder = true
});
Nodes[0].Children[0].Children[0].Children.Add(new FileNode()
{
DisplayValue = "v3.5",
Path = "C:\\Windows\\Microsoft.NET\\Framework64\\v3.5",
IsFolder = true
});
Nodes[0].Children[0].Children[0].Children.Add(new FileNode()
{
DisplayValue = "sbscmp10.dll",
Path = "C:\\Windows\\Microsoft.NET\\Framework64\\v3.5\\sbscmp10.dll",
IsFolder = false
});
Nodes[0].Children[0].Children[0].Children.Add(new FileNode()
{
DisplayValue = "sbscmp20_mscorwks.dll",
Path = "C:\\Windows\\Microsoft.NET\\Framework64\\v3.5\\sbscmp20_mscorwks.dll",
IsFolder = false
});
}
public ObservableCollection<FileNode> Nodes
{
get { return nodes; }
set { nodes = value; }
}
}
}
Currently we only care about the DisplayValue, Path, and Children properties so lets go ahead and add the data binding to AutoNode.xaml. Change the TextBlock and the ItemsControl to the following
<ItemsControl Grid.Column="1" Grid.Row="1" ItemTemplate="{StaticResource Children}" ItemsSource="{Binding Children}"/>
The Last step that we need to add a data context to the grid on DynamicTreeView.xaml and set an ItemsSource binding for the ItemsControl below is what the final DynamicTreeView.xaml should look like.
x:Class="SilverlightApplication1.DynamicTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SilverlightApplication1"
Width="300" Height="400">
<UserControl.Resources>
<DataTemplate x:Key="RootTreeViewItem">
<Grid>
<local:AutoNode/>
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="Black">
<Grid.DataContext>
<local:FileSystemHelper/>
</Grid.DataContext>
<ItemsControl ItemTemplate="{StaticResource RootTreeViewItem}" ItemsSource="{Binding Nodes}" />
</Grid>
</UserControl>
Lets not forget to add the DynamicTreeView to the MainPage.xaml so it shows up when we hit preview.
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SilverlightApplication1"
x:Class="SilverlightApplication1.MainPage"
Foreground="White">
<Grid HorizontalAlignment="Left" VerticalAlignment="Top">
<local:DynamicTreeView />
</Grid>
</UserControl>

To the left you can see that we currently have a stunningly bland proof of concept for our recursive TreeView. Its worth noting that there is not one line of programmatic code that is making this happen (other than the code to generate the items to display but with some work you can make the AutoNode use reflection to check to see if its DataContext has any property that implements ICollection/IList/IEnumerable and if so try to map that to the children field.)
Now you must be thinking OK but what can I do with this? Well I was thinking that myself after this was done and so here is an example of what to do with it. With a few changes to the code we had that defined the FileSystemHelper and FileNode classes we can make VisualFamilyTree and FrameworkElementNode classes as below.
/// Modified from FileSystemHelper
/// </summary>
public class VisualFamilyTree
{
private ObservableCollection<FrameworkElementNode> nodes = new ObservableCollection<FrameworkElementNode>();
public VisualFamilyTree() {}
/// <summary>
/// This method will be called after the rootElement has fully loaded and is ready to be transversed
/// </summary>
/// <param name="rootElement"></param>
public void Populate(FrameworkElement rootElement)
{
Nodes.Clear(); // this is done to make the tree only ever have one node
Nodes.Add(new FrameworkElementNode(rootElement));
}
public ObservableCollection<FrameworkElementNode> Nodes
{
get { return nodes; }
}
}
/// <summary>
/// Modified from FileNode
/// </summary>
public class FrameworkElementNode
{
public string DisplayValue { get; set; }
public string Path { get; set; }
private ObservableCollection<FrameworkElementNode> _children = new ObservableCollection<FrameworkElementNode>();
public ObservableCollection<FrameworkElementNode> Children
{
get { return _children; }
set { _children = value; }
}
public FrameworkElementNode() { }
/// <summary>
/// constructor that auto builds off of a FrameworkElement
/// </summary>
public FrameworkElementNode(FrameworkElement fe)
{
// how many child nodes does this element have that are direct children of this FrameworkElement
int childNodes = VisualTreeHelper.GetChildrenCount(fe);
if(childNodes> 0)
for (int i = 0; i < childNodes; i++)
{
// recursively call this for each FrameworkElement that is a child element of the FrameworkElement fe
Children.Add(new FrameworkElementNode(VisualTreeHelper.GetChild(fe, i) as FrameworkElement));
}
// using path for name so we didn't have to make any changes to the AutoNode.xaml
Path = fe.Name;
// the node "header" will be x:Name ( type of this FrameworkElement )
DisplayValue = Path+ "( " + fe.GetType().Name + " )";
}
}
The goal of this is to make our recursive TreeView build in real time from the live code that is running in browser to give us the same kind visual tree that we have in Expression Blend at design time. Looking below you can see a quick collection of controls that I threw on a page. A quick search didn't bring up anyone doing anything like this in real time running in Silverlight. The closest that I could find to doing something like this was Silverlight Spy. Granted currently it only looks for simple parent child relationships and wont peek into templates of any kind (though that should be pretty straight forward to implement) on the other hand the code to make the live tree only took like 10 min so its a fair trade I guess.
Now here is a screen shot of it running live in Silverlight and note that the tree is the same.

Download the full solution for Here.







































