Workarounds for supporting MVVM in the Silverlight TreeView Control

Posted by cibrax on ASP.net Weblogs See other posts from ASP.net Weblogs or by cibrax
Published on Thu, 27 Jan 2011 14:28:15 GMT Indexed on 2011/01/28 23:26 UTC
Read the original article Hit count: 559

Filed under:
|

MVVM (Model-View-ViewModel) is the pattern that you will typically choose for building testable user interfaces either in WPF or Silverlight. This pattern basically relies on the data binding support in those two technologies for mapping an existing model class (the view model) to the different parts of the UI or view.

Unfortunately, MVVM was not threated as first citizen for some of controls released out of the box in the Silverlight runtime or the Silverlight toolkit. That means that using data binding for implementing MVVM is not always something trivial and usually requires some customization in the existing controls.

In ran into different problems myself trying to fully support data binding in controls like the tree view or the context menu or things like drag & drop.  For that reason, I decided to write this post to show how the tree view control or the tree view items can be customized to support data binding in many of its properties.

In first place, you will typically use a tree view for showing hierarchical data so the view model somehow must reflect that hierarchy. An easy way to implement hierarchy in a model is to use a base item element like this one,

public abstract class TreeItemModel
{
    public abstract IEnumerable<TreeItemModel> Children;
}

You can later derive your concrete model classes from that base class. For example,

public class CustomerModel
{
    public string FullName { get; set; }
    public string Address { get; set; }
    public IEnumerable<OrderModel> Orders { get; set; }
}
 
public class CustomerTreeItemModel : TreeItemModel
{
    public CustomerTreeItemModel(CustomerModel customer)
    {
    }
 
    public override IEnumerable<TreeItemModel>  Children
    {
        get { // Return orders }
    }
}

The Children property in the CustomerTreeItem model implementation can return for instance an ObservableCollection<TreeItemModel> with the orders, so the tree view will automatically subscribe to all the changes in the collection.

You can bind this model to the tree view control in the UI by using a Hierarchical data template.

<e:TreeView x:Name="TreeView" ItemsSource="{Binding Customers}">
   <e:TreeView.ItemTemplate>
      <sdk:HierarchicalDataTemplate ItemsSource="{Binding Children}">
     <!-- TEMPLATE -->
      </sdk:HierarchicalDataTemplate>
   </e:TreeView.ItemTemplate>
</e:TreeView>

An interesting behavior with the Children property and the Hierarchical data template is that the Children property is only invoked before the expansion, so you can use lazy load at this point (The tree view control will not expand the whole tree in the first expansion).

The problem with using MVVM in this control is that you can not bind properties in model with specific properties of the TreeView item such as IsSelected or IsExpanded. Here is where you need to customize the existing tree view control to support data binding in tree items.

public class CustomTreeView : TreeView
{
    public CustomTreeView()
    {
    }
 
    protected override DependencyObject GetContainerForItemOverride()
    {
        CustomTreeViewItem tvi = new CustomTreeViewItem();
        
        Binding expandedBinding = new Binding("IsExpanded");
        expandedBinding.Mode = BindingMode.TwoWay;
        tvi.SetBinding(CustomTreeViewItem.IsExpandedProperty, expandedBinding);
        Binding selectedBinding = new Binding("IsSelected");
        selectedBinding.Mode = BindingMode.TwoWay;
        tvi.SetBinding(CustomTreeViewItem.IsSelectedProperty, selectedBinding);
        return tvi;
    }
}
 
public class CustomTreeViewItem : TreeViewItem
{
    public CustomTreeViewItem()
    {
        
    }
 
    protected override DependencyObject GetContainerForItemOverride()
    {
        CustomTreeViewItem tvi = new CustomTreeViewItem();
        Binding expandedBinding = new Binding("IsExpanded");
        expandedBinding.Mode = BindingMode.TwoWay;
        tvi.SetBinding(CustomTreeViewItem.IsExpandedProperty, expandedBinding);
        Binding selectedBinding = new Binding("IsSelected");
        selectedBinding.Mode = BindingMode.TwoWay;
        tvi.SetBinding(CustomTreeViewItem.IsSelectedProperty, selectedBinding);
        return tvi;
    }
}

You basically need to derive the TreeView and TreeViewItem controls to manually add a binding for the properties you need. In the example above, I am adding a binding for the “IsExpanded” and “IsSelected” properties in the items. The model for the tree items now needs to be extended to support those properties as well,

public abstract class TreeItemModel : INotifyPropertyChanged
{
    bool isExpanded = false;
    bool isSelected = false;
 
    public abstract IEnumerable<TreeItemModel> Children { get; }
 
    public bool IsExpanded
    {
        get 
        { 
            return isExpanded; 
        }
        set 
        { 
            isExpanded = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("IsExpanded"));
        }
    }
 
    public bool IsSelected
    {
        get
        {
            return isSelected;
        }
        set
        {
            isSelected = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
        }
    }
 
    public event PropertyChangedEventHandler PropertyChanged;
}

However, as soon as you use this custom tree view control, you lose all the automatic styles from the built-in toolkit themes because they are tied to the control type (TreeView in this case).  The only ugly workaround I found so far for this problem is to copy the styles from the Toolkit source code and reuse them in the application.

© ASP.net Weblogs or respective owner

Related posts about .NET

Related posts about Silverlight