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 }