Click here to Skip to main content
Click here to Skip to main content

Simplifying the WPF TreeView by Using the ViewModel Pattern

By , 22 May 2008
Rate this:
Please Sign up or sign in to vote.

TreeViewWithViewModel/fengshui-bonsai.jpg

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., TreeViewItems 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 TreeViewItems for you. Since TreeViewItems 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:

TreeViewWithViewModel/solutionexplorer.png

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:

TreeViewWithViewModel/familytree_screenshot.png

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();

        // Get raw family tree data from a database.
        Person rootPerson = Database.GetFamilyTree();

        // Create UI-friendly wrappers around the 
        // raw data objects (i.e. the view-model).
        _familyTree = new FamilyTreeViewModel(rootPerson);

        // Let the UI bind to the view-model.
        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:

/// <summary>
/// A simple data transfer object (DTO) that contains raw data about a person.
/// </summary>
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:

/// <summary>
/// Gets/sets whether the TreeViewItem 
/// associated with this object is selected.
/// </summary>
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.

/// <summary>
/// Gets/sets whether the TreeViewItem 
/// associated with this object is expanded.
/// </summary>
public bool IsExpanded
{
    get { return _isExpanded; }
    set
    {
        if (value != _isExpanded)
        {
            _isExpanded = value;
            this.OnPropertyChanged("IsExpanded");
        }

        // Expand all the way up to the root.
        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 PersonViewModels is quite straightforward. Note that the connection between the TreeViewItems and PersonViewModel objects lies in the control’s ItemContainerStyle:

<TreeView ItemsSource="{Binding FirstGeneration}">
  <TreeView.ItemContainerStyle>
    <!-- 
    This Style binds a TreeViewItem to a PersonViewModel. 
    -->
    <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:

/// <summary>
/// Gets/sets a fragment of the name to search for.
/// </summary>
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:

/// <summary>
/// Returns the command used to execute a search in the family tree.
/// </summary>
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
    {
        // I intentionally left these empty because
        // this command never raises the event, and
        // not using the WeakEvent pattern here can
        // cause memory leaks. WeakEvent pattern is
        // not simple to implement, so why bother.
        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;

    // Ensure that this person is in view.
    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 TreeViewItems 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:

TreeViewWithViewModel/loadondemand_screenshot.png

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:

/// <summary>
/// The ViewModel for the LoadOnDemand demo. This simply
/// exposes a read-only collection of regions.
/// </summary>
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);
}

/// <summary>
/// Gets/sets whether the TreeViewItem 
/// associated with this object is expanded.
/// </summary>
public bool IsExpanded
{
    get { return _isExpanded; }
    set
    {
        if (value != _isExpanded)
        {
            _isExpanded = value;
            this.OnPropertyChanged("IsExpanded");
        }

        // Expand all the way up to the root.
        if (_isExpanded && _parent != null)
            _parent.IsExpanded = true;

        // Lazy load the child items, if necessary.
        if (this.HasDummyChild)
        {
            this.Children.Remove(DummyChild);
            this.LoadChildren();
        }
    }
}

/// <summary>
/// Returns true if this object's Children have not yet been populated.
/// </summary>
public bool HasDummyChild
{
    get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
}

/// <summary>
/// Invoked when the child items need to be loaded on demand.
/// Subclasses can override this to populate the Children collection.
/// </summary>
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>
    <!-- 
    This Style binds a TreeViewItem to a TreeViewItemViewModel. 
    -->
    <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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Josh Smith
Software Developer (Senior) Cynergy Systems
United States United States
Josh creates software, for iOS and Windows.
 
He works at Cynergy Systems as a Senior Experience Developer.
 
Read his iOS Programming for .NET Developers[^] book to learn how to write iPhone and iPad apps by leveraging your existing .NET skills.
 
Use his Master WPF[^] app on your iPhone to sharpen your WPF skills on the go.
 
Check out his Advanced MVVM[^] book.
 
Visit his WPF blog[^] or stop by his iOS blog[^].
Follow on   Twitter

Comments and Discussions

 
Questionabout database PinmemberMustafaMahmoud5-Jun-11 5:09 
AnswerRe: about database PinmemberDominic_777-Jun-11 8:31 
AnswerRe: about database PinmemberMember 766727417-Nov-11 2:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140415.2 | Last Updated 22 May 2008
Article Copyright 2008 by Josh Smith
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid