Click here to Skip to main content
Click here to Skip to main content
Go to top

A Simple Treeview in Silverlight 2b1

, 14 May 2008
Rate this:
Please Sign up or sign in to vote.
Creating a templatable treeview in Silverlight

Introduction

First of all, please accept my apologies for my poor English. Smile | :)

This article discusses creation of a simple templatable treeview in Silverlight. My goal is to explain to the reader how to create a true control (and not a simple UserControl) from scratch by inheriting from Control and ContentControl classes. I want to show the power of INotifyCollectionChanged to manage items in a simple CRUD way to control internal data.

Template?

What is a template? This is a very simple thing according to the Microsoft Silverlight team. A template control is a widget with two parts: a logical part (.NET code) and a visual part (XAML code). For example, all of you know the Button widget; there are a lot of possible skins for a button whether you see it on Linux, Windows, WebSite, etc. But there is only one set of behavior for a button. Each behavior is called State. A button has:

  • Pressed State
  • Mouse Over State
  • Disabled State
  • Normal State

How does this state change? The logical part of a control manages this set of states by listening to external events such as mouse events. When, for example, a mouse clicks on our Button, the logical part tries to find in the Visual part a specific storyboard that will give to the Button an aspect of a pressed button and call it.

The relation between the logical part (your C# code) and the visual part (XAML code) is specified by a set of TemplatePartAttribute upon your class. If you open the Button class (Silverlight Framework) in reflector, you can see something like:

[TemplatePart(Name="Normal State", Type=typeof(Storyboard)), 
 TemplatePart(Name="MouseOver State", Type=typeof(Storyboard)), 
 TemplatePart(Name="RootElement", Type=typeof(FrameworkElement)), 
 TemplatePart(Name="Pressed State", Type=typeof(Storyboard)), 
 TemplatePart(Name="FocusVisualElement", Type=typeof(UIElement)), 
 TemplatePart(Name="Disabled State", Type=typeof(Storyboard))]
public class Button : ButtonBase
{
    // ...
    protected override void OnApplyTemplate();
}

A templatepart associates a key (a Name here) to a type. This key has to be associated in the logical part to a UIElement or a Storyboard. We can see two important parts here: "Pressed State" that is a Storyboard and RootElement which is a FrameworkElement. When the logical part intercepts a mouse click on the RootElement, it calls the Pressed State storyboard. This storyboard changes the aspect of UI elements specified in the visual parts. If you look at the OnApplyTemplate method in this class, you will see that the code makes a handle towards each of the parts defined here.

 protected override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    object templateChild = base.GetTemplateChild("RootElement");
    this._elementRoot = templateChild as FrameworkElement;
    this._elementFocusVisual = base.GetTemplateChild("FocusVisualElement")as UIElement;  
    if (this._elementRoot != null)
    {
        this._stateNormal = this._elementRoot.Resources["Normal State"]as Storyboard;
        this._stateMouseOver = 
		this._elementRoot.Resources["MouseOver State"]as Storyboard;
        this._stateMouseOver = obj5 as Storyboard;
        this._statePressed = this._elementRoot.Resources["Pressed State"]as Storyboard;
        this._stateDisabled = this._elementRoot.Resources["Disabled State"]as Storyboard;
     }
    base.UpdateVisualState();
} 

This method makes the link between the Visual and Logical part. With those members (like _elementRoot, _stateNormal, ...) the logical can lead the visual part without having to worry about its content. You can create your own Visual Part without any .NET code to change the behavior of your control. You just have to give to your UIElement and Storyboard in the template the same names as those indicated in templates parts.

The template of the Visual Part is defined in your resources. So inside the generic.xaml (the System.Windows.Control uses a generic.xaml) you can specify it inside the app.xaml or your usercontrol resources too, to make your own skin and save the default.

Sorry, this is very theoretical but necessary for the things that follow.

INotifyCollectionChanged

How does a control such as the ListBox control know when to update its UI when its databinded collection is changed?

The Microsoft Silverlight team uses a type named ObservableCollection that inherits from INotifyCollectionChanged. A control can subscribe to its event to know when the collection is changed by:

  • adding
  • removing
  • clearing
  • ...

Just take a loot with reflector to the listbox control. We can see that the ItemsSource property calls a method names ItemsSourceChanged when it is changed:

  ItemsSourceProperty = DependencyProperty.Register
	("ItemsSource", typeof(IEnumerable), typeof(ItemsControl),
 new PropertyChangedCallback(ItemsControl.ItemsSourceChanged));  

This method subscribes the collection behind ItemsSource to the call of a method named OnCollectionChange. In this method, the control can manage the modification made to its collection and update its visual aspect:

private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    switch (e.Action)
    {
        case NotifyCollectionChangedAction.Add:
            // ...
            this.UpdateContainerForItem(e.NewStartingIndex);
            return;
        case NotifyCollectionChangedAction.Remove:
            //...
            this.ClearContainerForItemOverride
		(elements[e.OldStartingIndex], e.OldItems[0] as UIElement);
            return;
        case NotifyCollectionChangedAction.Replace:
            //...
                this.ClearContainerForItemOverride
		(elements[e.NewStartingIndex], e.OldItems[0] as UIElement);
                elements.RemoveAt(e.NewStartingIndex);
                this.UpdateContainerForItem(e.NewStartingIndex);
            return;
        case NotifyCollectionChangedAction.Reset:
            this.ClearVisualChildren(this.GetItems());
             break;
        default:
            return;
    }
}

The code of this method is truncated to be "readable".

End of theory, let's start with the practical part.

My DOM

My TreeView control is based upon the two theorical things we just saw.

I have three important classes:

  • TreeNodeCollection which is used by a Node for its children. This class inherits from control.
  • TreeView that inherits from TreeNodeCollection.
  • TreeNode that inherits from ContentControl.

The treenode expects about 10 parts in its visual part:

[TemplatePart(Name = "Normal Expand State", Type = typeof(Storyboard)),
TemplatePart(Name = "Normal Collapse State", Type = typeof(Storyboard)),
TemplatePart(Name = "Selected Collapse State", Type = typeof(Storyboard)),
TemplatePart(Name = "Selected Expand State", Type = typeof(Storyboard)),
TemplatePart(Name = "NodeIcon Expand State", Type = typeof(Storyboard)),
TemplatePart(Name = "NodeIcon Collapse State", Type = typeof(Storyboard)),
TemplatePart(Name = "MouseOver Collapse State", Type = typeof(Storyboard)),
TemplatePart(Name = "MouseOver Expand State", Type = typeof(Storyboard)),
TemplatePart(Name = "RootElement", Type = typeof(FrameworkElement)),
TemplatePart(Name = "ExpandedNodeIconZone", Type = typeof(FrameworkElement)),
TemplatePart(Name = "ContentZone", Type = typeof(FrameworkElement)),
TemplatePart(Name = "NodesPresenter", Type = typeof(FrameworkElement)),
TemplatePart(Name = "SelectionZone", Type = typeof(FrameworkElement))]        

This will give you the opportunity to change your skin and the behavior with freedom.

My treeview is similar to the treeview class of Windows Forms with the same events and same methods.

I will add some others in the coming days.

public interface ITreeView
{
    event Arcane.Silverlight.Controls.TreeViewEventHandler AfterCollapse;
    event Arcane.Silverlight.Controls.TreeViewEventHandler AfterExpand;
    event Arcane.Silverlight.Controls.TreeViewEventHandler AfterSelect;
    event Arcane.Silverlight.Controls.TreeViewCancelEventHandler BeforeCollapse;
    event Arcane.Silverlight.Controls.TreeViewCancelEventHandler BeforeExpand;
    event Arcane.Silverlight.Controls.TreeViewCancelEventHandler BeforeSelect;
    event Arcane.Silverlight.Controls.TreeNodeMouseClickEventHandler NodeMouseClick;
    event Arcane.Silverlight.Controls.TreeNodeMouseClickEventHandler 
						NodeMouseDoubleClick;
    event Arcane.Silverlight.Controls.TreeNodeMouseHoverEventHandler NodeMouseHover;
    System.Windows.DataTemplate NodeTemplate { get; set; }
    Arcane.Silverlight.Controls.TreeNode SelectedNode { get; set; }
} 

The Sample

The sample shows two skins (the base skin and a WinForm skin) and two ways to insert data :

Arcane

First way, in the XAML:

 <src:TreeView Margin="10, 10, 10, 10"  x:Name="myTreeView"  Grid.Column="0" Grid.Row="0">
    <src:TreeNode Background="Transparent">
        <TextBlock Text="Hello"></TextBlock>
    </src:TreeNode>
    <src:TreeNode Background="Transparent">
        <TextBlock Text="This is a test for a long text node ! 
		Yeah, that's great !"></TextBlock>
    </src:TreeNode>
    <src:TreeNode Background="Transparent">
        <TextBlock Text="Node 2"></TextBlock>
        <src:TreeNode.Nodes>
            <src:TreeNodeCollection>
                <src:TreeNode>
                    <TextBlock Text="Node 2.1"></TextBlock>
                </src:TreeNode>
                <src:TreeNode>
                    <TextBlock Text="Node 2.2"></TextBlock>
                </src:TreeNode>
                <src:TreeNode>
                    <TextBlock Text="Node 2.3"></TextBlock>
                    <src:TreeNode.Nodes>
                        <src:TreeNodeCollection>
                            <src:TreeNode>
                                <TextBlock Text="Node 2.3.1"></TextBlock>
                            </src:TreeNode>
                            <src:TreeNode>
                                <TextBlock Text="Node 2.3.2"></TextBlock>
                            </src:TreeNode>
                            <src:TreeNode>
                                <TextBlock Text="Node 2.3.3"></TextBlock>
                            </src:TreeNode>
                        </src:TreeNodeCollection>
                    </src:TreeNode.Nodes>
                </src:TreeNode>
                <src:TreeNode>
                    <TextBlock Text="Node 2.4"></TextBlock>
                </src:TreeNode>
                <src:TreeNode>
                    <TextBlock Text="Node 2.5"></TextBlock>
                </src:TreeNode>
                <src:TreeNode>
                    <TextBlock Text="Node 2.1"></TextBlock>
                </src:TreeNode>
            </src:TreeNodeCollection>
        </src:TreeNode.Nodes>
    </src:TreeNode>
    <src:TreeNode Background="Transparent">
        <TextBlock Text="Node 3"></TextBlock>
        <src:TreeNode.Nodes>
            <src:TreeNodeCollection>
                <src:TreeNode>
                    <TextBlock Text="Node 3.1"></TextBlock>
                </src:TreeNode>
                <src:TreeNode>
                    <TextBlock Text="Node 3.2"></TextBlock>
                </src:TreeNode>
                <src:TreeNode>
                    <TextBlock Text="Node 3.3"></TextBlock>
                </src:TreeNode>
            </src:TreeNodeCollection>
        </src:TreeNode.Nodes>
    </src:TreeNode>
    <src:TreeNode Background="Transparent">
        <TextBlock Text="Node 4"></TextBlock>
    </src:TreeNode>
    <src:TreeNode Background="Transparent">
        <TextBlock Text="Node 5"></TextBlock>
    </src:TreeNode>
    <src:TreeNode Background="Transparent">
        <TextBlock Text="Node 6"></TextBlock>
    </src:TreeNode>
    <src:TreeNode Background="Transparent">
        <TextBlock Text="Node 7"></TextBlock>
    </src:TreeNode>
    <src:TreeNode Background="Transparent">
        <TextBlock Text="Node 8"></TextBlock>
        <src:TreeNode.Nodes>
            <src:TreeNodeCollection>
                <src:TreeNode>
                    <TextBlock Text="Node 8.1"></TextBlock>
                </src:TreeNode>
                <src:TreeNode>
                    <TextBlock Text="Node 8.2"></TextBlock>
                    <src:TreeNode.Nodes>
                        <src:TreeNodeCollection>
                            <src:TreeNode>
                                <TextBlock Text="Node 8.2.1"></TextBlock>
                            </src:TreeNode>
                            <src:TreeNode>
                                <TextBlock Text="Node 8.2.2"></TextBlock>
                            </src:TreeNode>
                            <src:TreeNode>
                                <TextBlock Text="Node 8.2.3"></TextBlock>
                            </src:TreeNode>
                        </src:TreeNodeCollection>
                    </src:TreeNode.Nodes>
                </src:TreeNode>
                <src:TreeNode>
                    <TextBlock Text="Node 8.3"></TextBlock>
                    <src:TreeNode.Nodes>
                        <src:TreeNodeCollection>
                            <src:TreeNode>
                                <TextBlock Text="Node 8.3.1"></TextBlock>
                            </src:TreeNode>
                            <src:TreeNode>
                                <TextBlock Text="Node 8.3.2"></TextBlock>
                            </src:TreeNode>
                        </src:TreeNodeCollection>
                    </src:TreeNode.Nodes>
                </src:TreeNode>
            </src:TreeNodeCollection>
        </src:TreeNode.Nodes>
    </src:TreeNode>
    <src:TreeNode Background="Transparent">
        <TextBlock Text="Node 9"></TextBlock>
    </src:TreeNode>
 </src:TreeView>

and inside the code:

this.treeview.BeforeExpand += new TreeViewCancelEventHandler(treeview_BeforeExpand);
//a business data
SampleNodeData data = new SampleNodeData();
data.Name = "Adventures works !";
BitmapImage image = new BitmapImage();
image.UriSource = new Uri(HtmlPage.Document.DocumentUri.AbsoluteUri.Replace(
                             "Arcane.Silverlight.ControlsTestPage.aspx", "database.png"));
data.NodeImage = image;
image = new BitmapImage();
image.UriSource = new Uri(HtmlPage.Document.DocumentUri.AbsoluteUri.Replace(
                         "Arcane.Silverlight.ControlsTestPage.aspx", "databaseopen.png"));
data.SelectedNodeImage = image;

TreeNode node = this.treeview.Add(data);
node.Nodes = new TreeNodeCollection();
node.Tag = "Database"; 

with a DataTemplate to show the simple business data.

 <src:TreeView x:Name="myTreeViewDataBinded" 
              Margin="10, 10, 10, 10" Background="White"  
              ItemContainerStyle="{StaticResource WinFormTreeView}"
              Grid.Column="1" Grid.Row="0" Width="300" Height="300">
    <src:TreeView.ItemTemplate>
        <DataTemplate>
            <Grid  Background="Transparent">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Column="0" Text="{Binding Name}"/>
            </Grid>
        </DataTemplate>
    </src:TreeView.ItemTemplate>
 lt;/src:TreeView>

Then to finish, I set two skins in my treeview: the first is inside the generic.Xaml. This is the default skin used for the first treeview (on the left). The second more beautiful one is the custom skin I make inside the app.xaml file. You can create your own very easily.

You can ask me all the questions you want, I am better at reading English than at writing it. Smile | :)

History

Changes made on TreeNode.Nodes property: set is now public.

I will update my work as soon as I can. The DOM will not change, but there will be additions.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Valentin Billotte
Architect Viablue - GraphicStream
France France
I spent most of time on Silverlight, Xna (where i am MVP) and ADO.Net Data Services.

Comments and Discussions

 
GeneralError related to MouseEventArgs Pinmemberabhishekvashistha21-Dec-08 23:42 
QuestionTon code fonctionne avec SL 2 Beta 2? Pinmemberfxea141016-Oct-08 10:39 
GeneralAuthor Valentin Billotte, please help me! Pinmemberkhunglongsaigon13-Oct-08 17:16 
QuestionCan not load the child nodes! Pinmemberkhunglongsaigon12-Oct-08 21:00 
QuestionCan anybody share me the code about how to bind data to the TreeView? Pinmemberkhunglongsaigon12-Oct-08 20:48 
QuestionDoes this work in Silverlight 2 RC0? Pinmemberhotprogster30-Sep-08 13:48 
Generalanother one PinmemberUnruled Boy26-Sep-08 16:32 
Generalcheckbox to the tree control Pinmembersudarshan.deepty7-Jul-08 20:13 
QuestionAny other SL2 tree control alternatives besides vectorlight? Pinmembertntomek20-Jun-08 9:36 
Questiongot some error when run the souce code Pinmemberpuneet_k0719-Jun-08 0:35 
AnswerRe: got some error when run the souce code PinmemberSupply Chain19-Jun-08 12:45 
QuestionRe: got some error when run the souce code Pinmemberpuneet_k0719-Jun-08 18:06 
GeneralHere is what needs to be done to make the TreeView control work under Beta 2 [modified] PinmemberNick Polyak12-Jun-08 8:54 
GeneralRe: Here is what needs to be done to make the TreeView control work under Beta 2 Pinmemberhotprogster12-Jun-08 17:07 
GeneralRe: Here is what needs to be done to make the TreeView control work under Beta 2 Pinmemberoxospaz19-Jun-08 16:16 
GeneralNot working Silverlight 2 Beta 2 Pinmemberhotprogster11-Jun-08 12:43 
GeneralRe: Not working Silverlight 2 Beta 2 PinmemberNick Polyak12-Jun-08 4:40 
QuestionDisplaying multiple levels of the tree ( just like in Expand all) PinmemberSupply Chain4-Jun-08 11:53 
AnswerRe: Displaying multiple levels of the tree ( just like in Expand all) PinmemberValentin Billotte8-Jun-08 22:38 
GeneralRe: Displaying multiple levels of the tree ( just like in Expand all) PinmemberNeil Moore30-Jul-08 3:47 
QuestionHow do I reference this project in my solution? PinmemberKeithR9921-May-08 15:19 
AnswerRe: How do I reference this project in my solution? PinmemberKeithR9921-May-08 15:57 
GeneralRe: How do I reference this project in my solution? PinmemberValentin Billotte22-May-08 5:29 
GeneralRe: How do I reference this project in my solution? PinmemberjoeyCooly22-Sep-08 21:38 
GeneralTreeview nodes collection PinmemberZoltan Kalman14-May-08 5:53 
GeneralRe: Treeview nodes collection PinmemberValentin Billotte14-May-08 21:06 
GeneralRe: Treeview nodes collection PinmemberZoltan Kalman15-May-08 0:34 
QuestionHow do i put this into a Class Libray project, Then just reference as a DLL from my Silverlight project? PinmemberLebza4Sure12-May-08 3:27 
AnswerRe: How do i put this into a Class Libray project, Then just reference as a DLL from my Silverlight project? PinmemberValentin Billotte12-May-08 4:32 
GeneralRe: How do i put this into a Class Libray project, Then just reference as a DLL from my Silverlight project? PinmemberLebza4Sure13-May-08 0:35 
AnswerRe: How do i put this into a Class Libray project, Then just reference as a DLL from my Silverlight project? PinmemberLebza4Sure13-May-08 3:34 
GeneralRe: How do i put this into a Class Libray project, Then just reference as a DLL from my Silverlight project? PinmemberValentin Billotte13-May-08 4:17 
GeneralGood job sir PinmvpSacha Barber6-May-08 23:48 
AnswerRe: Good job sir PinmemberValentin Billotte7-May-08 1:18 
GeneralRe: Good job sir PinmvpSacha Barber7-May-08 5:21 
GeneralDon't apologise for your english PinmemberDanielDyson6-May-08 12:09 
AnswerRe: Don't apologise for your english PinmemberAnpeCodeur6-May-08 21:45 
GeneralVery Nice! PinmemberDavid J Jon3-May-08 1:58 
AnswerRe: Very Nice! PinmemberMember 35010273-May-08 2:00 
GeneralThis is quite good! PinmemberDewey30-Apr-08 23:27 
AnswerRe: This is quite good! PinmemberMember 35010271-May-08 22:19 
GeneralGreat work! Pinmembermatkwan16829-Apr-08 15:33 
GeneralRe: Great work! PinmemberMember 350102729-Apr-08 21:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140926.1 | Last Updated 15 May 2008
Article Copyright 2008 by Valentin Billotte
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid