Windows Presentation Foundation - TreeView Control
Language(s):C#, WPF
Category(s):TreeView

An example of using the Windows Presentation Foundation TreeView Control

WPF TreeView Control

Part 2

Part 3

This article will walk through a few examples of the Windows Presentation Foundation (WPF) TreeView Control. We will look at how to enter items statically as well as dynamically along with how to react to user input.

Static TreeView

A static TreeView control can be set up without any code using XAML. This can be done by nesting TreeViewItem tags inside a TreeView tag. For a text based tree, we can use the Header attribute to indicate the text of each node. A node can be set to initially display as open using the IsExpanded attribute. Here is an example of some XAML to create a simple static Tree:

        <TreeView Margin="12,12,12,58" Name="treeView1" Background="LightBlue">

            <TreeViewItem Header="Food" IsExpanded="True">

                <TreeViewItem Header="Fruit">

                    <TreeViewItem Header="Apple">

                        <TreeViewItem Header="Golden Delicious"/>

                        <TreeViewItem Header="Jonathan"/>

                        <TreeViewItem Header="McIntosh"/>

                    </TreeViewItem>

                    <TreeViewItem Header="Cherry">

                        <TreeViewItem Header="Bing"/>

                        <TreeViewItem Header="Black"/>

                        <TreeViewItem Header="Maraschino"/>

                    </TreeViewItem>

                </TreeViewItem>   

                    <TreeViewItem Header="Nuts">

                        <TreeViewItem Header="Pecan">

                        </TreeViewItem>

                    <TreeViewItem Header="Walnut">

                        <TreeViewItem Header="Black" />

                        <TreeViewItem Header="English" />

                    </TreeViewItem>

                </TreeViewItem>

                <TreeViewItem Header="Grain" IsExpanded="True">

                    <TreeViewItem Header="Corn">

                        <TreeViewItem Header="Black" />

                        <TreeViewItem Header="Red" />

                        <TreeViewItem Header="White" />

                        <TreeViewItem Header="Yellow" />

                    </TreeViewItem>

                    <TreeViewItem Header="Rice">

                        <TreeViewItem Header="Brown" />

                        <TreeViewItem Header="White" />

                    </TreeViewItem>

                    <TreeViewItem Header="Wheat" IsExpanded="True">

                        <TreeViewItem Header="Spring" />

                        <TreeViewItem Header="Summer" />

                    </TreeViewItem>

                </TreeViewItem>

            </TreeViewItem>

        </TreeView>

The XAML above will create a tree as shown in Figure 1:

Figure 1: WPF TreeView Control

Figure 1: WPF TreeView Control

Dynamically Loading a TreeView  

While a static TreeView might be useful in some circumstances, it will be more common to want to load a TreeView in code. Let's look at how we might load a tree like the one in Figure 1. For this example, we will populate the TreeView using the following class definition:

    internal class TreeViewData

    {

        private int _id = 0;

        internal int ID { get { return (_id); } }

        internal int Level { get; set; }

        internal bool Expanded { get; set; }

        internal string NodeText { get; set; }

 

 

 

 

 

 

 

        internal TreeViewData(int Level, bool Expanded, string NodeText)

        {

            _id++;

            this.Level = Level;

            this.Expanded = Expanded;

            this.NodeText = NodeText;

        }

    }

 

 

 

 

 

 

 

In a real world situation of course, the data would normally come from a database, but for this example we will just hard-code the values into a typed List object. The routine LoadTreeviewData (not shown here) does just that. Once the List has been loaded, we can load the TreeView as follows:

    private void LoadTreeview()

    {

        //Maximum level - zero based

        const int NUM_LEVELS = 4;

 

 

 

 

 

 

 

        //These will keep track of the parent node at each level

        TreeViewItem[] parent = new TreeViewItem[NUM_LEVELS];

 

 

 

 

 

 

 

        //Populate the list 

        List<TreeViewData> treeviewData = LoadTreeviewData();

 

 

 

 

 

 

 

        //Populate the treeview

        for (int i = 0; i < treeviewData.Count; i++)

        {

            //Grab this item

            TreeViewData data = (TreeViewData)treeviewData[i];

 

 

 

 

 

 

 

            //Create a new node

            TreeViewItem item = new TreeViewItem();

 

 

 

 

 

 

 

            //Set the text

            item.Header = data.NodeText;

 

 

 

 

 

 

 

            //Set the IsExpanded property

            item.IsExpanded = data.Expanded;

 

 

 

 

 

 

 

            //We can store the class itself in the tag property

            item.Tag = data;

           

            //Save this node 

            int level = data.Level;

            parent[level] = item;

 

 

 

 

 

 

 

            //Add this node to the tree or parent treeview item

            if (level == 0)

                //This is a top level node

                //added to the treeview itself

                tvFood.Items.Add(item);

            else

                //Here we are adding a node to an existing node

                //Note - we are assuming that there will be

                //no orphans in the list!

                parent[level - 1].Items.Add(item);

        }

    }

 

 

 

 

 

 

 

    private List<TreeViewData> LoadTreeviewData()

    {

        List<TreeViewData> treeviewData = new List<TreeViewData>();

        treeviewData.Add(new TreeViewData(0, true, "Food"));

        treeviewData.Add(new TreeViewData(1, false, "Fruit"));

        treeviewData.Add(new TreeViewData(2, false, "Apple"));

        treeviewData.Add(new TreeViewData(3, false, "Golden Delicious"));

        treeviewData.Add(new TreeViewData(3, false, "Jonathan"));

        treeviewData.Add(new TreeViewData(3, false, "McIntosh"));

        treeviewData.Add(new TreeViewData(2, false, "Cherry"));

        treeviewData.Add(new TreeViewData(3, false, "Bing"));

        treeviewData.Add(new TreeViewData(3, false, "Black"));

        treeviewData.Add(new TreeViewData(3, false, "Marachino"));

        treeviewData.Add(new TreeViewData(1, false, "Nuts"));

        treeviewData.Add(new TreeViewData(2, false, "Pecan"));

        treeviewData.Add(new TreeViewData(2, false, "Walnut"));

        treeviewData.Add(new TreeViewData(3, false, "Black"));

        treeviewData.Add(new TreeViewData(3, false, "English"));

        treeviewData.Add(new TreeViewData(1, true, "Grain"));

        treeviewData.Add(new TreeViewData(2, false, "Corn"));

        treeviewData.Add(new TreeViewData(3, false, "Black"));

        treeviewData.Add(new TreeViewData(3, false, "Red"));

        treeviewData.Add(new TreeViewData(3, false, "White"));

        treeviewData.Add(new TreeViewData(3, false, "Yellow"));

        treeviewData.Add(new TreeViewData(2, false, "Rice"));

        treeviewData.Add(new TreeViewData(3, false, "Brown"));

        treeviewData.Add(new TreeViewData(3, false, "White"));

        treeviewData.Add(new TreeViewData(2, true, "Wheat"));

        treeviewData.Add(new TreeViewData(3, false, "Spring"));

        treeviewData.Add(new TreeViewData(3, false, "Summer"));

        return (treeviewData);

    }

Events that Just Sit there and Work

Events in WPF are a big gotcha and the TreeView control is no exception. But first, let's look at an easy one. Trapping the SelectedItemEvent changed is one of the strait forward ones. You can simply define the method in the XAML and it will work as you would expect. Let's set up the event so that it will display the currently selected item in a status panel that I have conveniently defined on the form just for this purpose. To hook up the event, you can click on the TreeView tag and press F4 to bring up the properties window. Now click the Events icon, select the event and double-click. (See Figure 2).  

 

 

Figure 2 - Defining the SelectedItem changed event.
Figure 2 - Defining the SelectedItem changed event.

The XAML for the Status Bar will look something like the following:

        <StatusBar Name="stsMessages" VerticalAlignment="Bottom"

                   Height="20" Grid.Row="2" BorderBrush="Black"

                   BorderThickness="1" />

 

We can now write an event handler that will display the selected item in the Status Bar:

    private void tvFood_SelectedItemChanged(object sender,

        RoutedPropertyChangedEventArgs<object> e)

    {

        TreeViewItem item = (TreeViewItem)tvFood.SelectedItem;

        string message = String.Format("You selected: {0}", item.Header);

        if (stsMessages.Items.Count == 0)

            stsMessages.Items.Add(message);

        else

            stsMessages.Items[0] = message;

    }

 

 

 

 

 

 

 

Events that Drive You Nuts

Ok - if you have been tearing your hair out trying to get certain WPF events to fire, here is a glimmer of hope for you. For some reason, which must seem like a good idea to the WPF team at Microsoft, many events in WPF are not so strait forward as the SelectedItemChanged event. First of all, let's look at what's wrong with the SelectedItemChanged event. Often it is not enough to know when the selected item has changed. For example, you may want to fire an event when the user *clicks* a node on the TreeView. The SelectedItemChanged event will not serve this purpose in the event that the user clicks a node that is already selected. In this case, the selection has not changed, and the SelectedItemChanged event will not fire. No problem - we will just trap the Click event right? Wrong. There is no Click event for a TreeView control. Ok - still no problem. Looking at the list of TreeView events, we see that there is a MouseLeftButtonDown event. Surely this is the path to salvation - right? Wrong. Try it and you will be surprised to see that it does not fire. What's up with that? If you want to understand more about this, you should check out this really good article I found by Brian Noyes that explains why this is so: http://msdn.microsoft.com/en-us/magazine/cc785480.aspx. But if you ask me, there is no good reason for this - its nuts - pure and simple. It just doesn't work to define the event as we did for the SelectedItemChanged event and expect it to work. To make a long story short, the problem is that it will be handled under the covers and our code will never run. Instead, we must add the event handler in code, paying careful attention to pass a parm indicating that - yes - we actually DO want the event we are defining to actually run when the event is actually fired. Ok this is why we make the big bucks right? So let's stop whining and just write the code. First of all, let's look at another nasty little gotcha. If you already tried to do this from the properties window and have an event defined to trap the MouseLeftButtonDown event, you will have to change the signature slightly. The signature you get when you do that way looks like this:

private void tvFood_MouseLeftButtonDown(object sender, EventArgs e)

I had trouble getting that to work as it doesn't match the signature the AddHandler method wants - and since it won't work unless we invoke the AddHandler method in code, it seems like a good idea to be nice to the AddHandler method. Instead we want the second parameter to be of type EventArgs.

Now with the event handler written with the correct signature, we will need to connect it to the event using code:

  public Window1()

  {

      InitializeComponent();

      tvFood.AddHandler(UIElement.MouseDownEvent,

          new RoutedEventHandler(tvFood_MouseDown), true);

}

 

 

 

 

 

 

 

Note the last parameter on the AddHandler method. This tells WPF that we want the event to fire even if WPF has handled the event internally. Why this is not the default behavior is beyond me. Apparently the WPF team expects that after writing the method and explicitly hooking it to the control's event you won't actually want to use it. So the default behavior is that it won't work and you have to override this behavior with code.    

Once you run this, you will notice that clicking anywhere on the TreeView control will fire the method, whether it be on the text, the expand/contract node or on the scroll bar. You will probably want to act differently depending on which area the user has clicked in. Luckily, we can examine the Source and OriginalSource properties of the EventArgs parm. The following method shows how to do this:

    private void tvFood_MouseLeftButtonDown(object sender, EventArgs e)

    {

        //We can determine where on the control the click was received

        //by looking at the OrginalSource property

        string clickArea = ((RoutedEventArgs)e).OriginalSource.GetType().Name;

 

 

 

 

 

 

 

        if (clickArea == "TextBlock")

        {

            TreeViewItem item = (TreeViewItem)((RoutedEventArgs)e).Source;

            MessageBox.Show("You clicked on item: " + item.Header);

        }

        else

            MessageBox.Show("You clicked on the: " + clickArea);

    }

 

Notes: BTW - I should point out that another solution would have been to use the PreviewMouseLeftButtonDown event as well, which can be hooked up from XAML. It bugged me that the MouseRightButtonDown event wouldn't fire when I told it to, so I wanted to figure out. You must always beat the computer if you want to be a guru!

The source code for the program featured in this article can be downloaded by clicking on the link above.

From: cigdem - 2015-12-18
super!!! very good an clear example.

This article has been viewed 22464 times.
The examples on this page are presented "as is". They may be used in code as long as credit is given to the original author. Contents of this page may not be reproduced or published in any other manner what so ever without written permission from Idioma Software Inc.