Powered by MSDN

US - English
NEW! Silverlight 5 is available Learn More

Toolkit treeview questions RSS

32 replies

Last post Aug 23, 2011 11:03 AM by Prakash_4work

(1)
  • 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

  • Ted Glaza [MSFT]

    Ted Glaza [M...

    Member

    50 Points

    10 Posts

    Re: Toolkit treeview questions

    Nov 04, 2008 06:21 AM | LINK

    Hi Jerry,

    Unfortunately in Silverlight there are some things you can only do in XAML and not code.  You can't currently create DataTemplates (or HierarchicalDataTemplates, etc.) in code (except for using XamlReader.Load on a string of XAML).

    You can still do most of your work in code if you prefer though.  The trick is to create a regular DataTemplate that defines how you like your items rendered and stick it in the Resources of a named control.  Then you can look it up with DataTemplate emailTemplate = myControl.Resources["MyTemplate"] as DataTemplate;.  Just create all your TreeViewItems, set the Header to be your email, and set the HeaderTemplate to be emailTemplate.  You can add children directly to the TreeViewItems.Items collection.  It's a little more work to set up, but you don't have to worry about whether items are in the visual tree when you construct them yourself.

    Thanks,
    Ted

    TreeView TreeViewItem

    This posting is provided "AS IS" with no warranties, and confers no rights.
  • Je8

    Je8

    Member

    54 Points

    50 Posts

    Re: Toolkit treeview questions

    Nov 04, 2008 12:11 PM | LINK

    Hello Ted,

    Thanks for the comment. May I suggest this gets added to the samples/documentation? Is it possible to knock up a code snippet that does this with an HDataTemplate?

    One thing - the problem with DataTemplates is that they violate the separation of design and code. All of a sudden I have to fill a not very easy to segment XAML document with a load of stuff that may or may not make sense to a Blend designer. A very unfortunate side effect is that you now have (effectively) duplicated class declarations, one in a C# file, the other in the XAML. It is, however, an intractable problem - after all you do have to tell the control what to display.

    While you are here: what is the best way to add a column header to a multi-column tree view like I have in my screen snaps?

    ATB

    Jerry

    treeview header

  • anyeone

    anyeone

    Participant

    826 Points

    199 Posts

    Re: Re: Toolkit treeview questions

    Nov 04, 2008 02:49 PM | LINK

     This is a nice tutorial, Jerry.  Unfortunately I'm still stumped in the scenario when you only want to load the top level items upfront - you can't see the expand-o button unless there is a child node, so there is a classic chicken-and-egg problem.

     Any ideas?

    --
    Anye Mercy
    AnyeDotNet.blogspot.com

    Please "Mark as Answer" the posts that help you - this lets others know the problem has been solved and helps others having the same problem know which solution works. Thanks!
  • Je8

    Je8

    Member

    54 Points

    50 Posts

    Re: Re: Toolkit treeview questions

    Nov 04, 2008 03:30 PM | LINK

    Anye - to clarify then the initial situation is that your tree has 3 root items. None of these has any children but you want to populate on demand? Here is one possible solution that assumes you've something similar to my EmailItem class. So:

    1. If its a root node then its parent is null - by definition.

    2. If nodes are to be populated on demand then the user is going to have to indicate which node is to be loaded and displayed.

    3. 2. implies you could use the selectionChanged event on the tree - so we have a couple of extra lines of code:

    EmailItem emi = newItem;
    // has the item got any children? There are any number of variations on this theme - one could have a loaded flag in the item for example
    if (emi.Items.Count == 0)
    {
        EmailItem ema = new EmailItem("re: " + emi.Title, "1.0", DateTime.Now);
        // this correctly sets up the visual state too.
        emi.Items.Add(ema);
    }

    Does that help? I know it means the initial nodes will not display a > until they are clicked but that is not too horrible to contemplate. You could flag this with textual or color changes.

    The alternative is to modify the code and add the ability to set the visual state to 'expandable' even if the collection of child items is empty. I hope that this is one of a number of improvements that will get added to this important control - trees have become essential elements in most of the apps I write these days.

    HTH + ATB

    Jerry

  • anyeone

    anyeone

    Participant

    826 Points

    199 Posts

    Re: Re: Re: Toolkit treeview questions

    Nov 04, 2008 04:06 PM | LINK

     Thanks, Jerry.. after I posted it occured to me that I could probably fiddle with the TreeView source to make the button appear even if there is no child.  I think I will try that.

    The reason I didn't want to just use the selectedChanged is that my TreeViewItem templates have either checkboxes or radio buttons depending on where they are being used - so I don't necessarily want them "selected" when expanding :)

     

    Cheers,

    Anye

    --
    Anye Mercy
    AnyeDotNet.blogspot.com

    Please "Mark as Answer" the posts that help you - this lets others know the problem has been solved and helps others having the same problem know which solution works. Thanks!
  • Je8

    Je8

    Member

    54 Points

    50 Posts

    Re: Re: Re: Toolkit treeview questions

    Nov 04, 2008 04:13 PM | LINK

    Anye, maybe it is really easy to set the right visual state. Is not all this stuff in the XAML template?

  • russgove

    russgove

    Member

    60 Points

    23 Posts

    Re: Re: Re: Toolkit treeview questions

    Nov 05, 2008 04:41 PM | LINK

    Another option would be to Load the top level of the Hierarchy up front and then load the data needed to expand each of the top level nodes asynchrounoulsly in the background. This way once the backgound task to get the data for a given node has completed  and updated the ObservableCollection for that node  to have children, the ui would automatically display the Expand-o button.

    If you have only a few top-level nodes with just one level of nested nodes this may be useful. You show the user the top level nodes quickly, and as the data is retrived for each of thos nodes the Expander button appears. 

    Russell
  • Je8

    Je8

    Member

    54 Points

    50 Posts

    Re: Re: Re: Toolkit treeview questions

    Nov 05, 2008 08:17 PM | LINK

     Hello Russ, interesting idea - have you tried this?

  • russgove

    russgove

    Member

    60 Points

    23 Posts

    Re: Re: Re: Toolkit treeview questions

    Nov 06, 2008 01:59 AM | LINK

    Yes, I have.  The top level nodes display once the control is loaded, and then the 'expand-o' images start to appear one ate a time on each top level node.

    I'm calling a WCF service to fill my top level. nodes. When I get the results from that call I'm looping through the results adding nodes to my ObservableCollection and also -- for each node-- calling another service to get the nodes children.

    Not sure if this is the right way to do this, but it looks really cool watching the expand-o icons light up one after another going down the page :-}

     

     

    TreeView

    Russell