Why Doesn't This Work? - Port Positioning

After looking at some examples in the Demo directory, I am attempting to place “Label Ports” (ports that just are text) at various cardinal positions around a node. Below are the key parts of the code I am using to do this. I am getting the labels displayed, but they are all overlapping, versus being positioned at the cardinal positions I have given.

Before I get responses indicating I need to do offsets (for the text lengths) and left/right/middle text justification, I know I will. I am just trying to get the basics of this working so I can then refine it with the alignments.

Can anyone tell me what I am doing wrong? This is essentially what they do in the PinNode example in the Demo…

public static DiagramNode CreateTextOnlyNode(string text)
{
DiagramNode node = new DiagramNode();
GoText textComp = new GoText();
textComp.Text = text;
node.Label = textComp;
node.Editable = false;
node.Width = 50;
node.Height = 50;

        string text2 = text + "port";
        node.AddLabelAt(DiagramConstants.CardinalDirections.Center, "Center");
        node.AddLabelAt(DiagramConstants.CardinalDirections.North, "North");
        node.AddLabelAt(DiagramConstants.CardinalDirections.East, "East");
        node.AddLabelAt(DiagramConstants.CardinalDirections.South, "South");
        node.AddLabelAt(DiagramConstants.CardinalDirections.West, "West");
        return node;
    }

public class DiagramNode : GoNode
{
public Image IconImage
{
get
{
GoObject obj = this[0];
if (obj is GoImage)
return ((GoImage)obj).Image;
else
return null;
}

        set
        {
            GoObject obj = this[0];
            if (obj is GoImage)
                ((GoImage)obj).Image = value;
            else
            {
                GoImage gimg = new GoImage();
                gimg.Image = value;
                InsertBefore(obj, gimg);
            }
        }
    }

    public override GoText Label
    {
        get
        {
            return label;
        }
        set
        {
            if (label != null)
                Remove(label);
            label = value;
            Add(label);
        }
    }

    public void AddLinkAt(DiagramConstants.CardinalDirections direction, DiagramLink link)
    {
    }

    public void ConnectNodeAt(DiagramConstants.CardinalDirections direction, DiagramNode node)
    {
    }

    public void AddLabelAt(DiagramConstants.CardinalDirections direction, string text)
    {
        GoGeneralNodePort port = new GoGeneralNodePort();
        port.ToSpot = (int) direction;
        port.FromSpot = (int) direction;
        port.Style = GoPortStyle.Object;
        GoText gtext = new GoText();
        gtext.Text = text;
        port.Editable = false;
        port.PortObject = gtext;
        Add(port);
    }

    private GoText label;
}

You haven’t positioned the ports relative to the Label. So they default to being at (0, 0).

Setting the node’s Height and Width will basically set the Label’s Height and Width, because the Label is the only child of the group at that time. Until you understand better about what will happen when you try to resize a group or resize a child of a group, I don’t suggest you do so until you have implemented an override of LayoutChildren.

LayoutChildren is responsible for sizing and positioning all of the immediate children relative to each other. You’ll note that all of the example classes (and all of the predefined classes!) implement overrides of LayoutChildren when any kind of resizing might happen. For example, the PinNode class certainly does.

And you’ll want to make sure that the parts of the node that you don’t want independently selectable have GoObject.Selectable set to false. PinNode does this too.

Can you explain your response a bit more?

You state I have not positioned the ports, isn’t that what the toSpot/FromSpot do? Do I have to explicitly position them with pixel coordinates? What then is the purpose of the GoObject.TopCenter (etc) and the to/fromSpot() calls?

ToSpot / FromSpot are where the links connecting To (or From) the port connect. It has nothing to do with the position of the port itself.

What is the coordinate representation for the positioning of things relative to the node? You said they default to (0,0), but what do I use to position as coordinates?

What would be a good example to learn the ins/outs of LayoutChildren? Is there any documentation on this?

I’ll guess that your image is the “main” object for your node, so you’ll want to position the other things, including the text label and the ports, about it.

Search for “LayoutChildren” in the User Guide, in the API reference, and in the samples.

What makes something the “main” object? Is there an order in the list of objects that determines this?

What makes something the “main” object is your decision that a particular child object is the one about which all of the other objects are sized and/or positioned. There’s nothing inherent in GoDiagram that makes such a policy decision for you.

The default implementation of LayoutChildren is a no-op: there are no restrictions or constraints or other policies regarding the size or position of any children relative to any other child object for that group.

Some predefined examples:

GoListGroup implements a commonly-desired policy for LayoutChildren: positioning all of the children in a line (vertical or horizontal, with optional spaces, and obeying some kind of alignment for handling objects of different size).

GoTextNode.LayoutChildren assumes the Label is the “main” object and sizes the Background to fit around the Label, and positions the ports around the Background.

GoIconicNode.LayoutChildren assumes the Icon is the “main” object and positions the Label about that Icon and the Port at the middle of the Icon.

Ok… I am seeing what you are saying, a bit… But why does the following code not position the ports/labels correctly?

GoGeneralNodePort port = new GoGeneralNodePort();
port.ToSpot = (int) direction;
port.FromSpot = (int) direction;
float widthOff = this.Width / 2;
float heightOff = this.Height / 2;
//float diagOff = (float) Math.Sqrt((widthOff * widthOff) + (heightOff * heightOff));
switch (direction)
{
case DiagramConstants.CardinalDirections.Center:
port.SetSpotLocation((int)direction, this, (int)direction);
break;
case DiagramConstants.CardinalDirections.East:
port.SetSpotLocation((int)direction, this, (int)direction, new SizeF(widthOff, 0F));
break;
case DiagramConstants.CardinalDirections.North:
port.SetSpotLocation((int)direction, this, (int)direction, new SizeF(0F, heightOff));
break;
case DiagramConstants.CardinalDirections.NorthEast:
port.SetSpotLocation((int)direction, this, (int)direction, new SizeF(widthOff, heightOff));
break;
case DiagramConstants.CardinalDirections.NorthWest:
port.SetSpotLocation((int)direction, this, (int)direction, new SizeF(-widthOff, heightOff));
break;
case DiagramConstants.CardinalDirections.South:
port.SetSpotLocation((int)direction, this, (int)direction, new SizeF(0F, -heightOff));
break;
case DiagramConstants.CardinalDirections.SouthEast:
port.SetSpotLocation((int)direction, this, (int)direction, new SizeF(widthOff, -heightOff));
break;
case DiagramConstants.CardinalDirections.SouthWest:
port.SetSpotLocation((int)direction, this, (int)direction, new SizeF(-widthOff, -heightOff));
break;
case DiagramConstants.CardinalDirections.West:
port.SetSpotLocation((int)direction, this, (int)direction, new SizeF(-widthOff, 0F));
break;
}
port.Style = GoPortStyle.Object;
GoText gtext = new GoText();
gtext.Text = text;
port.Editable = false;
port.PortObject = gtext;
Add(port);

What’s “this” in your calls to SetSpotLocation? If it’s the node, what are the node’s Bounds each time you execute this code?

Is there a reason you aren’t putting the construction and initialization of the parts of the node in one method (perhaps in the node’s constructor, or perhaps in some other initialization method) and the sizing/positioning of the parts of the node in an override of LayoutChildren, as I keep suggesting?

Sorry I think we have a misunderstanding…

First, let me say, this is not a full implementation, what you see right now, is just code to try to figure out how to get things to layout as I am attempting. Once I do, I will add overrides to LayoutChildren, etc to make it work correctly.

Second, “this” is the node.

Third, I have to allow the user to add label dynamically at various positions on the node. So the code I have provided you is the AddLabelAt(direction, text) method on the node that supplies the user with the ability.

Fourth, the “View” will be read only. We are using the Go framework to layout fixed representations of networks, as a pictorial while they are going through a wizard. The only thing that will change is certain labels will be added/removed as the user goes through the wizard (such as IP addresses).

I hope this clears things up.

And I meant to add the nodes bounds are 200x200 as is set up in the following code:

        <i>DiagramNode node = new DiagramNode();
        //GoText textComp = new GoText();
        //textComp.Text = text;
        //node.Label = textComp;
        node.Editable = false;
        node.Width = 200;
        node.Height = 200;</i>

The Bounds of a GoGroup are defined to be the union of the Bounds of its children. Setting node.Width = 200 will cause all of the node’s children to be scaled proportionally, for each child whose AutoRescales property is true. Depending on the child objects at the time, this may or may not result in a node whose Width == 200 afterwards.

So the misunderstanding is the assumption that each node (actually each GoGroup) is a fixed size box in which to place things.

Please try putting all the sizing and positioning code in an override of LayoutChildren.

LayoutChildren will get called whenever any child is added or removed or moved or resized.

If you want to simplify the implementation of LayoutChildren not to have to worry about a partially constructed node, you can temporarily set Initializing = true in the constructor/initializer method, and then after setting Initializing = false, call LayoutChildren(null). Your LayoutChildren method should then be a no-op when Initializing is true.

Actually, since you say you looked at the PinNode example class in Demo1, you can see it does the same thing with the GoObject.Initializing property. It doesn’t explicitly call LayoutChildren(null) in the constructor because it does explicitly set the node.Size after all of the children have been added, and changing the Size of a group will also call LayoutChildren, as I discussed before.

Ok, so I am confused…

How when I AddLabelAt(direction, text) will I be able to pass along the “at” (direction) part to LayoutChildren?

As you can see from my code example, all I am doing in the AddLabelAt() method is creating a port, setting the to/fromSpot and the SetSpotLocation. The later (SetSpotLocation) has to be based on the direction context, which would be lost later at the LayoutChildren call.

Furthermore, how do I calculate the offset to SetSpotLocation (Width/Height) if the “node’s” width and height are variable based on the children, and what I am setting is one of the children? Is there some magic ju ju I am missing here.

Well, you just need to associate whatever information you need with each port. You can use some kind of dictionary, such as a Hashtable. Or you could subclass the port and add whatever properties you need. But the easiest would be to keep the direction in the GoPort.UserFlags property, if you aren’t already using it for something else. The GoPort.UserObject property is another predefined storage mechanism too.

You could position each port relative to the IconImage.

Ok will do…

Related question…

When I call:

p.SetSpotLocation((int)direction, this, (int)direction, new SizeF(widthOff, 0F))

this being the node, p being a port I am adding.

What coordinates are being adjusted, and what are they being adjusted to?

So specifically is the SizeF an offset from the top/left corner of the node, and does it set the top/left corner of the port?