[Journal - Simple Object Graphs]

Simple Object Graphs

Wednesday, March 30, 2005

Walking object graphs with no special information about the object relationships is an interesting concept. It's used in Serialization, but also in property browsers, like the Windows Forms property grid. It isn't that hard to accomplish with, you guessed it, Reflection:

// <build:addin?C#,0>
// <macro:TestObjectGraphs.Foo>

using System;
using System.Windows.Forms;
using Gregor.Core;
using Gregor.Core.Collections;
using Gregor.WebEdit;

[Module()]
public class TestObjectGraphs {

    public static void Foo(){

        CObjectGraph graph = CObjectGraph.CreateForTypeRoot(typeof(WebEditApp));
        graph.AllowDuplicates = true;
        graph.Options = GraphOptions.Default | GraphOptions.FieldMembers;

        TreeView tvw = new TreeView();
        tvw.ImageList = WebEditApp.ImageList.Inner;
        tvw.Dock = DockStyle.Fill;
        tvw.BeforeExpand += new TreeViewCancelEventHandler(HandleTreeViewBeforeExpand);

        AddNode(tvw.Nodes, graph.RootNode);

        Form frm = new Form();
        frm.Controls.Add(tvw);
        frm.ShowDialog();
    }

    private static void HandleTreeViewBeforeExpand(object sender, TreeViewCancelEventArgs e){
        e.Node.Nodes.Clear();
        CGraphNode node = (CGraphNode) e.Node.Tag;
        node.Reset(); // forces refresh
        foreach(CGraphNode kidNode in node.ChildNodes){
            AddNode(e.Node.Nodes, kidNode);
        }
    }

    private static void AddNode(TreeNodeCollection nodes, CGraphNode node){
        string sText = node.DisplayName + " : " + node.TypeName + " {" + node.Value + "}";
        TreeNode tn = new TreeNode(sText);
        tn.Tag = node;
        if(node.ChildNodes.Count > 0){
            tn.Nodes.Add("Dummy");
        }
        int iIcon = WebEditApp.ImageList.GetIconIndex("Property.ico");
        tn.SelectedImageIndex = iIcon;
        tn.ImageIndex = iIcon;
        nodes.Add(tn);
    }

} // class TestObjectGraphs

The CObjectGraph and CGraphNode classes of Gregor.Core.Collections are suitable for building a tree of objects. That tree can be traversed with the standard CTreeWalker and CDefaultTreeWalkHelper classes.

The actual work of enumerating child objects (fields, properties, or items in a collection) is done in the GetChildObjects subroutine in the Gregor.Core.Reflect module, which can be used outside of trees as well.

Whether you build a tree or not, there are some options to choose from:

[Flags()]
public enum GraphOptions{
	None             = 0x00,
	PreferCollection = 0x01, // no fields and properties on collections
	FieldMembers     = 0x02, // can both be used together, _
	PropertyMembers  = 0x04, // if you really want to
	AllowNulls       = 0x08, // support pending
	AllowValueTypes  = 0x10, // values are leafs always
	Default          = PreferCollection | PropertyMembers
}