1

I am having a WPF MVVM application which has a TreeView with all the static items maintained in XAML page. How do I know in my view-model which MenuItem is clicked so that I can show that respective page accordingly.

    <TreeView Height="Auto" HorizontalAlignment="Stretch" Margin="0" Name="MyTreeViewMenu" 
                      VerticalAlignment="Stretch" Width="Auto" Opacity="1" 
                     BorderThickness="1" BorderBrush="Black" Grid.Row="2">

        <TreeViewItem Header="Country" Width="Auto" HorizontalAlignment="Stretch" 
                      ></TreeViewItem>

        <TreeViewItem Header="View Details" Width="Auto" HorizontalAlignment="Stretch" IsEnabled="False">
                <TreeViewItem Header="User" />
                <TreeViewItem Header="Group" />
                <TreeViewItem Header="User Group" />
            </TreeViewItem>
    </TreeView>
Ibrahim Najjar
  • 19,178
  • 4
  • 69
  • 95
user2519971
  • 345
  • 3
  • 12
  • 27

4 Answers4

4

I suppose that Selected event will have same effect as a click in your case. To determine which one TreeViewItem was selected you should add event Trigger:

<TreeView Height="Auto" HorizontalAlignment="Stretch" Margin="0" Name="MyTreeViewMenu" 
                      VerticalAlignment="Stretch" Width="Auto" Opacity="1" 
                     BorderThickness="1" BorderBrush="Black" Grid.Row="2">

        <TreeViewItem Header="Country" Width="Auto" HorizontalAlignment="Stretch"></TreeViewItem>    
        <TreeViewItem Header="View Details" Width="Auto" HorizontalAlignment="Stretch" IsEnabled="False">
                <TreeViewItem Header="User" />
                <TreeViewItem Header="Group" />
                <TreeViewItem Header="User Group" />
            </TreeViewItem>
               <i:Interaction.Triggers>
                  <i:EventTrigger EventName="SelectedItemChanged">
                      <i:InvokeCommandAction 
                         Command="{Binding selectItemCommand}"
                         CommandParameter="{Binding SelectedItem, ElementName=MyTreeViewMenu}"/>
                  </i:EventTrigger>
              </i:Interaction.Triggers>
</TreeView>

As a result you can use and determine which item was selected by a parameter passed to Command.

ViewModel should look something like this:

private ICommand _selectItemCommand;
public ICommand selectItemCommand
{
    get
    {
        return _selectItemCommand ?? (_selectItemCommand = new RelayCommand(param => this.LoadPage(param)));
    }
}

private void LoadPage(object selectedMenuItem)
{
      ...
}
Anatolii Gabuza
  • 6,184
  • 2
  • 36
  • 54
  • `TreeViewItem.Selected` is an event isn't it? How do you use a `StaticResource` to match a `Command` with an event? Also, the request was for a MVVM solution. – Sheridan Aug 16 '13 at 08:00
  • Added command and properties in my view model. Though command is being called on menu item selection but how to get selectedmenuItem ? See below comments. – user2519971 Aug 16 '13 at 09:33
  • private string _SelectedItem; public string SelectedItem { get { return _SelectedItem; } set { _SelectedItem = value; FirePropertyChanged("SelectedItem"); } } private ICommand _selectItemCommand; public ICommand selectItemCommand { get { return _selectItemCommand ?? (_selectItemCommand = new DelegateCommand(() => { LoadPage(); })); } } – user2519971 Aug 16 '13 at 09:34
  • @user2519971 In my example there is a `CommandParameter` which stands for `SelectedItem` that will be passed to your method. With paremeterless method this will not work. You'll have to modify command to have corresponding parameter. – Anatolii Gabuza Aug 16 '13 at 09:45
2

Take a look at the TreeView.SelectedItem Property page at MSDN.

You can bind directly to the TreeView.SelectedItem property:

<TreeView ItemsSource="{Binding Items}" SelectedItem="{Binding Item, Mode=OneWay}" />

Note that the TreeView.SelectedItem property is only read only, so you must use a OneWay binding... this means that you cannot set the selected item from your view model. To do that, you will need to create your own two way selected item property using an Attached Property.

EDIT >>>

My apologies @Scroog1, I normally use an AttachedProperty to do this. You are right that even with a OneWay binding, there is an error using this method. Unfortuately, my AttachedProperty code is long, but there is another way to do this.

I wouldn't necessarily recommend this as it's never really a good idea to put UI properties into your data objects, but if you add an IsSelected property to your data object, then you can bind it directly to the TreeViewItem.IsSelected property:

<TreeView ItemsSource="Items" HorizontalAlignment="Stretch" ... Name="MyTreeViewMenu">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsSelected" Value="{Binding IsSelected}" />
        </Style>
    </TreeView.ItemContainerStyle>
</TreeView>

I just searched and found a 'fuller' answer for you in the WPF MVVM TreeView SelectedItem post here on StackOverflow.

Alternatively, there is another way... you could also use the TreeView.SelectedValue and TreeView.SelectedValuePath properties. The basic idea is to set the TreeView.SelectedValuePath property to the name of a property on your data object. When an item is selected, the TreeView.SelectedValue property will then be set to the value of that property of the selected data item. You can find out more about this method from the How to: Use SelectedValue, SelectedValuePath, and SelectedItem page at MSDN. This generally works best if you have a uniquely identifiable property like an identifier of some kind. This code example is from MSDN:

<TreeView ItemsSource="{Binding Source={StaticResource myEmployeeData}, 
XPath=EmployeeInfo}" Name="myTreeView" SelectedValuePath="EmployeeNumber" />

<TextBlock Margin="10">SelectedValuePath: </TextBlock>
<TextBlock Margin="10,0,0,0" Text="{Binding ElementName=myTreeView, 
Path=SelectedValuePath}" Foreground="Blue"/>

<TextBlock Margin="10">SelectedValue: </TextBlock>
<TextBlock Margin="10,0,0,0" Text="{Binding ElementName=myTreeView, 
Path=SelectedValue}" Foreground="Blue"/>
Community
  • 1
  • 1
Sheridan
  • 68,826
  • 24
  • 143
  • 183
0

In addition to binding to the TreeView.SelectedItem property:

When using MVVM it helped me to stop thinking about events in the UI and start thinking about state in the UI.

You can bind the ViewModel to properties of the View. So in general I try to bind a SelectedItem to a property on the ViewModel so the ViewModel knows what is selected.

In the same way you could add a property to the ViewModel items being shown called Selected and bind this property to a checkbox in the View. That way you can enable multiple selection and access the selected items easily within the ViewModel.

Emond
  • 50,210
  • 11
  • 84
  • 115
0

For completeness, here are the attached property and TreeView subclass options:

Attached property option

public static class TreeViewSelectedItemHelper
{
    public static readonly DependencyProperty BindableSelectedItemProperty
        = DependencyProperty.RegisterAttached(
            "BindableSelectedItem",
            typeof (object),
            typeof (TreeViewSelectedItemHelper),
            new FrameworkPropertyMetadata(false,
                                          OnSelectedItemPropertyChanged)
                {
                    BindsTwoWayByDefault = true
                });

    public static object GetBindableSelectedItem(TreeView treeView)
    {
        return treeView.GetValue(BindableSelectedItemProperty);
    }

    public static void SetBindableSelectedItem(
        TreeView treeView, 
        object selectedItem)
    {
        treeView.SetValue(BindableSelectedItemProperty, selectedItem);
    }

    private static void OnSelectedItemPropertyChanged(
        DependencyObject sender,
        DependencyPropertyChangedEventArgs args)
    {
        var treeView = sender as TreeView;
        if (treeView == null) return;
        SetBindableSelectedItem(treeView, args.NewValue);
        treeView.SelectedItemChanged -= HandleSelectedItemChanged;
        treeView.SelectedItemChanged += HandleSelectedItemChanged;
        if (args.OldValue != args.NewValue)
            SetSelected(treeView, args.NewValue);
    }

    private static void SetSelected(ItemsControl treeViewItem,
                                    object itemToSelect)
    {
        foreach (var item in treeViewItem.Items)
        {
            var generator = treeViewItem.ItemContainerGenerator;
            var child = (TreeViewItem) generator.ContainerFromItem(item);
            if (child == null) continue;
            child.IsSelected = (item == itemToSelect);
            if (child.HasItems) SetSelected(child, itemToSelect);
        }
    }

    private static void HandleSelectedItemChanged(
        object sender,
        RoutedPropertyChangedEventArgs<object> args)
    {
        if (args.NewValue is TreeViewItem) return;
        var treeView = sender as TreeView;
        if (treeView == null) return;
        var binding = BindingOperations.GetBindingExpression(treeView,
            BindableSelectedItemProperty);
        if (binding == null) return;
        var propertyName = binding.ParentBinding.Path.Path;
        var property = binding.DataItem.GetType().GetProperty(propertyName);
        if (property != null)
            property.SetValue(binding.DataItem, treeView.SelectedItem, null);
    }
}

Subclass option

public class BindableTreeView : TreeView
{
    public BindableTreeView()
    {
        SelectedItemChanged += HandleSelectedItemChanged;
    }

    public static readonly DependencyProperty BindableSelectedItemProperty =
        DependencyProperty.Register(
            "BindableSelectedItem",
            typeof (object),
            typeof (BindableTreeView),
            new FrameworkPropertyMetadata(
                default(object),
                OnBindableSelectedItemChanged) {BindsTwoWayByDefault = true});

    public object BindableSelectedItem
    {
        get { return GetValue(BindableSelectedItemProperty); }
        set { SetValue(BindableSelectedItemProperty, value); }
    }

    private static void OnBindableSelectedItemChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        var treeView = d as TreeView;
        if (treeView != null) SetSelected(treeView, e.NewValue);
    }

    private static void SetSelected(ItemsControl treeViewItem,
                                    object itemToSelect)
    {
        foreach (var item in treeViewItem.Items)
        {
            var generator = treeViewItem.ItemContainerGenerator;
            var child = (TreeViewItem) generator.ContainerFromItem(item);
            if (child == null) continue;
            child.IsSelected = (item == itemToSelect);
            if (child.HasItems) SetSelected(child, itemToSelect);
        }
    }

    private void HandleSelectedItemChanged(
        object sender,
        RoutedPropertyChangedEventArgs<object> e)
    {
        SetValue(BindableSelectedItemProperty, SelectedItem);
    }
}
Scroog1
  • 3,539
  • 21
  • 26