Powered by MSDN

US - English
NEW! Silverlight 5 is available Learn More

  • Je8

    Je8

    Member

    54 Points

    50 Posts

    Re: Toolkit treeview questions

    Nov 01, 2008 06:23 PM | LINK

     OK - here is a programmatic solution to the problem. I hope this might save someone else 3 or 4 days of experimentation.

    1. The first thing is to set up a class to represent the data that is going to be viewed - the fact that this control is called a TreeView is, I think, no accident. It is quite different to a Win32/WinForms control. As my original question concerned email let's stick with that. Here is the class that we'll use.

         // this will be displayed in the Silverlight TreeView
        public class EmailItem
        {
            // constructor takes various arguments as one might expect
            public EmailItem(string Title, string From,DateTime Date)
            {
                // by default a root item so no parent
                this.Parent = null;
                // the observable collection is very important here, it means that an instance of this class
                // will get notified if the collection changes,
                // as well as the container getting notified of changes - see the Add() code later.
                this.Items = new ObservableCollection<EmailItem>();
                // this is for sanity checking and general proof of understanding
                this.Items.CollectionChanged +=
                    new NotifyCollectionChangedEventHandler(Items_CollectionChanged);
                // assign the constructor parameters                
                this.Title = Title;
                this.From = From;
                this.Date = Date;
            }
            // the collection has changed - just spew a debug message for the moment
            void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                if (sender != null)
                {
                    Debug.WriteLine("Items_CollectionChanged: " + sender.ToString() + "(" + e.Action.ToString() + ")");
                }
            }
            // our email properties.
            public string Title { get; set; }
            public string From { get; set; }
            public DateTime Date { get; set; }
            public EmailItem Parent { get; set;}  // the parent emailItem - enables us to work back up through the hierarchy
            // this is special - this collection will contain all of the
            // emails which are children in the hierachy. Imagine them to be
            // the collected replies to a specific email. The depth of the
            // hierarchy is bounded only by systems limits.
            public ObservableCollection<EmailItem> Items { get; set; }
        }

    2.  Now we need to tell the tree view what is it going to display. The XAML here totallly dictates the appearance of the item in the view. This XAML snippet places a tree view on the layout and determines the content. Somewhere you will need to have added a namespace to the XAML referencing the Silverlight toolkit (SLTK)

           xmlns:controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"

    So any SLTK controls are prefixed with a controls: namespace qualifier

            <controls:TreeView Margin="8,80,102,78" x:Name="trvEmails" SelectedItemChanged="ctlTree_SelectedItemChanged">
                <controls:TreeView.ItemTemplate>
                    <controls:HierarchicalDataTemplate x:Name="EmailItemTemplate" ItemsSource="{Binding Items}">
                        <StackPanel Orientation="Horizontal">
                            <controls:Label  Width="80" Content="{Binding Title}"/>
                            <controls:Label  Width="80" Content="{Binding From}"/>
                            <controls:Label Foreground="Gray" Content="{Binding Date}"/>
                        </StackPanel>
                    </controls:HierarchicalDataTemplate>
                </controls:TreeView.ItemTemplate>

    In English then, we've got a  TreeView called trvEmails with an event handler called ctlTree_SelectedItemChanged that is invoked when the selection changes in the tree.

    Next comes the clever/tricky bit - the ItemTemplate. This contains a HierarchicalDataTemplate declaration which has as it source binding a property called Items. This is bound directly to the Items property in the EmailItem class. Importantly Items is not directly viewable - the bits of the email message we wish to display is specified by the StackPanel. Note I've chosen to orientate this horizontally - this means we get a columnar layout for the EmailItem with the content described by the 3 labels that follow. Note that each label is bound to a separate property of the EmailItem class - in turn Title, From and Date. Just to show we are in control, the date is drawn as greyed text.

    That, as they say, is that. Let's move on to the code.

    3. Now if you have added the C# code and the XAML markup to a project, the tree, at runtime, will be empty. Here's some code to populate the tree:

            // I've added this code to the page constructor

            public Page()
            {
                InitializeComponent();
                // add the first new email item to the root collection
                EmailItem emi = new EmailItem("Email 1","Root", DateTime.Now);
                trvEmails.Items.Add(emi);
                // add a 'reply' to the first email
                emi.Items.Add(new EmailItem("Email 1.1", "abc", DateTime.Now));
                // add another root item
                emi = new EmailItem("Email 2", "Root", DateTime.Now);
                // insert it into the view collection
                trvEmails.Items.Add(emi);
                // and add a reply to the second item
                emi.Items.Add(new EmailItem("Email 2.1", "abc",DateTime.Now));
                // finally a third root item
                trvEmails.Items.Add(new EmailItem("Email 3", "def", DateTime.Now));
            }

    This should be pretty self explanatory - what you get then is a tree that initially looks like this - note the arrows indicating that the item has children. Magic!

    TrreView 1

    and here is the tree with the items expanded:

    Expanded Treeview 

    4.  Neat. Let's code up the event handler that is called when the tree selection changes:

            private void ctlTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
            {
                Debug.WriteLine("ctlTree_SelectedItemChanged");
                EmailItem oldItem = e.OldValue as EmailItem;
                EmailItem newItem = e.NewValue as EmailItem;
                // _selectedEmailItem is declared within the class as:
                // private EmailItem _selectedEmailItem;
                _selectedEmailItem = newItem;
                // see next paragraph
                btnAdd.IsEnabled = (_selectedEmailItem != null);
                if (oldItem != null)
                {
                    Debug.WriteLine("Old: " + oldItem.Title);
                }
                if (newItem != null)
                {
                    EmailItem emi = newItem;
                    while (emi != null)
                    {
                        Debug.WriteLine("Walking back to root: " + emi.Title);
                        emi = emi.Parent;
                    }
                }
            }

    This handler updates an EmailItem instance (_selectedEmailItem) that is declared within the page code. This makes life a bit simpler in a moment,as I hope to show. Once we are sure we've updated the _selectedEmailItem we do a little bit more work to ensure we've actually tamed the beast and it is working as we expect. Caveat: I'm not sure this is 100% neccessary. There may be a more generic navigation mechanism built into the TreeViewItem but I've not found it yet. What we do when the selection changes is to walk back up the tree until the item under examination has a null parent - at each stage we print out the email title.

    5. Now, in the case of the items we have inserted so far, we have not set the Parent property. I've skipped over it entirely This might be useful if you want to refer back to the email in your reply for example. Assume a button called btnAdd has been added to the XAML and an suitable handler added to the C# code. Here's what the handler will do:

            private void btnAdd_Click(object sender, RoutedEventArgs e)
            {
                // invariant - cannot be null if button enabled
                if (_selectedEmailItem != null)
                {
                    // this new email is from jerry
                    EmailItem emi = new EmailItem("re:" + _selectedEmailItem.Title,"Jerry", DateTime.Now);
                    // and its parent is the current selection
                    emi.Parent = _selectedEmailItem;
                    // add the new child to the collection in the parent
                    _selectedEmailItem.Items.Add(emi);
                }
            }

    This should be adequately commented - we add a new item to the tree as a child of the current selection. What is pretty clever is that at no point is the TreeView or any TreeViewItems involved. But, despite this absence of an explicit link, the action of adding the email will ensure the view is updated, with the modified item now displaying the 'i've got child items' arrow. See green callouts.

    Modified treeview 

     6. Finally. That covers the essentials and answers my initial question. Hope you find this useful. Please feel free to comment/query/criticise as you see fit. I'd appreciate it if the latter was constructive in nature!

    Jerry

     

    tree view c# cod insert runtime