Start by taking a look at the following article by Josh Smith on Simplifying the WPF TreeView by Using the ViewModel Pattern.
I am also using the DataGrid from the WPF toolkit.
To get a sense as to how this code works look at the IsSelected property below.
Here is the XAML that contains a tree and a datagrid:
<Window x:Class="TreeviewDatagrid.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfToolkit="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
xmlns:ViewModels="clr-namespace:TreeviewDatagrid.ViewModels" Title="Main Window" Height="400" Width="800">
<DockPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="7*"/>
</Grid.ColumnDefinitions>
<TreeView ItemsSource="{Binding Groups}"
Grid.Column="0">
<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 ViewModels:GroupViewModel}"
ItemsSource="{Binding Children}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GroupName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
<WpfToolkit:DataGrid
Grid.Column="1"
SelectedItem="{Binding Path=SelectedGroup, Mode=TwoWay}"
ItemsSource="{Binding Path=Groups, Mode=OneWay}" >
</WpfToolkit:DataGrid>
</Grid>
</DockPanel>
</Window>
Here is the main view model that the TreeView and DataGrid bind to:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using TreeviewDatagrid.Models;
namespace TreeviewDatagrid.ViewModels
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Group g1 = new Group();
g1.Id = 1;
g1.GroupName = "Planners";
g1.Description = "People who plan";
GroupViewModel gvm1 = new GroupViewModel(this, g1);
Group g2 = new Group();
g2.Id = 2;
g2.GroupName = "Thinkers";
g2.Description = "People who think";
GroupViewModel gvm2 = new GroupViewModel(this, g2);
Group g3 = new Group();
g3.Id = 3;
g3.GroupName = "Doers";
g3.Description = "People who do";
GroupViewModel gvm3 = new GroupViewModel(this, g3);
IList<GroupViewModel> list = new List<GroupViewModel>();
list.Add(gvm1);
list.Add(gvm2);
list.Add(gvm3);
_selectedGroup = gvm1;
_groups = new ReadOnlyCollection<GroupViewModel>(list);
}
readonly ReadOnlyCollection<GroupViewModel> _groups;
public ReadOnlyCollection<GroupViewModel> Groups
{
get { return _groups; }
}
private GroupViewModel _selectedGroup;
public GroupViewModel SelectedGroup
{
get
{
return _selectedGroup;
}
set
{
// keep selection in grid in-sync with tree
_selectedGroup.IsSelected = false;
_selectedGroup = value;
_selectedGroup.IsSelected = true;
OnPropertyChanged("SelectedGroup");
}
}
public void ChangeSelectedGroup(GroupViewModel selectedGroup)
{
_selectedGroup = selectedGroup;
OnPropertyChanged("SelectedGroup");
}
}
}
Here is the viewmodel that I use to bind to the grid and the tree:
using TreeviewDatagrid.Models;
namespace TreeviewDatagrid.ViewModels
{
public class GroupViewModel : TreeViewItemViewModel
{
private readonly MainViewModel _mainViewModel;
readonly Group _group;
bool _isSelected;
public GroupViewModel(MainViewModel mainViewModel, Group group) : base(null, true)
{
_mainViewModel = mainViewModel;
_group = group;
}
public string GroupName
{
get { return _group.GroupName; }
}
public override bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
if (_isSelected )
{
// keep tree selection in sync with grid
_mainViewModel.ChangeSelectedGroup(this);
}
this.OnPropertyChanged("IsSelected");
}
}
}
protected override void LoadChildren()
{
// load children in treeview here
}
}
}
For completeness here is the Group object:
namespace TreeviewDatagrid.Models
{
public class Group
{
public int Id { get; set; }
public string GroupName { get; set; }
public string Description { get; set; }
}
}
And also the base class for the TreeView:
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace TreeviewDatagrid.ViewModels
{
/// <summary>
/// Base class for all ViewModel classes displayed by TreeViewItems.
/// This acts as an adapter between a raw data object and a TreeViewItem.
/// </summary>
public class TreeViewItemViewModel : INotifyPropertyChanged
{
#region Data
static readonly TreeViewItemViewModel DummyChild = new TreeViewItemViewModel();
readonly ObservableCollection<TreeViewItemViewModel> _children;
readonly TreeViewItemViewModel _parent;
bool _isExpanded;
bool _isSelected;
#endregion // Data
#region Constructors
protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren)
{
_parent = parent;
_children = new ObservableCollection<TreeViewItemViewModel>();
if (lazyLoadChildren)
_children.Add(DummyChild);
}
// This is used to create the DummyChild instance.
private TreeViewItemViewModel()
{
}
#endregion // Constructors
#region Presentation Members
#region Children
/// <summary>
/// Returns the logical child items of this object.
/// </summary>
public ObservableCollection<TreeViewItemViewModel> Children
{
get { return _children; }
}
#endregion // Children
#region HasLoadedChildren
/// <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; }
}
#endregion // HasLoadedChildren
#region IsExpanded
/// <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();
}
}
}
#endregion // IsExpanded
#region IsSelected
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is selected.
/// </summary>
public virtual bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
#endregion // IsSelected
#region LoadChildren
/// <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()
{
}
#endregion // LoadChildren
#region Parent
public TreeViewItemViewModel Parent
{
get { return _parent; }
}
#endregion // Parent
#endregion // Presentation Members
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion // INotifyPropertyChanged Members
}
}