Help with a design question

Hi,

I have a new customer request to make a node that “looks like a Visio object”. While vague, I think what they are asking for is an object with a basic rectangular shape with multiple ports around the outside of the rectangle: one port at each corner and one port midway along each side, for a total of eight ports. The interior of the object, the body, could be anything, images, text or a combination of both.

In thinking about how to implement this I keep coming back to the GoBoxNode. It has the rectangular shape that I need, it implements the “body” concept, and it knows how to resize itself based on the size of the body. The fact that the rectangle (box) sits behind and is slightly larger than the body works well too. Unfortunately, the GoBoxNode only has the one port (the box!). I need eight ports.

So, what I need is a GoBoxNode-like object with eight ports. To implement this object I was thinking of using two GoNodes.

The first GoNode, the parent, would contain the 8 ports and the GoRectangle background. The eight ports would be spotted around the rectangle. The second GoNode, the body, would contain the remaining content of the object (images, text, etc…).

For my implementation, the one thing I don’t know how to do is to manage/maintain the size of the rectangle around the body, the way BoxNode does it. The body can be modified at any time based on user actions (note, resize for the sake of resize would not be allowed, only adding/removing content). Each change to the body would require a resizing of the rectangle, but I’m not sure what methods to override to do this. Also, should all of the functionality of both GoNodes be wrapped into a single class or would the parent GoNode need to be it’s own class and the body GoNode need to be it’s own class? The reason I ask this question is because I’m not sure which methods I need to override in order to resize the rectangle border around the body.

Could I implement all of this functionality using a single GoNode…, or some other set of GoObjects? Have you already implemented this functionality somewhere?

Your thoughts or comments would be greatly appreciated.

Thank you very much.

R. Houston

[code] [Serializable]
public class VisioNode : GoNode {
public VisioNode() {
GoRectangle r = new GoRectangle();
r.Selectable = false;
//??? more initialization
Add®;
GoImage img = new GoImage();
img.Selectable = false;
//??? more initialization
Add(img);
GoText lab = new GoText();
lab.Selectable = false;
//??? more initialization
Add(lab);
AddPort(TopLeft);
AddPort(MiddleTop);
AddPort(TopRight);
AddPort(MiddleRight);
AddPort(BottomRight);
AddPort(MiddleBottom);
AddPort(BottomLeft);
AddPort(MiddleLeft);
}

public GoShape Shape {
  get { return this[0] as GoShape; }
}

public GoImage Image {
  get { return this[1] as GoImage; }
}

private void AddPort(int spot) {
  GoPort p = new GoPort();
  p.FromSpot = spot;
  p.ToSpot = spot;
  p.AutoRescales = false;
  p.Size = new SizeF(6, 6);
  p.Style = GoPortStyle.Times;
  p.Pen = Pens.Blue;
  p.Brush = null;
  Add(p);
}

public override void LayoutChildren(GoObject childchanged) {
  if (this.Count < 3) return;
  GoShape shape = this.Shape;
  GoImage img = this.Image;
  GoText lab = this.Label;
  if (shape != null) {
    foreach (GoObject obj in this) {
      GoPort p = obj as GoPort;
      if (p != null) {
        p.SetSpotLocation(p.ToSpot, shape, p.ToSpot);
      }
    }
    if (img != null) img.Center = shape.Center;  //??? might want different Size and/or Position
    if (lab != null) lab.Center = shape.Center;  //??? might want different Position
  }
}

}[/code]

Thanks Walter,

The GoBoxNode provides for a visible margin (portbordermargin) between the body and the port surrounding the body. How would I implement this concept using a single GoNode where the GoRectangle child is the “port” and all of the other contents of the GoNode (images, gotexts, gogroups, golistgroups, etc…) the “Body”. I’d like to set an 8 pixel margin of whitespace around each side of the the “Body”. Does my question make sense?

Thanks a lot.

R. Houston

[code] [Serializable]
public class VisioNode : GoNode {
public VisioNode() {
this.Resizable = false;
// default shape is a GoRectangle:
GoRectangle r = new GoRectangle();
r.Selectable = false;
r.Brush = Brushes.White;
Add®;
// default body is just a GoText:
GoText body = new GoText();
body.Selectable = false;
body.Text = “VisioNode”;
Add(body);
AddPort(TopLeft);
AddPort(MiddleTop);
AddPort(TopRight);
AddPort(MiddleRight);
AddPort(BottomRight);
AddPort(MiddleBottom);
AddPort(BottomLeft);
AddPort(MiddleLeft);
}

private void AddPort(int spot) {
  GoPort p = new GoPort();
  p.FromSpot = spot;
  p.ToSpot = spot;
  p.AutoRescales = false;
  p.Size = new SizeF(6, 6);
  p.Style = GoPortStyle.Times;
  p.Pen = Pens.Blue;
  p.Brush = null;
  Add(p);
}

public GoShape Shape {
  get { return this[0] as GoShape; }
}

public GoObject Body {
  get { return this[1]; }
  set {
    if (value != null) {
      this[1] = value;
      value.Selectable = false;
    }
  }
}

public override void LayoutChildren(GoObject childchanged) {
  if (this.Count < 2) return;
  GoShape shape = this.Shape;
  GoObject body = this.Body;
  // make sure Shape surrounds the Body by some margins
  RectangleF bounds = body.Bounds;
  bounds.Inflate(10, 10);
  shape.Bounds = bounds;
  foreach (GoObject obj in this) {
    GoPort p = obj as GoPort;
    if (p != null) {
      p.SetSpotLocation(p.ToSpot, shape, p.ToSpot);
    }
  }
}

}[/code]

Example use:

VisioNode vn = new VisioNode(); GoListGroup vng = new GoListGroup(); GoEllipse vnge = new GoEllipse(); vnge.Selectable = false; vnge.Size = new SizeF(100, 50); vnge.Brush = Brushes.Blue; vng.Add(vnge); GoText vngt = new GoText(); vngt.Selectable = false; vngt.Editable = true; //??? vngt.Text = "some text"; vng.Add(vngt); GoDiamond vngd = new GoDiamond(); vngd.Selectable = false; vngd.Size = new SizeF(40, 40); vngd.Pen = null; vngd.Brush = Brushes.Green; vng.Add(vngd); vn.Body = vng; doc.Add(vn);

Hi Walter,

I implemented two GoNodes per your example: The first, or parent, GoNode holds the rectangle border and the ports. The second, or child, GoNode holds the remainder of the object’s contents. The second GoNode is added as a child of the first GoNode. Bottom line - It works.

My problem now is one that I don’t understand, involving copy and paste of the node. When I display the node, select it and try to copy it (using C) I see that LayoutChildren is called as part of the copy operation. The problem is that the only child is the rectangle (border). Very strange. Using Visual Studio’s debugger, I see that the node’s other children (i.e., the ports and the “body” GoNode), the children that should be part of the node, are not there. So, layoutchildren crashes with a null reference when I attempt to reference one of the non-existent children.

I did have copy/paste working before this change and I don’t recognize what I"ve done to make it stop working.

Do you have any thoughts on this issue?

Thank you.

R. Houston

OK,

I wrote code to get around this problem but what I don’t understand is this:

When the copy ( C) is performed a new instance of the node is created using the MemberwiseClone method. Shouldn’t the output of that method be a carbon copy of the original node, with all of its children intact? If not, why is layoutchildren being called before the node has been reconstructed in it’s entirety? Is the layout part of the cloning process? I’m just trying to understand what’s going on behind the scenes.

Thanks.

R. Houston

My second post implemented a node that was designed to be like GoBoxNode in having a Body property that could be any GoObject. The Shape is automatically resized to fit around that Body.

The example use of that node created a GoGroup to hold three GoObjects. (Actually it’s a GoListGroup, so that it automatically lays out those three child objects in a column.) You could do the same – you don’t need to use a GoNode as the group class.

You must have added a field to hold a reference to the child “node”. You can do that if you want, but you need to implement an override of CopyChildren to update that reference.

But you’ll notice that the VisioNode that I defined doesn’t bother adding any fields. That’s a time/space trade-off, as well as a run-time vs coding-time trade-off.

I suggest you start with the VisioNode code I posted.

Hi Walter,

I implemented my nodes in a fairly similar manner to what you posted in your second post. I don’t maintain a reference to the body, or child, node. The body node is defined as a local variable in my initialization code and once added to the parent GoNode goes out of scope, as per your example. And, it works. Thank you for your help with this.

My previous post had to do with my lack of understanding of the details of copying Go objects. It seems as though the layout of children is part of the cloning (copying) process. My previous post lays out my concern/question in more detail.

Just to recap, my code is working now but I still have the copy question as outlined in my previous post.

Thanks again.

R. Houston

When a GoObject is Add’ed or Remove’d from a GoGroup, LayoutChildren is called.

GoGroup.CopyChildren of course Add’s each copied child object. If you are worried about copying efficiency, you can make your implementation of LayoutChildren be a no-op when Initializing is true. The nodes with lots of children tend to do this.

Hi again,

It’s been a few days since I implemented the new node design, as discussed above. Things are looking good, EXCEPT for this one curious problem.

When content (an image, text, etc…) is added to the node, the node and border rectangle correctly resize to accommodate the new content. Furthermore, when I select the node by clicking on it the green selection box around the node has the same bounds as the node’s border rectangle. So far, so good.

However, when I remove content from the node, strange things start to happen to the green selection box. While the border rectangle has the correct bounds (around the node) the green selection box is all wrong. When I click on a node after removing content from it the green selection box is usually larger in size and offset in position from the node’s border rectangle. Visually, it appears as though the selection object’s bounds and position are somehow diverging from the node’s bounds and position. How does this happen and what can I do about it? At all times I want the green selection box to match the bounds and position of the node’s border rectangle.

Thoughts?

Thanks.

R. Houston

Are any of your node’s children or grandchildren Selectable?

What is your node.Bounds after the node has gotten smaller? Does it match what you see of the node? Or does it match what the selection handle shows?

Hi Walter,

Let me answer your questions:

Q: Are any of your node’s children or grandchildren Selectable
A: Yes. Content in the body node can be selected and then deleted or copied. The problem however has to do with deleting content

Q: What is your node.bounds after the node has gotten smaller?
A: I overrode ComputeBounds in my node class to obtain the following information. Please keep in mind that the Body Node is a child of Node and that the SelectionObject is Node. Bounds shown below were obtained after calling base.ComputeBounds.

Before the delete:
1. Selection Object’s Bounds: X = 340.0 Y = 166.061768 Width = 109.456055 Height = 215.078613}
2. Node’s bounds: {X = 325.246338 Y = 155.690918 Width = 138.963379 Height = 235.820313}
3. Body Node’s bounds: {X = 329.246338 Y = 159.690918 Width = 130.963379 Height = 227.820313}

 <b>After </b>the delete:    
  1.  Selection Node's bounds: {X = 325.246338 Y = 155.601074 Width = 139.0 Height = 236.089844}
  2.  Node's Bounds:  {X = 340.0183 Y = 155.646 Width = 139.0 Height = 246.578613}
  3.  Body Node's Bounds: {X = 344.0183 Y = 170.224609 Width = 101.456055 Height = 206.842773}

As you can see the numbers don’t make sense. I would have thought the SelectionObject’s bounds would have been the same as Node’s bounds given that the SelectionObject is Node. Unfortunately, their bounds do not match.

Any ideas as to how the bounds diverged?

Thanks a lot.

R. Houston

Hi Walter,

OK I’ve fixed the problem but I’m not sure why. My fix was to size each of the ports according to the dimensions of the border rectangle (around the body node) rather than using the size of the node itself. Using the node’s size to set the port size really messed up the selection box dimensions, as the previous post’s numbers show. I know port size can be anything, including the node’s size, so there must be something more to this that I’m overlooking. Anyway, I’m up and running again.

Thanks.

R. Houston

You aren’t overriding GoObject.ComputeBounds for any reason other than to get this information, right? You’ll note that my example classes do not override that method. It’s unusual to do so except perhaps for dealing with non-Visible children. And you didn’t need to do so just to get this information for debugging, since you can just look at the value of the Bounds property in the debugger.

Did you override GoObject.SelectionObject? Normally for the node I would expect this to be the node itself, which is the default value for the property. That will cause the bounding selection handle to be the same as the node’s bounds (well, just a bit bigger, so it surrounds the node).

Hi Walter,

Yes, the only reason I overrode ComputeBounds was to obtain the bounds information that I provided in a previous post. My overriden ComputeBounds does nothing besides call the base class computebounds and provide a breakpoint for me to inspect the bounds property of the three classes. I removed the overridden method once I had fixed the problem.

Correct, I have not overridden SelectionObject.

Something in my code hosed-up the selectionobject bounds calculation performed by GoDiagrams. At least I know how to fix it, even if I don’t understand why.

Thanks again.

R. Houston