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:
And Fully expanded:
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:
The ContainerFromItem allows us to find a TreeViewItem based on a specific Business class.
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:
And click the sample:
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?
But why is BooTreeViewItem empty? Well, it’s because an ItemContainerGenerator only knows the Items of the same level.
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:
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:
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:
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.
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:
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:
And in Visual Studio “Output” window we can see:
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:
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:
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?
We’ll get a “OMG, This TreeView doesn’t exist” exception:
But, if we do the same thing when the TreeView is expanded:
It will work:
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:
We can clearly that only TreeViewItems have been created up until now.
Let’s expand the ‘Hello’ TreeViewItem:
And once that TreeViewItem has expanded the TreeViewItems were created.
We’ll Expand the rest of the Tree:
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:
Let’s click the button:
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:
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:
And once we expand ‘Hello’ TreeViewItem:
And ‘Moo’ TreeViewItem:
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