Treenode XmlTrans draws ports in front of Nodes

Hello GoDiagrammers!
I have a question about the Go.Xml class library.
First, I'm quite enamored of the GoDiagram tool and have convinced my manager to purchase it and develop our decision tree application around it. While the purchase will be happening soon, I am still using the latest eval version on my development machine for prototyping.
I'm building a decision tree app based on the example TreeAppNode class. I am currently working on GoXML classes to consume and generate the tree diagrams.
The GoXMLTransformer subclasses for the TreeAppNode, GoPort, and GoLink all appear to basically work at writing and reading XML files and reproducing the tree diagram, almost, but for two minor but unnacceptable issues.
Problem 1 is that, when reading the file back in, the links and nodes all appear in front of the TreeAppNodes, and problem 2 is that the collapsible handles don't re-appear.
The constructor for the form the GoXml loads onto have the following lines, which I thought would assure that the :
goView1.Document = new TreeApp.TreeXMLDocument();
goView1.Document.LinksLayer = goView1.Document.Layers.CreateNewLayerBefore(goView1.Document.DefaultLayer);
Since I have heard that a picture is worth a thousand words (that would make a good song title, wouldn't it?), here are screenshots of the original diagram, and then the diagram after being loaded from the XML:
Before:
After:
Some additional, minor points:
Note also that the label font size has changed back to default even though, in the constructor of the GoView form, I have the line:
goView1.Font = new Font(FontFamily.GenericSansSerif, 10);
And the links don't terminate in arrows, even though the GoView constructor also contains the line:
goView1.NewGoLink.ToArrow = true;
Related to item 2, the TreeAppNode constructor (basically identical to the class in the TreeApp example project with a few added methods and properties) contains lines to make the collapsible handle (please ignore the kludged PointF math, it is just to put the handle directly beneath the node, and I don't think it is relevant to the issue at hand, which is that the handle doesn't show up at all on the reload):
GoCollapsibleHandle h = new GoCollapsibleHandle();
h.Center = new PointF(this.Location.X - (h.Width * 1.3f), this.Location.Y + this.Height / 1.7f);
Add(h);
UpdateHandle();
Where UpdateHandle() is:
protected void UpdateHandle() {
foreach (GoObject obj in this) {
GoCollapsibleHandle h = obj as GoCollapsibleHandle;
if (h != null) {
h.Visible = HasChildren();
h.Printable = h.Visible;
break;
}
}
}

It's almost like some of the things done in the constructors of the GoView class and the TreeAppNode class are somehow being overridden or replaced after the class has been instantiated by the transformer class. I wonder if it might have something to do with what the base.GenerateAttributes() methods are doing, but I don't actually know what all they are doing since their code is not exposed. And it's possible it could be something entirely different. The
ProcessDocument example I based this on is different in some ways
from what I am trying to do, and I can't seem to find the info I need in Chapter 8 of the GoDiagram User's Guide on XML.
I append a copy of the entire transformer class I wrote. I started from, and did not deviate far from, the ProcessDocument example class to build this TreeXMLDocument class.
If anyone has any thoughts or suggestions as to what might be going on here, or things I might try, or more information I could provide, I would very much appreciate it.
Thank you so much,
Michael Robinson
Senior Programmer Analyst
CDM
using System;
using System.Collections;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using System.Xml;
using Northwoods.Go;
using Northwoods.Go.Xml;
namespace TreeApp {
[Serializable]
public class TreeXMLDocument : GoDocument {
public TreeXMLDocument() { }
public void Store(Stream file)
{
GoXmlWriter writer = new GoXmlWriter();
writer.RootElementName = "decisiontree";
writer.AddTransformer(new TransformGoPort());
writer.AddTransformer(new TransformGoLink());
writer.AddTransformer(new TransformTreeAppNode());
writer.Objects = this;
writer.Generate(file);
}
public TreeAppNode Load(Stream file)
{
TreeAppNode myNode = null;
StartTransaction();
Clear();
GoXmlReader reader = new GoXmlReader();
reader.AddTransformer(new TransformGoPort());
reader.AddTransformer(new TransformGoLink());
reader.AddTransformer(new TransformTreeAppNode());
reader.RootObject = this;
reader.Consume(file);
FinishTransaction("loaded decision tree from XML file");
return myNode;
}
}
public class TransformTreeAppNode : GoXmlTransformer
{
public TransformTreeAppNode()
{
this.TransformerType = typeof(TreeAppNode);
this.ElementName = "treenode";
this.IdAttributeUsedForSharedObjects = true;
this.GeneratesPortsAsChildElements = true;
this.BodyConsumesChildElements = true;
}
public override void GenerateAttributes(Object obj)
{
base.GenerateAttributes(obj);
TreeAppNode n = (TreeAppNode)obj;
WriteAttrVal("label", n.Text);
WriteAttrVal("xy", n.Position);
}
public override Object Allocate()
{
TreeAppNode n = new TreeAppNode();
return n;
}
public override void ConsumeAttributes(Object obj)
{
base.ConsumeAttributes(obj);
TreeAppNode n = (TreeAppNode)obj;
n.Location = PointFAttr("xy", new PointF(100, 100));
n.Text = StringAttr("label", n.Text);
}
public override void ConsumeChild(Object parent, Object child)
{
base.ConsumeChild(parent, child);
TreeAppNode n = (TreeAppNode)parent;
n.Add(child);
}
}
public class TransformGoPort : GoXmlTransformer
{
public TransformGoPort()
{
this.TransformerType = typeof(GoPort);
this.ElementName = "node_link_port";
this.IdAttributeUsedForSharedObjects = true;
}
public override void GenerateAttributes(Object obj)
{
base.GenerateAttributes(obj);
GoPort p = (GoPort)obj;
WriteAttrVal("UserFlags", p.UserFlags);
WriteAttrVal("xy", p.Position);
WriteAttrVal("spot", p.FromSpot);
}
public override void ConsumeAttributes(Object obj)
{
base.ConsumeAttributes(obj);
GoPort p = (GoPort)obj;
p.UserFlags = Int32Attr("UserFlags", p.UserFlags);
//if (IsAttrPresent("maxlinks"))
// p.MaxLinks = Int32Attr("maxlinks", p.MaxLinks);
p.Position = PointFAttr("xy", new PointF(110, 110));
int spot = Int32Attr("spot", -1);
if (spot > -1)
{
p.FromSpot = spot;
p.ToSpot = spot;
}
}
}
public class TransformGoLink : GoXmlTransformer
{
public TransformGoLink()
{
this.TransformerType = typeof(GoLink);
this.ElementName = "tree_path_link";
}
public override void GenerateAttributes(Object obj)
{
base.GenerateAttributes(obj);
GoLink flow = (GoLink)obj;
GoPort p = flow.FromPort as GoPort;
if (p != null)
{
String fromid = this.Writer.FindShared(p);
WriteAttrVal("from", fromid);
}
p = flow.ToPort as GoPort;
if (p != null)
{
String toid = this.Writer.FindShared(p);
WriteAttrVal("to", toid);
}
}
public override void ConsumeAttributes(Object obj)
{
base.ConsumeAttributes(obj);
GoLink flow = (GoLink)obj;
String fromid = StringAttr("from", null);
if (fromid != null)
{
GoPort from = this.Reader.FindShared(fromid) as GoPort;
flow.FromPort = from;
}
String toid = StringAttr("to", null);
if (toid != null)
{
GoPort to = this.Reader.FindShared(toid) as GoPort;
flow.ToPort = to;
}
}
}
}

I haven’t looked at your code in detail, but the screenshot suggests to me that perhaps your nodes (after loading) have two ports instead of just one.
Each TreeAppNode, which inherits from GoBasicNode, automatically has a Port.

But by having separate elements inside your elements, and overriding ConsumeChild to call GoGroup.Add, I believe you are creating new GoPorts (with default properties) and adding them to the TreeAppNodes. Then the links are connecting those extraneous ports.
That would also explain why the handles aren't there. Maybe they are working correctly, but because no TreeAppNode.Port has any links connected to it, the handles are supposed to be invisible.

Walter,
Thank you so much; good call. That was the basic problem.

To fix it I removed the Port GoXmlTransformer class, added line WriteAttrVal("portxy", n.Port.Position); to the TreeAppNode transformer GenerateAttributes, added line n.Port.Position = PointFAttr("portxy", new PointF(100, 100)); and modified the GoLink transformer class Attributes methods to reference the node IDs instead of the port IDs like this:
public override void GenerateAttributes(Object obj)
{
base.GenerateAttributes(obj);
GoLink flow = (GoLink)obj;
GoPort p = flow.FromPort as GoPort;
TreeAppNode n = (TreeAppNode) p.ParentNode;
if (n != null)
{
String fromid = this.Writer.FindShared(n);
WriteAttrVal("from", fromid);
}
p = flow.ToPort as GoPort;
n = (TreeAppNode)p.ParentNode;
if (n != null)
{
String toid = this.Writer.FindShared(n);
WriteAttrVal("to", toid);
}
}
public override void ConsumeAttributes(Object obj)
{
base.ConsumeAttributes(obj);
GoLink flow = (GoLink)obj;
String fromid = StringAttr("from", null);
if (fromid != null)
{
TreeAppNode from = this.Reader.FindShared(fromid) as TreeAppNode;
flow.FromPort = from.Port;
}
String toid = StringAttr("to", null);
if (toid != null)
{
TreeAppNode to = this.Reader.FindShared(toid) as TreeAppNode;
flow.ToPort = to.Port;
}
}
Thanks so much for the quick and accurate response!

Actually, you probably don’t need to save the port position, so you probably shouldn’t want to. The node (by its LayoutChildren method implementation) will make sure its Port has the proper Bounds.

By the way, version 3 will have support for declaratively binding objects and their properties with XML elements and their attributes, by adding a GoXmlBindingTransformer that handles all the common cases as you just have in your custom GoXmlTransformers.