5,693,062 members and growing! (21,266 online)
Email Password   helpLost your password?
Web Development » Silverlight » General     Advanced License: The Code Project Open License (CPOL)

A simple treeview in Silverlight 2b1

By Valentin Billotte

Créating a templatable treeview in silverlight
C#

Posted: 23 Apr 2008
Updated: 14 May 2008
Views: 26,332
Bookmarked: 34 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
30 votes for this Article.
Popularity: 6.77 Rating: 4.58 out of 5
2 votes, 6.7%
1
1 vote, 3.3%
2
1 vote, 3.3%
3
3 votes, 10.0%
4
23 votes, 76.7%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article
Download Arcane.Silverlight.zip - 1.31 MB

Introduction

First of all, please accept my appologizes for my poor english :)

This article speak about creating 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 inherits from Control and ContentControl class. I want to show the power of the 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 knows the Button widget ; there is 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 have

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

How this states change ? The logicial part of a control manage this set of states by listening external events such a the mouse events. When, for example, a mouse click on our Button, the logical part try 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 logicial 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 associate a key (a Name here) to a type. This key have to by associated in the logical part to an UIElement or a Storyboard. We can see here two important parts : "Pressed State" that is a Storyboard and RootElement which is a FrameworkElement. When the logical part intercept a mouse click on the RootElement, it call the Pressed State storyboard. This storyboard change 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 make a handle toward 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 make the link between Visual and Logical part. With thoses 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 templatesparts.

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

Sorry this is very theorical but neccesary for next things.

INotifyCollectionChanged

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

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

  • an adding
  • a remove
  • a clear
  • ...

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

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

This method subscribe 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, lets start pract

My DOM

My TreeView control is based uppon the two theorical things we just see.

I have three important classes :

TreeNodeCollection witch is used by a Node for its childs. This class inherist from control.

TreeView that inherit from TreeNodeCollection.

TreeNode that inherits from ContentControl.

The treenode expect 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 opertunity 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 next 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>
        </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 is the custom skin a make inside the app.xaml file. You can create your own very easely.

You can ask me all the question you want, i am better to read english than to write it :)

History

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

I will update my work i 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)

About the Author

Valentin Billotte


I spent most of time on Silverlight, Xna (where i am MVP) and ADO.Net Data Services.
Occupation: Architect
Company: Exakis
Location: France France

Other popular Silverlight articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 25 of 42 (Total in Forum: 42) (Refresh)FirstPrevNext
GeneralTon code fonctionne avec SL 2 Beta 2?memberfxea141011:39 16 Oct '08  
GeneralAuthor Valentin Billotte, please help me!memberkhunglongsaigon18:16 13 Oct '08  
GeneralCan not load the child nodes!memberkhunglongsaigon22:00 12 Oct '08  
GeneralCan anybody share me the code about how to bind data to the TreeView?memberkhunglongsaigon21:48 12 Oct '08  
GeneralDoes this work in Silverlight 2 RC0?memberhotprogster14:48 30 Sep '08  
Generalanother onememberUnruled Boy17:32 26 Sep '08  
Generalcheckbox to the tree controlmembersudarshan.deepty21:13 7 Jul '08  
GeneralAny other SL2 tree control alternatives besides vectorlight?membertntomek10:36 20 Jun '08  
Questiongot some error when run the souce codememberpuneet_k071:35 19 Jun '08  
AnswerRe: got some error when run the souce codememberSupply Chain13:45 19 Jun '08  
QuestionRe: got some error when run the souce codememberpuneet_k0719:06 19 Jun '08  
GeneralHere is what needs to be done to make the TreeView control work under Beta 2 [modified]memberNick Polyak9:54 12 Jun '08  
GeneralRe: Here is what needs to be done to make the TreeView control work under Beta 2memberhotprogster18:07 12 Jun '08  
GeneralRe: Here is what needs to be done to make the TreeView control work under Beta 2memberoxospaz17:16 19 Jun '08  
GeneralNot working Silverlight 2 Beta 2memberhotprogster13:43 11 Jun '08