So it turns out that the WinForms TreeView control doesn't support binding anything but a Text property to its nodes. This is a pretty lame oversight given what the control is used for (displaying a set of hierarchical data) and we had a need to find a way to bind complex types to the thing. Were this a few months ago we would have had to subclass the control, take in Objects, and do awkward things to make it happen. But now that .net 3.5 is my bff, a quick combination of extension methods and generics will do the trick nicely while still allowing you to use the default TreeView control on the designer surface. (note: you can do extension methods with a hack in .net 2.0 projects and make the same thing happen.)
I sat down and paired this solution with Brian the other day. TDD is your friend once again when writing code, as testing-first allows you to drive out the API and usage before you know explicitly what you want to do. So we knocked out a couple of tests, made them pass, and with just a handful of lines of code, we had a simple solution for binding complex types to a TreeView
The Code - A TreeNode subclass that supports the complex type binding, and 2 extension methods (one for the extended TreeNode, and one for the TreeView itself, to support both TreeViews having nodes, and TreeNodes having nodes). I would like to refactor the names a bit but you get the general idea.
public class ObjectBindableTreeNode<T>:TreeNode
{
private KeyValuePair<T, string> _boundType;
private T _boundValue;
public KeyValuePair<T, string> BoundType { get { return _boundType; } set { _boundType = value;
_boundValue = value.Key;
Text = value.Value;
} }
public T BoundItem { get { return _boundValue; } }
}
public static class TreeViewExtensions
{
public static void BindComplexDataSource<T>(this TreeView treeView, IDictionary<T, string> dataSource)
{
foreach(var item in dataSource)
{
var boundNode = new ObjectBindableTreeNode<T> {BoundType = item};
treeView.Nodes.Add(boundNode);
}
}
public static void BindComplexDataSource<T>(this ObjectBindableTreeNode<T> node, IDictionary<T, string> dataSource)
{
foreach (var item in dataSource)
{
var boundNode = new ObjectBindableTreeNode<T> { BoundType = item };
node.Nodes.Add(boundNode);
}
}
}
And the tests, which are a little noisy but show the usage (FakeComplexType is just an internal class on the test that has 3 properties)
[Test]
public void bindable_node_should_set_Text_member_when_binding_complex_type_to_a_property()
{
var testGuid = Guid.NewGuid();
var complexType = new FakeComplexType() {SomeDisplayValue = "Display", SomeValue = testGuid, FakeNumber = 4};
var bindableTreeNode = new ObjectBindableTreeNode<FakeComplexType>();
var dictItem = new KeyValuePair<FakeComplexType, string>(complexType, complexType.SomeDisplayValue);
bindableTreeNode.BoundType = dictItem;
bindableTreeNode.Text.ShouldEqual(complexType.SomeDisplayValue);
}
[Test]
public void binding_extension_method_on_TreeView_should_create_bindable_nodes()
{
var testGuid = Guid.NewGuid();
var complexType = new FakeComplexType() { SomeDisplayValue = "Display", e = testGuid, FakeNumber = 4 };
var dictionary = new Dictionary<FakeComplexType, string>();
dictionary.Add(complexType, complexType.SomeDisplayValue);
var treeView = new TreeView();
treeView.BindComplexDataSource(dictionary);
var testNode = treeView.Nodes[0] as ObjectBindableTreeNode<FakeComplexType>;
testNode.ShouldNotBeNull();
testNode.Text.ShouldEqual(complexType.SomeDisplayValue);
testNode.BoundItem.ShouldEqual(complexType);
}
[Test]
public void binding_extension_method_on_TreeNode_should_create_bindable_nodes()
{
var testGuid = Guid.NewGuid();
var complexType = new FakeComplexType() { SomeDisplayValue = "Display", SomeValue = testGuid, FakeNumber = 4 };
var dictionary = new Dictionary<FakeComplexType, string>();
dictionary.Add(complexType, complexType.SomeDisplayValue);
var treeView = new TreeView();
treeView.BindComplexDataSource(dictionary);
var testNode = treeView.Nodes[0] as ObjectBindableTreeNode<FakeComplexType>;
var dictionary2 = new Dictionary<FakeComplexType, string>();
dictionary2.Add(new FakeComplexType() {SomeDisplayValue = "subvalue", SomeValue = Guid.NewGuid()},
"subvalue");
testNode.BindComplexDataSource(dictionary2);
var realTestNode = testNode.Nodes[0] as ObjectBindableTreeNode<FakeComplexType>;
realTestNode.ShouldNotBeNull();
realTestNode.Text.ShouldEqual("subvalue");
}