NShape - create custom shape which is a polyline + text

Nov 7, 2015 at 6:01 PM
Hi there,

Firstly thank you for such an amazing product! This has saved me months of work.

Question:
Is it possible to extend the polyline, so it always has some text to the right of the line? (or is there already a control for this)

I've read over the 'create your own shape' docs, but am struggling to see how to connect a line and text shape.

I partly understand the source code, so if someone could point in the right direction of where to edit that would be great.
Coordinator
Nov 9, 2015 at 8:00 AM
Edited Nov 9, 2015 at 8:05 AM
You can attach a "Label" shape to the line (or any other shape):
The Label has an external 'glue point' (active connection point that sticks to passive connection points) which can be connected either to a whole shape or a specific connection point. In each case, the label will follow its partner shape and maintain its relative position to the partner shape.
Nov 9, 2015 at 9:35 AM
Hi Kurt, I tried this:

var lineText = (LabelBase)Project.ShapeTypes["Label"].CreateInstance();
InsertShape(lineText);
lineText.SetCaptionText(0, "s");
lineText.Connect(ControlPointId.FirstVertex, lineShape, ControlPointId.Reference);
lineText.X = lineShape.X + 20;
lineText.Y = lineShape.Y + 10;

But am just getting errors. Not sure I understand these glue points.
Coordinator
Nov 9, 2015 at 1:07 PM
The code above would work for lines as these are defined by a set of vertices and therefore have a first and a last vertex.
Label shapes are planar shapes which have no vertices.
You can get points with various capabilities by calling
ControlPointId gluePointId = ControlPointIds.None;
foreach (ptId in lineText.GetControlPointIds(ControlPointCapabilities.Glue)) {
    gluePointId = ptId;
    break;
//
// ToDo: Move the glue point of the label to a position on the line where it 
// should stick to, typically near the begin/middle/end
lineText.Connect(gluePointId, lineShape, ControlPointId.Reference); 
Moving the label after it was connected will update it's relative position to its partner shape but not the point where the glue point sticks to.
Nov 9, 2015 at 2:10 PM
Edited Nov 9, 2015 at 2:19 PM
Hi Kurt, I tried out your code but got this error when calling lineText.Connect:

Image
(GetLineSegments() looped once, and LineContainsPoint() returned false)

Full code:
// add new shape
var newShape = (RectangleBase)Project.ShapeTypes["Text"].CreateInstance();
newShape.X = currentShape.X;
newShape.Y = currentShape.GetBoundingRectangle(false).Bottom + 70;
newShape.Width = 100;
newShape.Height = 60;
InsertShape(newShape);

// add connector
var lineShape = Project.ShapeTypes["Polyline"].CreateInstance();
InsertShape(lineShape);

// connect new shape to prev shape
lineShape.Connect(ControlPointId.FirstVertex, currentShape, ControlPointId.Reference);
lineShape.Connect(ControlPointId.LastVertex, newShape, ControlPointId.Reference);
                
// create text to place next to connector line
var lineText = (LabelBase)Project.ShapeTypes["Label"].CreateInstance();
InsertShape(lineText);
lineText.SetCaptionText(0, "s");
lineText.X = lineShape.X + 20;
lineText.Y = lineShape.Y + 10;

// get text glue point
ControlPointId gluePointId = ControlPointId.None;
foreach (var ptId in lineText.GetControlPointIds(ControlPointCapabilities.Glue))
{
    gluePointId = ptId;
    break;
}
                
// glue text to connector
lineText.Connect(gluePointId, lineShape, ControlPointId.Reference); 
(I am trying to figure this stuff out, but am admittedly quite lost at the moment)
Coordinator
Nov 9, 2015 at 3:12 PM
You have to move the glue point 'over' the line shape at the point where it should connect. Just the same as if you would move the label's glue point with the mouse over the line in NShapeDesigner. Did you try to connect a label shape with a line in NShapeDesigner? Connecting via code is quite the same.

The exception says that the point Point(lineShape.X + 20, lineShape.Y + 10) is not 'on' the line.
I would recommend to calculate the coordinates and move the shapes there before calling Connect(), so you can check visually what's going on.
Example:
// ... (create shapes and get glue point id) ...

// Applies to a 1-segment-line only!
Point linePosA = lineShape.GetControlPointPosition(ControlPointIds.FirstVertex)
Point linePosB = lineShape.GetControlPointPosition(ControlPointIds.LaststVertex)
Point gluePointPos = Point(
    (int)Math.Round((linePosA.X + linePosB.X) * 0.3),
    (int)Math.Round((linePosA.Y + linePosB.Y) * 0.3)
)
// Define the point where the label should stick to
lineText.MoveControlPointTo(gluePointId, gluePointPos.X, gluePointPos.Y, ResizeModifiers.None)
// Define the relative position of the label to the line
lineText.MoveTo(lineShape.X + 20, lineShape.Y + 20)

// ... (connect shape shapes) ...
Marked as answer by Alexhbg on 11/12/2015 at 2:11 PM
Nov 12, 2015 at 9:27 PM
Edited Nov 12, 2015 at 9:29 PM
Cheers Kurt, fixed the code today and got it working. But did have to change the provided code slightly (for anyone else that comes across the same problem):
Point linePosA = lineShape.GetControlPointPosition(ControlPointId.FirstVertex);
Point linePosB = lineShape.GetControlPointPosition(ControlPointId.LastVertex);
Point gluePointPos = new Point(
    (int)(linePosA.X),
    (int)(linePosA.Y + ((linePosB.Y - linePosA.Y) * 0.5))
);

// Define the point where the label should stick to
lineText.MoveControlPointTo(gluePointId, gluePointPos.X, gluePointPos.Y, ResizeModifiers.None);

// glue text to connector
lineText.Connect(gluePointId, lineShape, ControlPointId.Reference);

// Define the relative position of the label to the line
lineText.MoveTo(lineShape.X + 20, lineShape.Y + 20);
Marked as answer by Alexhbg on 11/12/2015 at 2:11 PM
Feb 15, 2016 at 7:20 PM
Hey, i just wanna ask where i should put the code. I wrote this in Designer:
private void display_ShapesInserted(object sender, DiagramPresenterShapesEventArgs e) {

            // If Polyline was added to diagram then stick Label to center of Polyline
            foreach (var shape in e.Shapes)
            {
                if (shape.Template != null && shape.Template.Name == "Polyline")
                {
                    var lineText = project.ShapeTypes["Label"].CreateInstance();
                    currentDisplay.Diagram.Shapes.Add(lineText);
                    cachedRepository.Insert(lineText, currentDisplay.Diagram);

                    ControlPointId gluePointId = ControlPointId.None;
                    foreach (var ptId in lineText.GetControlPointIds(ControlPointCapabilities.Glue))
                    {
                        gluePointId = ptId;
                        break;
                    }

                    Point linePosStart = shape.GetControlPointPosition(ControlPointId.FirstVertex);
                    Point linePosEnd = shape.GetControlPointPosition(ControlPointId.LastVertex);

                    int linePosCenterX = (linePosStart.X + linePosEnd.X) / 2;
                    int linePosCenterY = (linePosStart.Y + linePosEnd.Y) / 2;

                    Point gluePointPos = new Point(
                        linePosCenterX,
                        linePosCenterY
                        );

                    // Define the point where the label should stick to
                    lineText.MoveControlPointTo(gluePointId, gluePointPos.X, gluePointPos.Y, ResizeModifiers.None);

                    // Glue text to connector
                    lineText.Connect(gluePointId, shape, ControlPointId.Reference);

                    // Define the relative position of the label to the line
                    lineText.MoveTo(linePosCenterX + 20, linePosCenterY + 20);
                }
            }

            UpdateStatusInfo();
        }
Everything seems to work but if I play with undo Label cant be discarded. If I delete label manually in some point I can see Reference control point of that Label even if Label was deleted.

Thanks for any ideas.
Feb 16, 2016 at 10:16 PM
Okey I found solution for some issues undo and redo working after using Commands:
        private void display_ShapesInserted(object sender, DiagramPresenterShapesEventArgs e) {

            // If Polyline was added to diagram then stic Label to center of Polyline
            foreach (var shape in e.Shapes)
            {
                if (shape.Template != null && shape.Template.Name == "Polyline")
                {
                    var lineText = project.ShapeTypes["Label"].CreateInstance();

                    project.ExecuteCommand(new CreateShapesCommand(cachedRepository, currentDisplay.Diagram, LayerIds.All, lineText, false, true));

                    ControlPointId gluePointId = ControlPointId.None;
                    foreach (var ptId in lineText.GetControlPointIds(ControlPointCapabilities.Glue))
                    {
                        gluePointId = ptId;
                        break;
                    }
                    
                    Point linePosStart = shape.GetControlPointPosition(ControlPointId.FirstVertex);
                    Point linePosEnd = shape.GetControlPointPosition(ControlPointId.LastVertex);

                    int linePosCenterX = (linePosStart.X + linePosEnd.X) / 2;
                    int linePosCenterY = (linePosStart.Y + linePosEnd.Y) / 2;

                    Point newGluePointPos = new Point(
                        linePosCenterX,
                        linePosCenterY
                        );

                    Point currentGluePointPos = lineText.GetControlPointPosition(gluePointId);

                    int dX = newGluePointPos.X - currentGluePointPos.X;
                    int dY = newGluePointPos.Y - currentGluePointPos.Y;

                    project.ExecuteCommand(new MoveGluePointCommand(cachedRepository, lineText, gluePointId, shape, dX, dY, ResizeModifiers.None));

                    // Define the relative position of the label to the line
                    // lineText.MoveTo(linePosCenterX + 20, linePosCenterY + 20);
                }
            }

            UpdateStatusInfo();
        }
But If I connect Label to line, which is connected to another Shape, and than i delete Label and after that I wand to connect another line somewhere I can see ReferencePoint of deleted Label. Also If I connect Label than delete and after it click on undo application crashed with: Label point 10 has to be a connection point.
Coordinator
Feb 25, 2016 at 8:59 AM
Ok, first of all:

The Undo/Redo system is based on commands. If you perform an action that should be undoable/redoable, you have to create a command (Dataweb.NShape.Commands.Command) for it and add it to the history.
If you use methods of the Presenters (e.g. Display, which is an IDiagramPresenter) or Controllers (e.g. DiagramSetController), you do not have to care about that as these methods handle inserting shapes into the repository and creating/executing commands.

The specific problem here is, that the label is not created (first problem) and connected (second problem) using a command, so when you redo the commands, the state of the shapes is inconsistent.
Use a ConnectCommand for connecting the shapes:
        private void display_ShapesInserted(object sender, DiagramPresenterShapesEventArgs e) {
            Display currentDisplay = display1;

            // Copy shapes in EventArgs buffer to a new list as the buffer will be re-used next time a shape is inserted
            List<Shape> shapes = new List<Shape>(e.Shapes);

            // If Polyline was added to diagram then stick Label to center of Polyline
            foreach (var shape in shapes) {
                if (shape.Template != null && shape.Template.Name == "Polyline") {
                    // Create Label shape from Template (in order to apply the styles defined by the design)
                    var lineText = project1.Repository.GetTemplate("Label").CreateShape();
                    // Use the display's InsertShape method that handles command creation and repository insertion.
                    display1.InsertShape(lineText);

                    ControlPointId gluePointId = ControlPointId.None;
                    foreach (var ptId in lineText.GetControlPointIds(ControlPointCapabilities.Glue)) {
                        gluePointId = ptId;
                        break;
                    }

                    Point linePosStart = shape.GetControlPointPosition(ControlPointId.FirstVertex);
                    Point linePosEnd = shape.GetControlPointPosition(ControlPointId.LastVertex);

                    int linePosCenterX = (linePosStart.X + linePosEnd.X) / 2;
                    int linePosCenterY = (linePosStart.Y + linePosEnd.Y) / 2;

                    Point gluePointPos = new Point(
                        linePosCenterX,
                        linePosCenterY
                        );

                    // Define the point where the label should stick to
                    lineText.MoveControlPointTo(gluePointId, gluePointPos.X, gluePointPos.Y, ResizeModifiers.None);

                    // Create and execute a ConnectCommand that handles moving control points, connecting shapes and redoing all that stuff
                    ICommand cmd = new ConnectCommand(lineText, gluePointId, shape, ControlPointId.Reference);
                    project1.ExecuteCommand(cmd);
                }
            }
        }