MVVM – Binding TreeView Item Changed to ICommand
February 19, 2012 6 Comments
Download Sample Project here:
http://www.box.com/s/0e8rokffhp9p0pshb6b6
After browsing the web for more tha an hour there were quite some post which helped me to get a clue on how to bind a Tree View item selected.
All these posts gave me call back, on which I had to fire an event again, and add a subscription to it.
I wanted that call back subscription to be an ICommand, so that it is in sync with MVVM model.
But to bring the Call back into the View model had to go an extra mile.
I had to synchronize two dependency property.
1. A dependency property to bind to TreeView Item Selected .
2. A dependency property to bind an ICommand, to TreeView Item Selected Changed.
Dependency property to bind the TreeView Item Selected:
You need a dependency property which would hold the Tree View Item Selected value.
This property should be always updated with the New value, so that you would get an call back on SelectedItemChanged when a new item in the tree is selected.
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.RegisterAttached("SelectedItem",
typeof(object),
typeof(TreeViewHelper),
new UIPropertyMetadata(null, SelectedItemChanged));
Dependency property to bind an ICommand, when an TreeView Item Selected is changed:
You need a second dependency property which you could bind to an ICommand and execute when ever a new item in the treeview is selected.
public static readonly DependencyProperty SelectedItemChangedProperty =
DependencyProperty.RegisterAttached("SelectedItemChanged",
typeof(ICommand),
typeof(TreeViewHelper));
Code to fire the ICommand on SelectedItemProperty call back events
private static void SelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is TreeView) || e.NewValue == null)
return;
var view = obj as TreeView;
view.SelectedItemChanged += (sender, e2) => SetSelectedItem(view, e2.NewValue);
var command = (ICommand) (view as DependencyObject).GetValue(SelectedItemChangedProperty);
if (command != null)
{
if (command.CanExecute(null))
command.Execute(new DependencyPropertyEventArgs(e));
}
}
Here is the xaml code to bind these two dependency properties
<UserControl x:Class="TreeViewItemChangedMvvm.View.MyTreeViewControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ViewModelUtils="clr-namespace:TreeViewItemChangedMvvm.ViewModelUtils" mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TreeView ItemsSource="{Binding ItemsSource}"
ViewModelUtils:TreeViewHelper.SelectedItem="{Binding CurrSelItem}"
ViewModelUtils:TreeViewHelper.SelectedItemChanged="{Binding MySelItemChgCmd}" />
</Grid>
</UserControl>
Thanks:
http://stackoverflow.com/questions/1000040/selecteditem-in-a-wpf-treeview
Sample Project:
http://www.box.com/s/0e8rokffhp9p0pshb6b6
Great work on this. I’ve got a slight problem that you may be able to help with. I’ve got my treeview in a tabcontrol but I’m finding if I switch tabs that when I go back the treeview is reset visually but it does remember the CurrSelItem. My question is whether there’s a way to get the treeview to expand to show what the selected item is.
@Mike: Happy to know it helped… You could bind the “IsExpanded” property and add a Trigger to it.
Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"
Great work. Helped me to save a lot of time. Unfortunally the provided download doesnt work. The property CurrselItem is never set. After a little bit investigation I found the reason: The binding must be declared explicitly as TwoWay.
ViewModelUtils:TreeViewHelper.SelectedItem=”{Binding CurrSelItem, Mode=TwoWay}”
There’s another issue with Your solution:
if (!(obj is TreeView) || e.NewValue == null)
return;
var view = obj as TreeView;
view.SelectedItemChanged += (sender, e2) => SetSelectedItem(view, e2.NewValue);
This piece of code in SelectedItemChanged in TreeeViewHelper causes adding a new event handler each time a tree view item is clicked.
This would be a q’nd fix:
if (!(obj is TreeView) || e.NewValue == null)
return;
var view = obj as TreeView;
if (!_wired)
{
view.SelectedItemChanged += (sender, e2) => SetSelectedItem(view, e2.NewValue);
_wired = true;
}
where _wired is a static bool …
Happy to know it helped..
>> Unfortunally the provided download doesn’t work.
Apologize! But I just checked, the link works fine.
https://www.box.com/s/0e8rokffhp9p0pshb6b6
>> The binding must be declared explicitly as TwoWay.
Agree! In my demo it was just OneWay to pop up a messageBox on SelectedItemChanged,
>> This piece of code in SelectedItemChanged in TreeeViewHelper causes adding a new event handler each time a tree view item is clicked.
Agree! But I guess the solution should be object specific and not global (static).
May be Unsubscribing before Subscription could be an idea..
view.SelectedItemChanged -= (sender, e2) => SetSelectedItem(view, e2.NewValue);
view.SelectedItemChanged += (sender, e2) => SetSelectedItem(view, e2.NewValue);