Zackary Lee
Zackary Lee

Reputation: 11

How can I bind a command to the Selected property of a TreeViewItem generated in a DataTemplate?

I have a TreeView that's items are generated from HierarchicalDataTemplates. I need something to happen when each item is selected, and I just don't know how to do that the way it is right now.

<TreeView ItemsSource="{Binding Servers}" Width="250" Height="410">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type data:Server}" ItemsSource="{Binding Path=Children}">
            <TextBlock Text="{Binding Path=Name}"/>
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate DataType="{x:Type data:Database}" ItemsSource="{Binding Path=Children}">
            <TextBlock Text="{Binding Path=Name}"/>
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate DataType="{x:Type data:Table}" ItemsSource="{Binding Path=Children}">
            <TextBlock Text="{Binding Path=Name}"/>
        </HierarchicalDataTemplate>
        <DataTemplate DataType="{x:Type data:Index}">
            <TextBlock Text="{Binding Path=Name}"/>
        </DataTemplate>
    </TreeView.Resources>
</TreeView>

Upvotes: 0

Views: 42

Answers (2)

thatguy
thatguy

Reputation: 22079

Command binding behavior

You can create a behavior that handles the SelectedItemChanged event and exposes a dependency property for binding a command in a view model to be executed with the SelectedItem.

public class SelectionChangedCommandBehavior : Behavior<TreeView>
{
   public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
      nameof(Command), typeof(object), typeof(SelectionChangedCommandBehavior), new PropertyMetadata(null));

   public ICommand Command
   {
      get => (ICommand)GetValue(CommandProperty);
      set => SetValue(CommandProperty, value);
   }

   protected override void OnAttached()
   {
      base.OnAttached();
      AssociatedObject.SelectedItemChanged += OnSelectedItemChanged;
   }

   protected override void OnDetaching()
   {
      base.OnDetaching();
      AssociatedObject.SelectedItemChanged -= OnSelectedItemChanged;
   }

   private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
   {
      var command = Command;
      var selectedItem = AssociatedObject.SelectedItem;

      if (command != null && command.CanExecute(selectedItem))
         command.Execute(selectedItem);
   }
}

You have to assign the behavior to your TreeView and then bind the Command property accordingly.

<TreeView ItemsSource="{Binding Servers}" Width="250" Height="410">
   <i:Interaction.Behaviors>
      <local:SelectionChangedCommandBehavior Command="{Binding SelectionChangedCommand}"/>
   </i:Interaction.Behaviors>
</TreeView>

For the behavior you need to install the Microsoft.Xaml.Behaviors.Wpf NuGet package to access the Behavior<T> base class. Add this namespace in your XAML to use i:Interaction.Behaviors.

xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

Upvotes: 1

Andy
Andy

Reputation: 12276

If you bound isselected from the container to each viewmodel, you could act in the setter when the property changes to true.

<Style TargetType="{x:Type TreeViewItem}">
    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />

You would need an isselected property in all your classes server, database etc. Put your logic in a method called from the property setter.

Upvotes: 0

Related Questions