To get this working, you can use an attached behaviour, and you'll see that it's a clean MVVM strategy.
Create a WPF app and add this Xaml...
<Grid>
<TreeView>
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="bindTreeViewExpand:Behaviours.ExpandingBehaviour" Value="{Binding ExpandingCommand}"/>
</Style>
</TreeView.Resources>
<TreeViewItem Header="this" >
<TreeViewItem Header="1"/>
<TreeViewItem Header="2"><TreeViewItem Header="Nested"></TreeViewItem></TreeViewItem>
<TreeViewItem Header="2"/>
<TreeViewItem Header="2"/>
<TreeViewItem Header="2"/>
</TreeViewItem>
<TreeViewItem Header="that" >
<TreeViewItem Header="1"/>
<TreeViewItem Header="2"/>
<TreeViewItem Header="2"/>
<TreeViewItem Header="2"/>
<TreeViewItem Header="2"/>
</TreeViewItem>
</TreeView>
</Grid>
Then create a View Model like this...
public class ViewModel : INotifyPropertyChanged
{
public ICommand ExpandingCommand { get; set; }
public ViewModel()
{
ExpandingCommand = new RelayCommand(ExecuteExpandingCommand, CanExecuteExpandingCommand);
}
private void ExecuteExpandingCommand(object obj)
{
Console.WriteLine(@"Expanded");
}
private bool CanExecuteExpandingCommand(object obj)
{
return true;
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
I use the Relay Command, but you can use the Delegate Command interchangeably. The source for the Relay Command is at http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
Then create a separate class that looks like this...
public static class Behaviours
{
#region ExpandingBehaviour (Attached DependencyProperty)
public static readonly DependencyProperty ExpandingBehaviourProperty =
DependencyProperty.RegisterAttached("ExpandingBehaviour", typeof(ICommand), typeof(Behaviours),
new PropertyMetadata(OnExpandingBehaviourChanged));
public static void SetExpandingBehaviour(DependencyObject o, ICommand value)
{
o.SetValue(ExpandingBehaviourProperty, value);
}
public static ICommand GetExpandingBehaviour(DependencyObject o)
{
return (ICommand) o.GetValue(ExpandingBehaviourProperty);
}
private static void OnExpandingBehaviourChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TreeViewItem tvi = d as TreeViewItem;
if (tvi != null)
{
ICommand ic = e.NewValue as ICommand;
if (ic != null)
{
tvi.Expanded += (s, a) =>
{
if (ic.CanExecute(a))
{
ic.Execute(a);
}
a.Handled = true;
};
}
}
}
#endregion
}
Then import the name space of this class into your Xaml...
xmlns:bindTreeViewExpand="clr-namespace:BindTreeViewExpand" (your name space will be different!)
Resharper will do this for you, or give you an intellesense prompt.
Finally wire up the View Model. Use the quick and dirty method like this...
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
Then, after the name spaces are resolved and the wiring is correct, it will start to work. Anchor your debugger in the Execute method and observe that you get a RoutedEvent argument. You can parse this to get which Tree view item was expanded.
The key aspect in this solution is the behaviour being specified in the STYLE! So it is applied to each and every TreeViewItem. No code behind either (other than the behaviour).
The behaviour I listed above marks the event as handled. You may wish to change that depending upon the behaviour you are after.