I have a typical treeview and a viewmodel. The viewmodel has an observable collection of other viewmodels that serves as a data source for the tree.
public class TreeViewVM {
public ObservableCollection<ItemVM> Items { get; private set; }
public ItemVM SelectedItem { get; set; }
}
and the ItemVM :
public class ItemVM {
public string Name { get; set; }
public ImageSource Image { get; private set; }
public ObservableCollection<ItemVM> Children { get; private set; }
public ICommand Rename { get; private set; }
}
The view :
<TreeView Selecteditem="{Binding SelectedItem}" ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.InputBindings>
<KeyBinding Key="F2" Command="{Binding Rename}"/>
</StackPanel.InputBindings>
<Image Source="{Binding Image}"/>
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
However my command will not be invoked no matter what I try as long as it is "inside" the HierarchicalDataTemplate.
If I move the KeyBinding in the TreeView.InputBindings (and the ICommand / RelayCommand from the ItemVM to the TreeViewVM) all is nice, the command gets invoked.
But I would like to have the command on the ItemVM (as it is where it makes sense). Any ideas?
The key binding would need to be defined on the TreeViewItem, as that is the element with the focus. The problem is you cannot define key bindings using a Style, which is what you'd probably want to do here.
Here is one workaround that uses a custom attached property to add items to the InputBinding collection via a Style. So you'd want to use something like that to define your Style, which you'd assign to TreeView.ItemContainerStyle.
But I would like to have the command on the ItemVM (as it is where it makes sense). Any ideas?
If TreeViewVM
tracks the selected item through the SelectedItem
property you can define InputBindings
on TreeView
and still have the commands implemented on the ItemVM
:
<TreeView ItemsSource="{Binding Items}">
<TreeView.InputBindings>
<KeyBinding Key="F2" Command="{Binding SelectedItem.Rename}"/>
</TreeView.InputBindings>
</TreeView>
Notice how the subproperty syntax SelectedItem.Rename
is employed to use the ItemVM
as the source of the binding.
Unfortunately, it is slightly tedious to bind to the selected item on a TreeView
. You cannot bind directly to SelectedItem
(as your XAML seems to suggest) but there exists various methods to overcome this limitation. One simple method I like is to use Blend Interativity:
<TreeView Name="treeView" ItemsSource="{Binding Items}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding SetSelectedItemCommand}" CommandParameter="{Binding SelectedItem, ElementName=treeView}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
You will have to implement a SetSeletectedItemCommand
on TreeViewVM
that sets the property SelectedItem
.