February 7, 2010 .net

Data Binding, Currency and the WPF TreeView

I was building a little WPF app to explore a hierarchical space (OData, if you must know), so of course, I was using the TreeView. And since I’m a big fan of data binding, of course I’ve got a hierarchical data source (basically):

abstract class Node {
public abstract string Name { get; }
  public abstract IEnumerable<Node> { get; }
public XDocument Document { get { ... } }
public Uri Uri { get { ... } }
}

I then bind to the data source (of course, you can do this in XAML, too):

// set the data context of the grid containing the treeview and text boxes
grid.DataContext = new Node[] { Node.GetNode(new Uri(uri)) };
// bind the treeview to the entire collection of nodes
leftTreeView.SetBinding(TreeView.ItemsSourceProperty, ".");
// bind each text box to a property on the current node
queryTextBox.SetBinding(TextBox.TextProperty,
  new Binding("Uri") { Mode = BindingMode.OneWay });
documentTextBox.SetBinding(TextBox.TextProperty,
  new Binding("Document") { Mode = BindingMode.OneWay });

What we’re trying to do here is leverage the idea of currency” in WPF where if you share the same data context, then item controls like textboxes will bind to the current” item as it’s changed by the list control. If this was a listview instead of a treeview, that would work great (so long as you set the IsSynchronizedWithCurrentItem property to true).

The problem, as my co-author and the-keeper-of-all-WPF-knowledge Ian Griffiths reminded me this morning, is that currency is based on a single collection, whereas a TreeView control is based on multiple collections, i.e. the one at the root and each one at sub-node, etc. So, as I change the selection on the top node, the treeview has no single collection’s current item to update (stored in an associated view” of the data), so it doesn’t update anything. As the user navigates from row to row, the current” item never changes and our textboxes are not updated.

So, Ian informed me of a common hack” to solve this problem. The basic idea is to forget about the magic current node” and explicitly bind each control to the treeview’s SelectedItem property. As it changes, regardless of which collection from whence the item came, each item control is updated, as data binding is supposed to work.

First, instead of setting the grid’s DataContext to the actual data, shared with the treeview and the textboxes, we bind it to the currently selected treeview item:

// bind the grid containing the treeview and text boxes
// to point at the treeview's currently selected item
grid.DataContext = new Binding("SelectedItem") { ElementName = "leftTreeView" };

Now, because we want the treeview to in fact show our hierarchical collection of nodes, we set it’s DataContext explicitly:

// set the treeview's DataContext to be the data we want it to show
leftTreeView.DataContext = new Node[] { Node.GetNode(new Uri(uri)) };

Now, the treeview will show the data we wanted it to show, as before, but as the user changes the selection, the treeview’s SelectedItem property changes, which updates the grid’s DataContext, which signals the textboxes, bound to properties on grid’s DataContext (because the DataContext property is inherited and we haven’t overridden it on the textboxes), and the textboxes are updated.

Or, in other words, the textboxes effectively have a new idea of the current” item that meshes with how the treeview works. Thanks, Ian!