Introduction
This article explores how to use the ViewModel pattern to make it easier to work with the TreeView
control in WPF. Along the way, we examine why people often have difficulty with the WPF TreeView
, what a ViewModel is, and two demo applications that show how to combine a TreeView
with a ViewModel. One of the demos shows how to create a searchable TreeView
, the other demonstrates how to implement lazy-loading (a.k.a. load-on-demand).
Background of TreeView
The TreeView
control in WPF has gained an undeserved bad reputation. Many people try to use it, and find it to be exceedingly difficult. The problem is that people often try to use it in the same way that one might code against the Windows Forms TreeView
control. In order to leverage the extensive features of the WPF TreeView
, you cannot use the same programming techniques as in Windows Forms. This is yet another example of how WPF requires you to shift mental gears to make use of the platform appropriately. We aren’t in Kansas anymore, Toto.
In Window Forms, it is very easy to use the TreeView
control because it is dead simple. That simplicity arises from the fact that the Windows Forms TreeView
is completely inflexible, offers no support for UI virtualization, offers zero possibility for visual customizations, and since it does not support data binding, it requires you to store data in its nodes. The WinForms TreeView
is “good enough for government work”.
In contrast, the WPF TreeView
is extremely flexible, inherently supports UI virtualization (i.e., TreeViewItem
s are created on-demand), allows for full visual customization, and has full support for data binding. Those excellent features come at a price. They make the control more complicated than the WinForms TreeView
. Once you learn how to properly work with the WPF TreeView
, those complexities disappear, and it is quite easy to leverage the full power of the control.
If you are curious to see some examples of how the WPF TreeView
can be customized, check out this article and this article.
Background of ViewModel
Back in 2005, John Gossman blogged about the Model-View-ViewModel pattern that his team at Microsoft was using to create Expression Blend (then known as ‘Sparkle’). It is quite similar to Martin Fowler’s Presentation Model pattern, only it fills in the gap between the presentation model and the view with WPF’s rich data binding. After Dan Crevier wrote his fantastic DataModel-View-ViewModel series of blog posts, the (D)MVVM pattern started growing in popularity.
The (Data)Model-View-ViewModel pattern is similar to the classic Model-View-Presenter, except you have a model tailor-made for the View, called the ViewModel. The ViewModel contains all the UI-specific interfaces and properties necessary to make it easy to develop a user interface. The View binds to the ViewModel, and executes commands to request an action from it. The ViewModel, in turn, communicates with the Model, and tells it to update in response to user interaction.
This makes it easier to create a user interface (UI) for the application. The easier it is to slap a UI on an application, the easier it is for a technically challenged Visual Designer to create a beautiful UI in Blend. Also, the more loosely coupled the UI is to the application functionality, the more testable that functionality becomes. Who does not want a beautiful UI and a suite of clean, effective unit tests?
What Exactly Makes the TreeView so Difficult?
The TreeView
is actually quite easy to work with, provided you use it the correct way. Using it the correct way, paradoxically, means not directly using it at all! Naturally, you will need to set properties and call the occasional method directly on a TreeView
. That is inescapable, and there’s nothing wrong with doing it. However, if you find yourself getting deep into the guts of the control, then you are probably not taking the best approach. If your TreeView
is data bound and you find yourself trying to walk up and down the items programmatically, then you are not doing things the right way. If you find yourself hooking the ItemContainerGenerator
’s StatusChanged
event so that you can access a TreeViewItem
’s child items when they are eventually created, you are way off track! Trust me; it does not have to be so ugly and difficult. There is a better way!
The fundamental problem with treating the WPF TreeView
like the WinForms TreeView
is, as I mentioned previously, that they are very different controls. The WPF TreeView
allows you to generate its items via data binding. This means that it will create the TreeViewItem
s for you. Since TreeViewItem
s are being generated by the control, instead of by you, it is not guaranteed that a data object’s corresponding TreeViewItem
exists when you need it. You must ask the TreeView
’s ItemContainerGenerator
if it has generated the TreeViewItem
for you yet. If it has not, you must hook its StatusChanged
event to be notified when it has created its child elements.
The fun does not stop there! If you want to get a TreeViewItem
that is nested deep down in the tree, you must ask the item’s parent/owning TreeViewItem
, not the TreeView
control, if its ItemContainerGenerator
has created the item. But, how can you get a reference to that parent TreeViewItem
if its parent has not yet created it? What if the parent’s parent has not yet been generated either? And so on, and so on, and so on. It can be quite torturous.
As you can see, the WPF TreeView
is a complicated beast. If you try to use it the wrong way, it will not be easy. Fortunately, if you use it the right way, it is a piece of cake. So, let’s see how to use it the right way…
ViewModel Comes to the Rescue
WPF is great because it practically requires you to separate an application’s data from the UI. All of the problems listed in the previous section derive from trying to go against the grain and treat the UI as a backing store. Once you stop treating the TreeView
as a place to put data, and start treating it as a place to show data, everything starts working smoothly. This is where the idea of a ViewModel comes into play.
Rather than writing code that walks up and down the items in a TreeView
, it is better to create a ViewModel to which the TreeView
binds, and then write code that manipulates your ViewModel. Not only does this allow you to ignore the TreeView
’s complexities, it also allows you to write code that can be easily unit tested. It is next to impossible to write meaningful unit tests for classes that have intimate dependencies on the runtime behavior of a TreeView
, but it is easy to write unit tests for classes that know nothing about such nonsense.
Now, it is time to see how to implement these concepts.
The Demo Solution
This article is accompanied by two demo applications, available for download at the top of this page. The solution has two projects. The BusinessLib class library project contains simple domain classes, used as mere data transfer objects. It also contains a Database
class that instantiates and returns those data transfer objects. The other project, TreeViewWithViewModelDemo, contains the sample applications. Those apps consume the objects returned by the BusinessLib assembly, and wrap them in a ViewModel before displaying them in a TreeView
.
Here is a screenshot of the solution’s Solution Explorer:
Demo 1 – Family Tree with Text Search
The first demo application we will examine populates a TreeView
with a family tree. It provides a search capability, made available to the user at the bottom of the UI. This demo can be seen in the screenshot below:
When the user types in some search text and presses Enter, or clicks the 'Find' button, the first matching item will display. Continuing the search will cycle through each matching item. All of that logic is in the ViewModel. Before getting too far into how the ViewModel works, let’s first examine the surrounding code. Here is the TextSearchDemoControl
’s code-behind:
public partial class TextSearchDemoControl : UserControl
{
readonly FamilyTreeViewModel _familyTree;
public TextSearchDemoControl()
{
InitializeComponent();
Person rootPerson = Database.GetFamilyTree();
_familyTree = new FamilyTreeViewModel(rootPerson);
base.DataContext = _familyTree;
}
void searchTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
_familyTree.SearchCommand.Execute(null);
}
}
The constructor shows how we convert raw data objects into a ViewModel, and then set that as the DataContext
of the UserControl
. The Person
class, which resides in the BusinessLib assembly, is quite simple:
public class Person
{
readonly List<Person> _children = new List<Person>();
public IList<Person> Children
{
get { return _children; }
}
public string Name { get; set; }
}
PersonViewModel
Since the Person
class is what the app’s data access layer returns, it is definitely not suited for consumption by the UI. Each Person
object will eventually be wrapped by an instance of the PersonViewModel
class, enabling it to have extended semantics, such as being expanded and selected. The FamilyTreeViewModel
class, seen above, initiates the process of wrapping Person
objects inside of PersonViewModel
objects, as seen in that class’ constructor:
public FamilyTreeViewModel(Person rootPerson)
{
_rootPerson = new PersonViewModel(rootPerson);
_firstGeneration = new ReadOnlyCollection<PersonViewModel>(
new PersonViewModel[]
{
_rootPerson
});
_searchCommand = new SearchFamilyTreeCommand(this);
}
The private PersonViewModel
constructor recursively walks down the family tree, wrapping each Person
object in a PersonViewModel
. Those constructors are seen below:
public PersonViewModel(Person person)
: this(person, null)
{
}
private PersonViewModel(Person person, PersonViewModel parent)
{
_person = person;
_parent = parent;
_children = new ReadOnlyCollection<PersonViewModel>(
(from child in _person.Children
select new PersonViewModel(child, this))
.ToList<PersonViewModel>());
}
PersonViewModel
has two kinds of members: those related to presentation, and those related to the state of a Person
. The presentation properties are what a TreeViewItem
will bind to, and the state properties are bound to by the content of a TreeViewItem
. One of the presentation properties, IsSelected
, is shown below:
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
This property has nothing to do with a “person”, but is simply a state used to synchronize the View with the ViewModel. Note that the property’s setter calls into an OnPropertyChanged
method, which ends up raising the object’s PropertyChanged
event. That event is the sole member of the INotifyPropertyChanged
interface. INotifyPropertyChanged
is a UI-specific interface, which is why the PersonViewModel
class implements it, not the Person
class.
A more interesting example of a presentation member on PersonViewModel
is the IsExpanded
property. This property easily solves the problem of ensuring that a data object’s corresponding TreeViewItem
is expanded when necessary. Keep in mind, these types of issues can be extremely thorny and difficult to deal with when programming directly against the TreeView
itself.
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
if (_isExpanded && _parent != null)
_parent.IsExpanded = true;
}
}
As I mentioned before, PersonViewModel
also has properties related to the state of its underlying Person
object. Here is an example:
public string Name
{
get { return _person.Name; }
}
The User Interface
The XAML for the TreeView
that binds to the tree of PersonViewModel
s is quite straightforward. Note that the connection between the TreeViewItem
s and PersonViewModel
objects lies in the control’s ItemContainerStyle
:
<TreeView ItemsSource="{Binding FirstGeneration}">
<TreeView.ItemContainerStyle>
-->
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Another piece of this demo’s UI is the search area. That area provides the user with a TextBox
into which a search string is entered, and a 'Find' button to perform a search against the family tree. Here is the XAML for the search area:
<StackPanel
HorizontalAlignment="Center"
Margin="4"
Orientation="Horizontal"
>
<TextBlock Text="Search for:" />
<TextBox
x:Name="searchTextBox"
KeyDown="searchTextBox_KeyDown"
Margin="6,0"
Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}"
Width="150"
/>
<Button
Command="{Binding SearchCommand}"
Content="_Find"
Padding="8,0"
/>
</StackPanel>
Now, let’s see the code in FamilyTreeViewModel
that supports this user interface.
FamilyTreeViewModel
The search functionality is encapsulated in the FamilyTreeViewModel
class. The TextBox
containing the search text is bound to the SearchText
property, which is declared like this:
public string SearchText
{
get { return _searchText; }
set
{
if (value == _searchText)
return;
_searchText = value;
_matchingPeopleEnumerator = null;
}
}
When the user clicks the 'Find' button, the FamilyTreeViewModel
’s SearchCommand
executes. That command class is nested within FamilyTreeViewModel
, but the property that exposes it to the View is public
. That code is shown below:
public ICommand SearchCommand
{
get { return _searchCommand; }
}
private class SearchFamilyTreeCommand : ICommand
{
readonly FamilyTreeViewModel _familyTree;
public SearchFamilyTreeCommand(FamilyTreeViewModel familyTree)
{
_familyTree = familyTree;
}
public bool CanExecute(object parameter)
{
return true;
}
event EventHandler ICommand.CanExecuteChanged
{
add { }
remove { }
}
public void Execute(object parameter)
{
_familyTree.PerformSearch();
}
}
If you are familiar with my WPF techniques and philosophies, you might be surprised to see that I am not using a routed command here. I normally prefer routed commands, for a multitude of reasons, but in this situation, it is cleaner and simpler to use a plain ICommand
implementation instead. Note, be sure to read the comments in the CanExecuteChanged
event declaration.
The search logic has absolutely no dependencies on TreeView
or TreeViewItem
. It is simply walking over the ViewModel objects and setting the ViewModel properties. Attempting to write this code directly against the TreeView
API would be much more difficult and bug-prone. Here’s my search logic:
IEnumerator<PersonViewModel> _matchingPeopleEnumerator;
string _searchText = String.Empty;
void PerformSearch()
{
if (_matchingPeopleEnumerator == null || !_matchingPeopleEnumerator.MoveNext())
this.VerifyMatchingPeopleEnumerator();
var person = _matchingPeopleEnumerator.Current;
if (person == null)
return;
if (person.Parent != null)
person.Parent.IsExpanded = true;
person.IsSelected = true;
}
void VerifyMatchingPeopleEnumerator()
{
var matches = this.FindMatches(_searchText, _rootPerson);
_matchingPeopleEnumerator = matches.GetEnumerator();
if (!_matchingPeopleEnumerator.MoveNext())
{
MessageBox.Show(
"No matching names were found.",
"Try Again",
MessageBoxButton.OK,
MessageBoxImage.Information
);
}
}
IEnumerable<PersonViewModel> FindMatches(string searchText, PersonViewModel person)
{
if (person.NameContainsText(searchText))
yield return person;
foreach (PersonViewModel child in person.Children)
foreach (PersonViewModel match in this.FindMatches(searchText, child))
yield return match;
}
Demo 2 – Geographic Breakdown with Load-On-Demand
The next demo application populates a TreeView
with information about various places within a country. It deals with three different types of objects: Region
, State
, and City
. Each of those types has a corresponding presentation class, to which the TreeViewItem
s bind.
Each of the presentation classes derives from the TreeViewItemViewModel
base class, which provides all of the presentation-specific functionality seen in the previous demo’s PersonViewModel
class. In addition, the items in this demo are lazy-loaded, meaning that the program does not fetch an item’s children and add them to the object graph until the user tries to view them. You can see this demo in the screenshot below:
As I mentioned above, there are three separate data classes here, and each data class has an associated presentation class. All of those presentation classes derive from a TreeViewItemViewModel
, described by this interface:
interface ITreeViewItemViewModel : INotifyPropertyChanged
{
ObservableCollection<TreeViewItemViewModel> Children { get; }
bool HasDummyChild { get; }
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
TreeViewItemViewModel Parent { get; }
}
The LoadOnDemandDemoControl
’s code-behind looks like this:
public partial class LoadOnDemandDemoControl : UserControl
{
public LoadOnDemandDemoControl()
{
InitializeComponent();
Region[] regions = Database.GetRegions();
CountryViewModel viewModel = new CountryViewModel(regions);
base.DataContext = viewModel;
}
}
That constructor is simply loading up some data objects from the BusinessLib assembly, creating some UI-friendly wrappers out of them, and then letting the View bind to those wrappers. The View’s DataContext
is set to an instance of this class:
public class CountryViewModel
{
readonly ReadOnlyCollection<RegionViewModel> _regions;
public CountryViewModel(Region[] regions)
{
_regions = new ReadOnlyCollection<RegionViewModel>(
(from region in regions
select new RegionViewModel(region))
.ToList());
}
public ReadOnlyCollection<RegionViewModel> Regions
{
get { return _regions; }
}
}
The interesting code is in TreeViewItemViewModel
. It is mostly just a copy of the presentation logic seen in the previous demo’s PersonViewModel
, but with one interesting twist. TreeViewItemViewModel
has built-in support for load-on-demand of child items. That logic exists in the class’ constructor and the setter of the IsExpanded
property. The load-on-demand logic of TreeViewItemViewModel
is seen below:
protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren)
{
_parent = parent;
_children = new ObservableCollection<TreeViewItemViewModel>();
if (lazyLoadChildren)
_children.Add(DummyChild);
}
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
if (_isExpanded && _parent != null)
_parent.IsExpanded = true;
if (this.HasDummyChild)
{
this.Children.Remove(DummyChild);
this.LoadChildren();
}
}
}
public bool HasDummyChild
{
get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
}
protected virtual void LoadChildren()
{
}
The actual work of loading an object’s child items is left for the subclasses to handle. They override the LoadChildren
method to provide a type-specific implementation of loading child items. The RegionViewModel
class, seen below, overrides this method to load State
objects and create StateViewModel
wrapper objects.
public class RegionViewModel : TreeViewItemViewModel
{
readonly Region _region;
public RegionViewModel(Region region)
: base(null, true)
{
_region = region;
}
public string RegionName
{
get { return _region.RegionName; }
}
protected override void LoadChildren()
{
foreach (State state in Database.GetStates(_region))
base.Children.Add(new StateViewModel(state, this));
}
}
This demo's user interface only contains a TreeView
, which is configured with the following XAML:
<TreeView ItemsSource="{Binding Regions}">
<TreeView.ItemContainerStyle>
-->
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:RegionViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16"
Margin="3,0" Source="Images\Region.png" />
<TextBlock Text="{Binding RegionName}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate
DataType="{x:Type local:StateViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16"
Margin="3,0" Source="Images\State.png" />
<TextBlock Text="{Binding StateName}" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:CityViewModel}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16"
Margin="3,0" Source="Images\City.png" />
<TextBlock Text="{Binding CityName}" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
Conclusion
If you have ever battled with the WPF TreeView
, perhaps this article has shed some light on an alternate way of using that control. Once you start going with the flow, and stop trying to swim upstream, WPF makes life very easy for you. The hard part is letting go of your hard-earned knowledge and skills, and to adopt radically different ways of approaching the same problems.
Special Thanks
I would like to thank Sacha Barber for encouraging me to write this article. He also gave me invaluable feedback and requests while I worked on the demo applications. If it wasn’t for him, I probably would have never written this article.
Revision History
- May 22, 2008 – Created the article.