Introduction
In the process of using the WPF TreeView
control, I'm sure many new WPF programmers have tried something like this:
treeView1.SelectedItem = myItem;
...only to discover that the SelectedItem
property is read-only. Coming from Windows Forms, it would seem natural to programmatically set the SelectedItem
in this manner. In WPF, however, the TreeView
control is much more powerful and there are a variety of reasons why the designers chose not to make the SelectedItem
property settable. Regardless of design decisions, though, we still need to be able to programmatically set the selected TreeViewItem
in a TreeView
(and hopefully in an easy manner).
A Solution
The solution I came up with is to have code that "walks" the tree for me, following a list (or chain) of items and selecting the last item in the chain. In the example image above, the selected path is Item 7\Item 8\Item 9\Item 10 and Item 10 is selected in the TreeView
. The chain of items, then, is Item 7 -> Item 8 -> Item 9 -> Item 10.
It doesn't matter what type the items in the chain are, just that they're unique for each parent TreeViewItem
. Behind the scenes, the code will walk the chain of items from left to right. Each item in the chain is searched for in the Items
property of the current ItemsControl
(starting with the TreeView
and descending into each TreeViewItem
). If there are more items in the chain, the current TreeViewItem
is expanded and the search continues. The last TreeViewItem
has its IsSelected
property set to true
.
Using the Code
Selecting a TreeViewItem
using a path like in the example above is easy:
- Include the TreeViewExtensions.cs and UIUtility.cs files in your project.
- Reference the namespace to include the extension methods:
using SynesthesiaM;
- Select your item:
yourTreeView.SetSelectedItem(@"Path\To\Your\Item");
Even for this simple scenario, there is a lot going on in the background and many assumptions are made about the objects in your TreeView
. When calling SetSelectedItem(string path)
, each component of the path (separated by System.IO.Path.DirectorySeparatorChar
) is compared with the ToString
representation of the objects in your TreeView
. If your TreeView
contained objects like this:
public class MyObject
{
public string Name { get; set; }
public int Id { get; set; }
public override ToString()
{
return (this.Name);
}
}
...then the components in your path will effectively be the Name
property of each MyObject
because that's what ToString
returns. What if you wanted to use the Id
property instead? Using one of the overloads of SetSelectedItem
, you can do it as follows:
var myConvertFunction = item =>
((MyObject)item).Id.ToString();
yourTreeView.SetSelectedItem(@"1\2\3\4", myConvertFunction);
If you need more complex item chains that don't have a unique string representation for each item, you can customize the behavior of SetSelectedItem
further by specifying a conversion method. Another way of doing the previous example would be:
var myConvertFunction = item =>
item.Id;
var myCompareFunction = (id1, id2) =>
id1 == id2;
yourTreeView.SetSelectedItem(new int[] { 1, 2, 3 },
myCompareFunction, myConvertFunction);
Different aspects of the path searching can be customized. Here's a brief description of each overload:
SetSelectedItem(string path, char separatorChar)
- Lets you specify your own separator character for path components (for example, use "
/
" for Unix-style paths).
SetSelectedItem(string path, Func<object, string> convertMethod)
- Lets you extract a path component by some other means besides calling
ToString
on the object.
SetSelectedItem<T>(IEnumerable<T> items)
- Lets you manually provide the item chain and type of items in the
TreeView
.
- Items are compared with the "
==
" operator.
SetSelectedItem<T>(IEnumerable<T> items, Func<T, T, bool> compareMethod)
- Lets you manually provide the item chain and type of items in the
TreeView
.
- Items are compared with your custom method.
SetSelectedItem<T>(IEnumerable<T> items, Func<T, T, bool> compareMethod, Func<object, T> convertMethod)
- Lets you manually provide the item chain and type of items in the
TreeView
.
- Items are compared with your custom method.
- Items in the
TreeView
are converted with your custom method before being compared.
If you want the maximum level of customization, use the UIUtility.SetSelectedItem
method. This method allows you to control almost every aspect of the searching, including the actions taken to select an item and request more items. The example project demonstrates how to use the most important SetSelectedItem
overloads.
Points of Interest and Potential Problems
The main reason it's so difficult to select items in a TreeView
is because of the generation of item containers. When an object is added to a TreeView
or a TreeViewItem
is expanded, the child TreeViewItem
s are generated in a background thread. Calling ItemsControl.ItemContainerGenerator.ContainerFromItem
before the background thread is finished will just return a null reference. UIUtility.SetSelectedItem
gets around the background thread problem by attaching an event handler to the ItemsControl.ItemContainerGenerator.StatusChanged
event and waiting for the generator to finish.
With all of the threading kung-fu, there's the potential for strange problems. One that I've noticed so far is calling any of the SetSelectedItem
methods twice, one right after the other, with different paths (or chains). Both calls will expand the appropriate TreeViewItem
s, but the last one to finish (not necessarily the last one called) will have its final item selected. Other potential problems most likely exist, so I wouldn't recommend using this in critical production code.
Contributions and Comments
Any contributions to the code or comments are welcome! I'm fairly new to WPF myself, so there might be many things that I'm missing or mistaken about. Also, this is my first Code Project article, so any formatting or styling recommendations are welcome too.
History
- 9/25/2007 - Initial version (1.0)