Page view counter

Silverlight TreeView Advanced Scenarios (TreeViewExtended)

Hi Folks,

One of the most important tenants of Silverlight Controls development we picked up in Microsoft is WPF Compatibility.

That means, that if we end up building FooControl in Silverlight and FooControl exists in WPF we’ll end up with the same API and behaviour.

Why is that good for you? It allows you to reuse to code from Silverlight in WPF. If we spin up new API or have differentiating behaviours you really won’t be able to to reuse your XAML and Code in WPF.

Why could this introduce problems? Well, The WPF API and Behaviours aren’t a divine decree. Some scenarios might not be covered by the WPF control.

We’ve hit this issue pretty hard when it comes to TreeView. The WPF TreeView doesn’t fully meet all user requirements, but we still want to be WPF Subset compat.
Hard choice, eh?

So, allow me to introduce a TreeViewExtended class that solves some of this issues.

 

Download code @ http://silverlight.net/blogs/justinangel/BlogStorage/TreeViewExtended.zip 
(To get TreeViewExtended and extension methods to work, include TreeViewExtended.cs in your project and go crazy.)

 

Prologue: Setting up a Hello World TreeView

Prerequisites for this article:
1. Silverlight Toolkit: TreeView, TreeViewItem & HierarchalDataTemplate 

If you don’t know how TreeView works, please read the afore mentioned article.

 

Here’s our basic TreeView:

<UserControl x:Class="TreeViewExtended.MainPage"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

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

   xmlns:common="clr-namespace:System.Windows;assembly=System.Windows.Controls"

   Width="400" Height="300">

    <UserControl.Resources>

        <common:HierarchicalDataTemplate x:Key="myHierarchicalTemplate" ItemsSource="{Binding Items}" >

            <TextBlock Text="{Binding myString}" />

        </common:HierarchicalDataTemplate>

    </UserControl.Resources>

    <StackPanel x:Name="LayoutRoot" Background="White" VerticalAlignment="Stretch">

        <controls:TreeView VerticalAlignment="Stretch" 

                          x:Name="trv"

                          ItemTemplate="{StaticResource myHierarchicalTemplate}" />

    </StackPanel>

</UserControl>

Nothing too fancy, Just a TreeView with a HierarchichalDataTemplate.

Here’s some sample data we’ll use in this solution:

public partial class MainPage : UserControl

{

    ObservableCollection<myItem> itemsSource =

      new ObservableCollection<myItem>()

      {

          new myItem("Hello",

              new myItem("World"),

              new myItem("Foo"),

              new myItem("Bar")),

          new myItem("Moo",

              new myItem("Boo",

                new myItem("Goo"))),

      };

 

    public MainPage()

    {

        InitializeComponent();

 

        trv.ItemsSource = itemsSource;

    }

}

And here’s the C# type we’ll use:

public class myItem

{

    public myItem(string myString, params myItem[] myItems)

    {

        ObservableCollection<myItem> itemsObservableCollection = new ObservableCollection<myItem>();

        foreach (var item in myItems)

            itemsObservableCollection.Add(item);

        Items = itemsObservableCollection;

    }

 

    public string myString { get; set; }

    public ObservableCollection<myItem> Items { get; set; }

}

 

When we run this simple TreeView sample we’ll see the following TreeView:

image

And Fully expanded:

image

 

 

Chapter #1: Finding a TreeViewItem based on a Business Class instance

Let’s create a Button that once Clicked it changes the “Moo” TreeViewItem to FontSize=20.

To do that, we’ll use the TreeView.ItemContainerGenerator shown here:

image

The ContainerFromItem allows us to find a TreeViewItem based on a specific Business class.

image

image

So, Let’s add the button:

<Button VerticalAlignment="Bottom" Content="Change Moo TreeViewItem FontSize" Click="Button_Click" />

And Let’s write the code needed to get the TreeViewItem:

ObservableCollection<myItem> itemsSource =

  new ObservableCollection<myItem>()

  {

      new myItem("Hello",

          new myItem("World"),

          new myItem("Foo"),

          new myItem("Bar")),

      new myItem("Moo",

          new myItem("Boo",

            new myItem("Goo"))),

  };

 

private void Button_Click(object sender, RoutedEventArgs e)

{

    myItem mooElement = itemsSource[1];

    TreeViewItem mootreeViewItem =

        (TreeViewItem)trv.ItemContainerGenerator.ContainerFromItem(mooElement);

 

    mootreeViewItem.FontSize = 20;

}

Let’s run this sample:

image

And click the sample:

image 

Now let’s change the sample to change the TreeViewItem for ‘Boo’

ObservableCollection<myItem> itemsSource =

  new ObservableCollection<myItem>()

  {

      new myItem("Hello",

          new myItem("World"),

          new myItem("Foo"),

          new myItem("Bar")),

      new myItem("Moo",

          new myItem("Boo",

            new myItem("Goo"))),

  };

 

private void Button_Click(object sender, RoutedEventArgs e)

{

    myItem booElement = itemsSource[1].Items[0];

    TreeViewItem bootreeViewItem =

        (TreeViewItem)trv.ItemContainerGenerator.ContainerFromItem(booElement);

 

    bootreeViewItem.FontSize = 20;

}

What do you think that’ll happen when we run this sample?

image

 

But why is BooTreeViewItem empty? Well, it’s because an ItemContainerGenerator only knows the Items of the same level.

image

So, The TreeViewItem itself has an ItemContainerGenerator we’ll need to access.

private void Button_Click(object sender, RoutedEventArgs e)

{

    myItem mooElement = itemsSource[1];

    TreeViewItem mootreeViewItem =

        (TreeViewItem)trv.ItemContainerGenerator.ContainerFromItem(mooElement);

 

    myItem booElement = mooElement.Items[0];

    TreeViewItem bootreeViewItem =

        (TreeViewItem)mootreeViewItem.ItemContainerGenerator.ContainerFromItem(booElement);

 

    bootreeViewItem.FontSize = 20;

}

And when we run this sample:

image 

So, first we had to get the ‘Moo’ TreeViewItem and than the ‘Boo’ TreeViewItem..

You can see how this gets a bit tricky if you’re just looking for any element in the TreeView.

 

Introducing TreeViewWorkarounds.
Based on the principles shown in this article up until now, you can easily create a method to iterate over all TreeViewItem and search for items.

public static class TreeViewWorkarounds

{

    public static TreeViewItem ContainerFromItem(this TreeView treeView, object item)

    {

        TreeViewItem containerThatMightContainItem = (TreeViewItem)treeView.ItemContainerGenerator.ContainerFromItem(item);

        if (containerThatMightContainItem != null)

            return containerThatMightContainItem;

        else

            return ContainerFromItem(treeView.ItemContainerGenerator, treeView.Items, item);

    }

 

    private static TreeViewItem ContainerFromItem(ItemContainerGenerator parentItemContainerGenerator, ItemCollection itemCollection, object item)

    {

        foreach (object curChildItem in itemCollection)

        {

            TreeViewItem parentContainer = (TreeViewItem)parentItemContainerGenerator.ContainerFromItem(curChildItem);

            if (parentContainer == null)

                return null;

            TreeViewItem containerThatMightContainItem = (TreeViewItem)parentContainer.ItemContainerGenerator.ContainerFromItem(item);

            if (containerThatMightContainItem != null)

                return containerThatMightContainItem;

            TreeViewItem recursionResult = ContainerFromItem(parentContainer.ItemContainerGenerator, parentContainer.Items, item);

            if (recursionResult != null)

                return recursionResult;

        }

        return null;

    }

 

    public static object ItemFromContainer(this TreeView treeView, TreeViewItem container)

    {

        TreeViewItem itemThatMightBelongToContainer = (TreeViewItem)treeView.ItemContainerGenerator.ItemFromContainer(container);

        if (itemThatMightBelongToContainer != null)

            return itemThatMightBelongToContainer;

        else

            return ItemFromContainer(treeView.ItemContainerGenerator, treeView.Items, container);

    }

 

    private static object ItemFromContainer(ItemContainerGenerator parentItemContainerGenerator, ItemCollection itemCollection, TreeViewItem container)

    {

        foreach (object curChildItem in itemCollection)

        {

            TreeViewItem parentContainer = (TreeViewItem)parentItemContainerGenerator.ContainerFromItem(curChildItem);

            if (parentContainer == null)

                return null;

            TreeViewItem itemThatMightBelongToContainer = (TreeViewItem)parentContainer.ItemContainerGenerator.ItemFromContainer(container);

            if (itemThatMightBelongToContainer != null)

                return itemThatMightBelongToContainer;

            TreeViewItem recursionResult = ItemFromContainer(parentContainer.ItemContainerGenerator, parentContainer.Items, container) as TreeViewItem;

            if (recursionResult != null)

                return recursionResult;

        }

        return null;

    }

}

Using these extension methods isn’t hard at all:

private void Button_Click(object sender, RoutedEventArgs e)

{

    //myItem mooElement = itemsSource[1];

    //TreeViewItem mootreeViewItem =

    //    (TreeViewItem)trv.ItemContainerGenerator.ContainerFromItem(mooElement);

 

    //myItem booElement = mooElement.Items[0];

    //TreeViewItem bootreeViewItem =

    //    (TreeViewItem)mootreeViewItem.ItemContainerGenerator.ContainerFromItem(booElement);

 

    //bootreeViewItem.FontSize = 20;

 

    trv.ContainerFromItem(itemsSource[1].Items[0]).FontSize = 20;

}

Let’s run this sample:

image

Much easier to use, eh?

Don’t worry about the code for the extension methods, you can freely use it.

 

 

Chapter #2: Searching for any TreeViewItems based on predicates

Based on the code from the previous extension methods, we can build a TreeView search based on predicates.

Essentially, we’d like to enable using search lambda predicates:

trv.FindContainer(t => ((myItem) t.Header).myString == "Boo").FontSize = 20;

 

We’ll add the FindContainer extension method to iterate over all TreeView and check the predicate.

public static class TreeViewWorkarounds

{

 

    public static TreeViewItem FindContainer(this TreeView treeView, Predicate<TreeViewItem> condition)

    {

        return FindContainer(treeView.ItemContainerGenerator, treeView.Items, condition);

    }

 

    private static TreeViewItem FindContainer(ItemContainerGenerator parentItemContainerGenerator, ItemCollection itemCollection, Predicate<TreeViewItem> condition)

    {

        foreach (object curChildItem in itemCollection)

        {

            TreeViewItem containerThatMightMeetTheCondition= (TreeViewItem)parentItemContainerGenerator.ContainerFromItem(curChildItem);

 

            if (containerThatMightMeetTheCondition == null)

                return null;

 

            if (condition(containerThatMightMeetTheCondition))

                return containerThatMightMeetTheCondition;

 

            TreeViewItem recursionResult = FindContainer(containerThatMightMeetTheCondition.ItemContainerGenerator, containerThatMightMeetTheCondition.Items, condition);

            if (recursionResult != null)

                return recursionResult;

        }

        return null;

    }

 

Let’s run this code sample:

image

 

We can also use this method to look for parent TreeViewItems.

trv.FindContainer(t => ((myItem)t.Header).Items.Any(i => i.myString == "World")).FontSize = 20;

In this code, we’re looking for any TreeViewItem that has a Child with the word “World” in it.

image

 

Really, once you get the power of lambda search predicates, you can do a lot with the FindContainer extension method.

 

 

Chapter #3: Exposing a TreeView.Containers collection

Sometimes, you just want to have a collection of all the TreeViewItems in the TreeView.
Maybe you want to check something, or manually search those items.

We’ll inherit from the normal TreeView to create TreeViewExtended:

public class TreeViewExtended : TreeView

{

    public TreeViewExtended()

    {

        this.DefaultStyleKey = typeof(TreeView);

    }

}

Next, we’ll change our sample to work with this new TreeView:

<UserControl x:Class="TreeViewExtended.MainPage"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

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

   xmlns:common="clr-namespace:System.Windows;assembly=System.Windows.Controls"

   xmlns:TreeViewExtended="clr-namespace:System.Windows.Controls"

   Width="400" Height="300">

    <UserControl.Resources>

        <common:HierarchicalDataTemplate x:Key="myHierarchicalTemplate" ItemsSource="{Binding Items}" >

            <TextBlock Text="{Binding myString}" />

        </common:HierarchicalDataTemplate>

    </UserControl.Resources>

    <StackPanel x:Name="LayoutRoot" Background="White" VerticalAlignment="Stretch">

        <!--<controls:TreeView VerticalAlignment="Stretch"  x:Name="trv" ItemTemplate="{StaticResource myHierarchicalTemplate}"  />-->

        <TreeViewExtended:TreeViewExtended VerticalAlignment="Stretch" x:Name="trv" ItemTemplate="{StaticResource myHierarchicalTemplate}" />

        <Button VerticalAlignment="Bottom" Content="Do something" Click="Button_Click" />

    </StackPanel>

</UserControl>

 

And obviously, everything works as we expected:

image

 

Now, let’s implement our collection.

 

public class TreeViewExtended : TreeView

{

    public TreeViewExtended()

    {

        this.DefaultStyleKey = typeof(TreeView);

    }

 

    public IEnumerable<TreeViewItem> Containers

    {

        get

        {

            return GetTreeViewItems(this.ItemContainerGenerator, this.Items);

        }

    }

 

    private static IEnumerable<TreeViewItem> GetTreeViewItems(ItemContainerGenerator parentItemContainerGenerator, ItemCollection itemCollection)

    {

        foreach (object curChildItem in itemCollection)

        {

            TreeViewItem container = (TreeViewItem)parentItemContainerGenerator.ContainerFromItem(curChildItem);

 

            if (container != null)

                yield return container;

 

            foreach (var treeViewItem in GetTreeViewItems(container.ItemContainerGenerator, container.Items))

                yield return treeViewItem;

        }

    }

}

 

And let’s print out the TreeViewItems when clicking the button:

    foreach (TreeViewItem treeViewItem in trv.Containers)

    {

        Debug.WriteLine("TreeViewItem " + ((myItem)treeViewItem.Header).myString);

    }

 

Let’s run this sample app and click the button:

image

And in Visual Studio “Output” window we can see:

image

 

Chapter #4: Aggregating the TreeViewItem Expanded & Collapsed on TreeView

We’d like to get an event to fire on our TreeView whenever a TreeViewItem.Expanded/Collapsed event fires.

Think with me here, if we want to do that – the TreeViewItem needs to know who TreeView is so it can tell the TreeView “Hey, the TreeViewItem event fired!”.

For that, we’ll need to create our own TreeViewItemExtended.

Now, look closely, this is how we override the 2 methods on an ItemsControl/TreeView to do that.

public class TreeViewExtended : TreeView

{

    protected override bool IsItemItsOwnContainerOverride(object item)

    {

        return item is TreeViewExtended;

    }

 

    protected override DependencyObject GetContainerForItemOverride()

    {

        return new TreeViewItemExtended();

    }

 

    …

}

 

public class TreeViewItemExtended : TreeViewItem

{

    protected override bool IsItemItsOwnContainerOverride(object item)

    {

        return item is TreeViewExtended;

    }

 

    protected override DependencyObject GetContainerForItemOverride()

    {

        return new TreeViewItemExtended();

    }

}

 

We’ll override these 2 methods so our TreeView and TreeViewItems will always generated our TreeVieWItemExtended.

Next, we’ll create a public property on TreeViewItem that points to the TreeView it belongs to.

public class TreeViewItemExtended : TreeViewItem

{

    protected override bool IsItemItsOwnContainerOverride(object item)

    {

        return item is TreeViewExtended;

    }

 

    protected override DependencyObject GetContainerForItemOverride()

    {

        return new TreeViewItemExtended();

    }

 

    public TreeViewExtended ParentTreeView { set; get; }

}

 

We’ll populate that property on the prepare method.

public class TreeViewExtended : TreeView

{

    protected override bool IsItemItsOwnContainerOverride(object item)

    {

        return item is TreeViewExtended;

    }

 

    protected override DependencyObject GetContainerForItemOverride()

    {

        return new TreeViewItemExtended();

    }

 

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)

    {

        TreeViewItemExtended treeViewItemExtended = (TreeViewItemExtended) element;

        treeViewItemExtended.ParentTreeView = this;

 

        base.PrepareContainerForItemOverride(element, item);

    }

 

And in the TreeViewItem for each child TreeViewItem:

public class TreeViewItemExtended : TreeViewItem

{

    protected override bool IsItemItsOwnContainerOverride(object item)

    {

        return item is TreeViewExtended;

    }

 

    protected override DependencyObject GetContainerForItemOverride()

    {

        return new TreeViewItemExtended();

    }

 

    public TreeViewExtended ParentTreeView { internal set; get; }

 

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)

    {

        TreeViewItemExtended treeViewItemExtended = (TreeViewItemExtended)element;

        treeViewItemExtended.ParentTreeView = this.ParentTreeView;

 

        base.PrepareContainerForItemOverride(element, item);

    }

}

 

Next, we’ll expose a TreeViewExtended event for Expanded/Collapsed.

public class TreeViewExtended : TreeView

{

   …

 

    public event RoutedEventHandler ContainerExpanded;

    public event RoutedEventHandler ContainerCollapsed;

}

 

And we’ll add appropriate methods for TreeViewItemExtended to invoke these events.

    public event RoutedEventHandler ContainerExpanded;

    internal void InvokeContainerExpanded(object sender, RoutedEventArgs e)

    {

        RoutedEventHandler expanded = ContainerExpanded;

        if (expanded != null) expanded(sender, e);

    }

 

    public event RoutedEventHandler ContainerCollapsed;

    internal void InvokeContainerCollapsed(object sender, RoutedEventArgs e)

    {

        RoutedEventHandler collapsed = ContainerCollapsed;

        if (collapsed != null) collapsed(sender, e);

    }

 

Finally, we’ll have the TreeViewItem invoke these event invoke methods.

public class TreeViewItemExtended : TreeViewItem

{

    public TreeViewItemExtended()

    {

        this.Expanded += new RoutedEventHandler(TreeViewItemExtended_Expanded);

        this.Collapsed += new RoutedEventHandler(TreeViewItemExtended_Collapsed);

    }

 

    void TreeViewItemExtended_Collapsed(object sender, RoutedEventArgs e)

    {

        ParentTreeView.InvokeContainerCollapsed(sender, e);

    }

 

    void TreeViewItemExtended_Expanded(object sender, RoutedEventArgs e)

    {

        ParentTreeView.InvokeContainerExpanded(sender, e);

    }

 

Let’s use these events:

public MainPage()

{

    InitializeComponent();

 

    trv.ItemsSource = itemsSource;

 

    trv.ContainerExpanded += (s, e) => PrintTreeViewMessage(s, "Expanded");

    trv.ContainerCollapsed += (s, e) => PrintTreeViewMessage(s, "Collapsed");

 

}

 

private void PrintTreeViewMessage(object sender, string description)

{

    TreeViewItemExtended treeViewItemExtended = (TreeViewItemExtended) sender;

    myItem item = (myItem) treeViewItemExtended.Header;

 

    textBlock.Text = string.Format("{0} TreeView was {1}", item.myString, description);

}

And when we run this sample, we can see that:

image

image

 

image

 

 

Chapter #5: Exposing the TreeViewItem.MouseLwftButtonDown event

For reasons of WPF subset Compatibility the Silverlight TreeViewItem does not raise the MouseLeftButtonDown event when clicked.
Normally, you should use the “Selected” event for that.

But, we can now expose a new TreeViewExtended.ContainerMouseLeftButtonDown event that does just that.

public class TreeViewExtended : TreeView

{

    public event MouseButtonEventHandler ContainerMouseButtonEventHandler;

    internal void InvokeContainerMouseButtonEventHandler(object sender, MouseButtonEventArgs e)

    {

        MouseButtonEventHandler handler = ContainerMouseButtonEventHandler;

        if (handler != null) handler(sender, e);

    }

public class TreeViewItemExtended : TreeViewItem

{

    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)

    {

        base.OnMouseLeftButtonDown(e);

        ParentTreeView.InvokeContainerMouseButtonEventHandler(this, e);

    }

 

And now, we’ll sign up for that event.

public MainPage()

{

    InitializeComponent();

 

    trv.ItemsSource = itemsSource;

 

    trv.ContainerMouseButtonEventHandler += (s, e) => PrintTreeViewMessage(s, "MouseLeftButtonDown");

}

 

And when we run this sample:

image

image

 

 

Chapter #6: Exposing a PreparingContainer event

So, one of the biggest thing you should know about TreeView – it’s virtualized (in a way).
It won’t create TreeViewItem until they’re really needed.

Which makes a lot of the work we’ve done up until now for not. Let’s have a look at this code snippet:

        private void Button_Click(object sender, RoutedEventArgs e)

        {

            trv.ContainerFromItem(itemsSource[1].Items[0].Items[0]).FontSize = 20;

        }

Basically, we’re looking for the TreeViewItem with the word ‘Goo’.

What do you think happens if we click the button when TreeView is fully collapsed?

image

We’ll get a “OMG, This TreeView doesn’t exist” exception:

image

But, if we do the same thing when the TreeView is expanded:

image

It will work:

image

 

Why? Because the ‘Goo’ TreeViewItem doesn’t exist until it’s needed.

And the TreeViewItem isn’t available until that Visual Tree has been built, which happens at somewhat random points.

So, let’s start by exposing an event that’ll happen every time a TreeViewItem is created.

public class TreeViewExtended : TreeView

{

    public event EventHandler<ContainerPreparedEventArgs> ContainerPrepared;

    internal void InvokeContainerPrepared(TreeViewItemExtended sender, object item)

    {

        EventHandler<ContainerPreparedEventArgs> prepared = ContainerPrepared;

        if (prepared != null) prepared(sender, new ContainerPreparedEventArgs(sender, item));

    }

 

 

public class ContainerPreparedEventArgs : EventArgs

{

    public ContainerPreparedEventArgs(TreeViewItemExtended container, object item)

    {

        Container = container;

        Item = item;

    }

 

    public TreeViewItemExtended Container { get; set; }

    public object Item { get; set; }

}

 

Next, whenever a TreeViewItem is prepared we’ll invoke this event.

public class TreeViewExtended : TreeView

{

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)

    {

        TreeViewItemExtended treeViewItemExtended = (TreeViewItemExtended) element;

        treeViewItemExtended.ParentTreeView = this;

 

        base.PrepareContainerForItemOverride(element, item);

 

        InvokeContainerPrepared(treeViewItemExtended, item);

    }

 

    public event EventHandler<ContainerPreparedEventArgs> ContainerPrepared;

    internal void InvokeContainerPrepared(TreeViewItemExtended sender, object item)

    {

        EventHandler<ContainerPreparedEventArgs> prepared = ContainerPrepared;

        if (prepared != null) prepared(sender, new ContainerPreparedEventArgs(sender, item));

    }

 

}

 

 

public class TreeViewItemExtended : TreeViewItem

{

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)

    {

        TreeViewItemExtended treeViewItemExtended = (TreeViewItemExtended)element;

        treeViewItemExtended.ParentTreeView = this.ParentTreeView;

 

        base.PrepareContainerForItemOverride(element, item);

 

        ParentTreeView.InvokeContainerPrepared(treeViewItemExtended, item);

    }

 

 

}

 

Now, Let’s print out when each TreeViewItem has been created:

public MainPage()

{

    InitializeComponent();

 

    trv.ItemsSource = itemsSource;

 

    trv.ContainerPrepared += (s, e) => AddTreeViewMessage(s, "Created");

}

 

 

Let’s start our application:

image

We can clearly that only TreeViewItems have been created up until now.

Let’s expand the ‘Hello’ TreeViewItem:

image

And once that TreeViewItem has expanded the TreeViewItems were created.

We’ll Expand the rest of the Tree:

image image

 

So the PreparingContainer event is pretty useful considering it lets us know when TreeViewItems are ready.

 

 

Chapter #7: Implementing Delayed Expanded TreeViewItems

This feature isn’t 100% useful before we get to the next Chapter.
What I’d like for us to do (and take me on faith here) is implement a method that :
1. Will expand TreeViewItem corresponding to Items
2. Will be able to work in a delayed manner (even if they are created after the method was called)

Expanding TreeViewItems creates more TreeViewItems, so this might be very interesting.

 

Here’s our method:

    private List<object> itemsToDelayExpand = new List<object>();

    public void ExpandDelayItems(params object[] ItemsCorrespondingToTreeViewItemsToExpand)

    {

        itemsToDelayExpand.AddRange(ItemsCorrespondingToTreeViewItemsToExpand);

 

        foreach (object itemtoTryAndExpand in ItemsCorrespondingToTreeViewItemsToExpand)

        {

            TreeViewItem treeViewItem = this.ContainerFromItem(itemtoTryAndExpand);

            if (treeViewItem != null)

            {

                treeViewItem.IsExpanded = true;

                itemsToDelayExpand.Remove(itemtoTryAndExpand);

            }

        }

    }

We’ll take a collection of Items and add those to a bigger list of all Items we’re waiting for.
Than we’ll go over the TreeView looking for these items and if they have TreeViewItems – we’ll expand those.

If they don’t have TreeViewItems, we’ll listen to the prepared event and wait for them to be created.

    internal void InvokeContainerPrepared(TreeViewItemExtended sender, object item)

    {

        EventHandler<ContainerPreparedEventArgs> prepared = ContainerPrepared;

        if (prepared != null) prepared(sender, new ContainerPreparedEventArgs(sender, item));

 

        if (itemsToDelayExpand.Contains(item))

        {

            sender.IsExpanded = true;

            itemsToDelayExpand.Remove(item);

        }

    }

 

    private List<object> itemsToDelayExpand = new List<object>();

    public void ExpandDelayItems(params object[] ItemsCorrespondingToTreeViewItemsToExpand)

    {

        itemsToDelayExpand.AddRange(ItemsCorrespondingToTreeViewItemsToExpand);

 

        foreach (object itemtoTryAndExpand in ItemsCorrespondingToTreeViewItemsToExpand)

        {

            TreeViewItem treeViewItem = this.ContainerFromItem(itemtoTryAndExpand);

            if (treeViewItem != null)

            {

                treeViewItem.IsExpanded = true;

                itemsToDelayExpand.Remove(itemtoTryAndExpand);

            }

        }

    }

 

Let’s test our method:

trv.ExpandDelayItems(itemsSource[1], itemsSource[1].Items[0]);

 

And if we run our application:

image

Let’s click the button:

image

You can see that ‘Moo’ TreeViewItem was expanded, and than the ‘Boo’ TreeViewItem was expanded.

 

 

Chapter #8: Set TreeView.SelectedItem with TreeView.SetSelectItem method

By now, you can see how using the ExpandDelayItems with the ContainerPrepared event let’s us set a selected item.

    private object SelectedItemDelayed = null;

    public void SetSelectedItem(object SelectedItem, params object[] SelectedItemParents)

    {

        ContainerPrepared += new EventHandler<ContainerPreparedEventArgs>(ContainerPrepared_LookForSelectedItem);

        SelectedItemDelayed = SelectedItem;

        ExpandDelayItems(SelectedItemParents);

    }

 

    private void ContainerPrepared_LookForSelectedItem(object sender, ContainerPreparedEventArgs e)

    {

        if (e.Item == SelectedItemDelayed)

        {

            e.Container.IsSelected = true;

            SelectedItemDelayed = null;

            ContainerPrepared -= new EventHandler<ContainerPreparedEventArgs>(ContainerPrepared_LookForSelectedItem);

        }

    }

Basically, all we did here is expand all Parent TreeViewItems and sit comfortably waiting for our TreeViewItem to create itself.

 

Let’s test our SetSelectedItem method.

trv.SetSelectedItem(itemsSource[1].Items[0].Items[0],

                    itemsSource[1].Items[0],

                    itemsSource[1]);

And when we’ll run the app we can see:

image

image

 

Now, the TreeView could start looking around for the Parent Items itself, but consider a scenario with millions of nested elements.
So if you call SetSelectedItem with the correct parents, it’ll end up setting your SelectedItem.

 


Chapter #9: Adding a Parent TreeViewItem property

One last tiny feature we’ll add is a TreeViewItem.ParentTreeViewItem property.

 

We’ll start by adding the property.

public class TreeViewItemExtended : TreeViewItem

{

    public TreeViewItemExtended ParentTreeViewItem { internal set; get; }

 

And during the Prepare Container for it’s nested elements we’ll set their Parent property to this TreeViewItem.

public class TreeViewItemExtended : TreeViewItem

{

 

    public TreeViewExtended ParentTreeView { internal set; get; }

    public TreeViewItemExtended ParentTreeViewItem { internal set; get; }

 

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)

    {

        TreeViewItemExtended treeViewItemExtended = (TreeViewItemExtended)element;

        treeViewItemExtended.ParentTreeView = this.ParentTreeView;

        treeViewItemExtended.ParentTreeViewItem = this;

 

        base.PrepareContainerForItemOverride(element, item);

 

        ParentTreeView.InvokeContainerPrepared(treeViewItemExtended, item);

    }

 

Let’s use this to change the parent background of any TreeViewItem with children:

            trv.ContainerExpanded += (s, args) =>

                {

                    TreeViewItemExtended parent = ((TreeViewItemExtended) s).ParentTreeViewItem;

                    if (parent != null)

                    {

                        parent.Background = new SolidColorBrush(Colors.Blue);

                    }

                };

Let’s run this application:

image

And once we expand ‘Hello’ TreeViewItem:

image

And ‘Moo’ TreeViewItem:

image

 

 

Epilogue

The TreeView control has a lot of extensibility points to overcome some innate WPF compatible features.

If you’d like to see TreeViewItemExtended ship in the Silverlight Toolkit, please feel free to vote on the issue here:
http://silverlight.codeplex.com/WorkItem/View.aspx?WorkItemId=3039

 

Sincerely,

-- Justin Angel

Microsoft Silverlight Program Manager

Published Tuesday, May 19, 2009 3:32 PM by JustinAngel

Comments

# Silverlight TreeView Advanced Scenarios (TreeViewExtended) - Justin myJustin = new Microsoft.Silverlight.Justin();

Pingback from  Silverlight TreeView Advanced Scenarios (TreeViewExtended) - Justin myJustin = new Microsoft.Silverlight.Justin();

# re: Silverlight TreeView Advanced Scenarios (TreeViewExtended)

It would be nice if there was a fully virtualized treeview in silverlight.  (where the IsExpanded property is in some non UI Object) and the UI objects are created only for what is currently on the screen.  (That way you can have MASSIVE trees like say the windows file system, without the massive memory hog of creating 1000's of UI tree nodes).

Tuesday, May 19, 2009 8:53 PM by obsid

# re: Silverlight TreeView Advanced Scenarios (TreeViewExtended)

Mmmn, this may be harsh but...  I've seen this again and again and again.  Always TreeView.  WPF compatibility is absolutely hugely important - I put a ton of effort into this requirement myself - but I wonder if this is not the right way to get there.  Maybe you guys should just bite the bullet and make a new TreeView control (TreeView2, TreeViewEx, whatever), do it right, and do it the same for both WPF and Silverlight.  Was the WPF TreeView really such a great control to be worth jumping through all these hoops?  Or is it more a matter of huge investment (past and future)?  I got the impression from a certain blog that a whole team of people spent quite awhile coming up with the WPF TreeView control.

It's sad when things that could be so simple end up being so terribly complex.

Wednesday, May 20, 2009 10:01 AM by tgrand

# re: Silverlight TreeView Advanced Scenarios (TreeViewExtended)

The part that worries me about this approach is that the new control is still derived from SL TreeView which is bound by the WPF TreeView compatibility constraints.  So, can you really overcome those constraints this way?  I don't know.  If you can, great!

It just seems that there are a lot of things people want to do with a tree control that either can't be done with the WPF tree control or are overly complex.  It would be great to have a tree control that would make it easy to do most of the things app developers regularly need while also having the same public API and behavior on Silverlight and WPF.

With WPF and SL control development there has to be a careful balance between making the control able to have a very flexible appearance vs. making the control excel at its main intended use... right?  I got the impression that the WPF TreeView control's design was too far on the wrong end of this balance, leading to many problems when trying to use it in the real world.

Wednesday, May 20, 2009 2:42 PM by tgrand

# The Technology Post for May 21st

UPDATE: If you are looking to follow this series, be sure to subscribe to my RSS feed at feeds.jasongaylord.com/JasonNGaylord

Thursday, May 21, 2009 11:52 PM by ASPInsiders

# Top-silverlight &raquo; Blog Archive &raquo; SILVERLIGHT TREEVIEW ADVANCED SCENARIOS (TREEVIEWEXTENDED)

Pingback from  Top-silverlight  &raquo; Blog Archive   &raquo; SILVERLIGHT TREEVIEW ADVANCED SCENARIOS (TREEVIEWEXTENDED)

# re: Silverlight TreeView Advanced Scenarios (TreeViewExtended)

SelectedItem should be able to be set via binding, like any other property.  Wherever possible I try to avoid using code and put as much as I can in the xaml. It be much better if I didn't have to resort to code for something as simple as setting the selected item.

...Stefan

Monday, June 29, 2009 7:34 PM by StefanOlson

# re: Silverlight TreeView Advanced Scenarios (TreeViewExtended)

I am trying to do this in my code:

TreeViewItem mootreeViewItem = (TreeViewItem)trv.ItemContainerGenerator.ContainerFromItem(mooElement);

But I get a null TreeViewItem. How to fix that?

See silverlight.net/.../47296.aspx

Anyway, your article is great! Nice workarounds, but I hope the TreeView will be redesigned, so that no workarounds are are necessary.

Tuesday, September 08, 2009 10:15 AM by Soennichsen

# re: Silverlight TreeView Advanced Scenarios (TreeViewExtended)

1.

protected override bool IsItemItsOwnContainerOverride(object item)

   {

       return item is TreeViewExtended;

   }

Shouldn't it be: TreeViewItemExtended?

2. does make it a WeakReference help for release?

public TreeViewExtended ParentTreeView

Thursday, October 15, 2009 11:01 AM by rich_r26