views:

29

answers:

1

I have problems with binding a treeview to a datagrid's selected item.

they are in different views, but datagrid's selected item is already passed to treeview's related viewmodel. There is a SelectedGroup property in treeview's related viewmodel which is datagrid's selected item and its type is Group. I want to bind the ID field of Group to treeview, i.e. I want the ID of selected item to be selected in treeview and also be updated by selected value of treeview. I couldn't find out how to bind. Here's my treeview's skeleton, which can just lists all of the groups hierarchically. Can anyone help me on filling the required fields please? Thanks in advance.

    <TreeView Grid.Column="1" Grid.Row="4" Height="251" HorizontalAlignment="Left"
        Margin="4,3,0,0" Name="parentGroupTreeView" VerticalAlignment="Top" 
        Width="246" ItemsSource="{Binding Groups}" ItemContainerStyle="{x:Null}"  
        SelectedValuePath="ID">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding 
                 Converter={x:Static Member=conv:GroupSubGroupsConv.Default}}">
                    <Label Name="groupLabel" Content="{Binding GroupName}"/>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>                
        </TreeView>
A: 

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
   }
}
Zamboni