Problem
How can I get the item selected in a WPF-treeview back? Because I want to bind it, I’d like to accomplish it in XAML.
You might believe it’s SelectedItem, but it doesn’t exist and is readonly, rendering it useless.
This is what I’d like to accomplish:
<TreeView ItemsSource="{Binding Path=Model.Clusters}"
ItemTemplate="{StaticResource ClusterTemplate}"
SelectedItem="{Binding Path=Model.SelectedCluster}" />
I’d like to link the SelectedItem to a Model property.
However, this results in the following error:
Okay, here’s how I came up with a solution:
<TreeView
ItemsSource="{Binding Path=Model.Clusters}"
ItemTemplate="{StaticResource HoofdCLusterTemplate}"
SelectedItemChanged="TreeView_OnSelectedItemChanged" />
and in my xaml’s codebehindfile:
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
Model.SelectedCluster = (Cluster)e.NewValue;
}
Asked by Natrium
Solution #1
I’m aware that this has already been answered, but I put this together to help with the situation. It’s based on the same concept as Delta’s, but without the need to subclass the TreeView:
public class BindableSelectedItemBehavior : Behavior<TreeView>
{
#region SelectedItem Property
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = e.NewValue as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (this.AssociatedObject != null)
{
this.AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
this.SelectedItem = e.NewValue;
}
}
You can then use this as follows in your XAML:
<TreeView>
<e:Interaction.Behaviors>
<behaviours:BindableSelectedItemBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
</e:Interaction.Behaviors>
</TreeView>
Hopefully, it will be of assistance to someone!
Answered by Steve Greatrex
Solution #2
TreeView.SelectedItem is a property that exists.
However, because it is read-only, you can’t assign it to a binding; you can only retrieve it.
Answered by Thomas Levesque
Solution #3
So, I came up with a solution. It cleans up the mess so that MVVM can work.
First, add the following class:
public class ExtendedTreeView : TreeView
{
public ExtendedTreeView()
: base()
{
this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(___ICH);
}
void ___ICH(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (SelectedItem != null)
{
SetValue(SelectedItem_Property, SelectedItem);
}
}
public object SelectedItem_
{
get { return (object)GetValue(SelectedItem_Property); }
set { SetValue(SelectedItem_Property, value); }
}
public static readonly DependencyProperty SelectedItem_Property = DependencyProperty.Register("SelectedItem_", typeof(object), typeof(ExtendedTreeView), new UIPropertyMetadata(null));
}
and include the following in your xaml:
<local:ExtendedTreeView ItemsSource="{Binding Items}" SelectedItem_="{Binding Item, Mode=TwoWay}">
.....
</local:ExtendedTreeView>
Answered by Delta
Solution #4
It provides a little more information than the OP had hoped for… But I’m hoping it will benefit at least one person.
If you wish to run an ICommand whenever the SelectedItem changes, you can bind a command to an event instead of using the ViewModel’s SelectedItem property.
To do so:
1- Add a System reference. Windows.Interactivity
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
2- Assign the command to the occurrence. SelectedItemChanged
<TreeView x:Name="myTreeView" Margin="1"
ItemsSource="{Binding Directories}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding SomeCommand}"
CommandParameter="
{Binding ElementName=myTreeView
,Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemTemplate>
<!-- ... -->
</TreeView.ItemTemplate>
</TreeView>
Answered by JiBéDoublevé
Solution #5
Only binding and the GalaSoft MVVM Light library’s EventToCommand can be used to achieve this in a ‘nicer’ approach. Add a command to your VM that will be called when the selected item is changed, and set it to perform whatever action is required. I used a RelayCommand in this example, and all I did was set the SelectedCluster attribute.
public class ViewModel
{
public ViewModel()
{
SelectedClusterChanged = new RelayCommand<Cluster>( c => SelectedCluster = c );
}
public RelayCommand<Cluster> SelectedClusterChanged { get; private set; }
public Cluster SelectedCluster { get; private set; }
}
Then, in your xaml, add the EventToCommand behavior. Blend makes this very simple.
<TreeView
x:Name="lstClusters"
ItemsSource="{Binding Path=Model.Clusters}"
ItemTemplate="{StaticResource HoofdCLusterTemplate}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding SelectedClusterChanged}" CommandParameter="{Binding ElementName=lstClusters,Path=SelectedValue}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
Answered by bstoney
Post is based on https://stackoverflow.com/questions/1000040/data-binding-to-selecteditem-in-a-wpf-treeview