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");
}
Technorati Tags:  
, , ,