In a new WPF application I'm writing I needed to bind the "SelectedItem" property of the TreeView control to the "SelectedItem" property of its ViewModel; but, alas, the TreeView's "SelectedItem" property is read-only...
After some googling I found the Versatile TreeView by Philip Sumi which adds the property I wanted, but since I'm learning WPF and want a deeper understanding I decided to write my own (and use Philip's TreeView as reference).
In the code I noticed a lot of dependency properties, and I figured its time to learn what are dependency properties and how do they work; again, some googling brought me to this post which I think gives a very good explanation of dependency properties.
Now that I knew what I had to do, I started writing the EnhancedTreeView
class
(which is based on the TreeView class).
At first I thought about overriding the SelectedItem
property but it didn't go
very smooth so I added the SelectedObject
property.
I added a callback to the dependency-property-changed event (see
SelectedObjectChangedCallback
) and when the property was changed I needed to
change the selected item; but, alas again!, the selected item is
read-only...
Some more googling brought me to this blog post which uses the
ItemsContainerGenerator
to find the matching TreeViewItem
for the object I
want to select.
And here's the final code:
/// <summary> /// This treeview class allows databinding the /// "SelectedObject" property /// </summary> public class EnhancedTreeView : TreeView { #region SelectedObject /// <summary> /// The dependency property that allows use to bind the /// "SelectedObject" property /// </summary> public static readonly DependencyProperty SelectedObjectProperty = DependencyProperty.Register( "SelectedObject", typeof(object), typeof(EnhancedTreeView), new PropertyMetadata(SelectedObjectChangedCallback)); /// <summary> /// Gets or sets the select object (a writable version of /// the "SelectedItem" property) /// </summary> [Bindable(true)] public object SelectedObject { get { return GetValue(SelectedObjectProperty); } set { SetValue(SelectedObjectProperty, value); } } /// <summary> /// This method is called whenever ever the selected /// object is changed, and if it was changed from the /// outside, this method will set the selected item. /// </summary> /// <param name="obj"></param> /// <param name="eventArgs"></param> private static void SelectedObjectChangedCallback (DependencyObject obj, DependencyPropertyChangedEventArgs eventArgs) { EnhancedTreeView treeView = (EnhancedTreeView)obj; if (!ReferenceEquals(treeView.SelectedItem, eventArgs.NewValue)) SelectItem(treeView, eventArgs.NewValue); } #endregion /// <summary> /// Searches the given item in the parent (recursively) /// and selects it, returns true if the item was found /// and selected, false otherwise. /// </summary> /// <param name="parent"></param> /// <param name="itemToSelect"></param> /// <returns></returns> private static bool SelectItem (ItemsControl parent, object itemToSelect) { var childTreeNode = parent.ItemContainerGenerator .ContainerFromItem(itemToSelect) as TreeViewItem; // if the item to select is directly under "parent", // just select it if (childTreeNode != null) { childTreeNode.Focus(); return childTreeNode.IsSelected = true; } // if the item to select is not directly under "parent", // search the child nodes of "parent" if (parent.Items.Count > 0) { foreach (object childItem in parent.Items) { var childItemsControl = parent.ItemContainerGenerator .ContainerFromItem(childItem) as ItemsControl; if (SelectItem(childItemsControl, itemToSelect)) return true; } } // if the given item wasn't found here: return false; } /// <summary> /// When the selected item is updated from inside the tree, /// this method will update the "SelectedObject" property. /// </summary> /// <param name="e"></param> protected override void OnSelectedItemChanged (RoutedPropertyChangedEventArgs<object> e) { this.SelectedObject = e.NewValue; base.OnSelectedItemChanged(e); } }
To use the EnhancedTreeView all you need is to bind the SelectedObject
property:
<controls:EnhancedTreeView SelectedObject="{Binding SelectedObject, Mode=TwoWay}" ItemsSource="..." />
It's important to add Mode=TwoWay
to the binding, otherwise it won't work.