Cannot connect shapes which have models assigned

Oct 18, 2012 at 10:51 AM
Edited Oct 18, 2012 at 11:05 AM

Hi,

In my application I noticed that if I assign model to shape, this shape looses connection points:

public Form1()
{
    InitializeComponent();

    this.project1.AutoLoadLibraries = false;
    this.LinkComponents();
    this.CreateProject();
    
    // <Playing with models>
    Template template = this.project1.Repository.GetTemplate("Processor");
    ProcessorModel modelObj = (ProcessorModel)this.project1.ModelObjectTypes["ProcessorModel"].CreateInstance();
    // Assign model object to template shape
    template.Shape.ModelObject = modelObj;
    // Add model object and update template
    this.project1.Repository.Insert(modelObj);
    this.project1.Repository.Update(template);
    // </Playing with models>

    this.CreateDiagram();
}

In your "Model Mapping Demo" project there is the same problem.

How can I fix this?

[UPDATE] The following code (placed into the Shape's code) helps, but I'm not sure that this is a valid solution:

protected override bool IsConnectionPointEnabled(ControlPointId pointId)
{
    return true;
}

Coordinator
Oct 18, 2012 at 1:50 PM

The reason for this behavior was described here: There are no mappings between the shape's connection points and the model object's terminals.

Simply add a terminal mapping to the template you are using for shape creation:

// MyModelObject.TerminalA and MyModelObject.TerminalB are
// simple public integer constants in this case
myTemplate.MapTerminal(MyModelObject.TerminalA, ControlPointId.FirstVertex);
wireTemplate.MapTerminal(MyModelObject.TerminalB, ControlPointId.LastVertex);

You don't have to implement any special methods for terminals - from the framework's point of view it's enough to have an Id for the terminal and a mapping to a connection point.

Oct 19, 2012 at 5:11 AM

Thank you for your reply.

1. My shape is not a linear one, so FirstVertex/LastVertex cannot ne used, I guess. Different types of my shapes have different connection points - one has 4 and 5, the other - 1, 5 and 6. How should I refer to these points using your code? I tried searching help for "MapTerminal" string but this gave me nothing. The ControlPointId structure has no members for all possible points, of course. I guess the only possible solution is just to use integers when calling MapTerminal, right? especially as the ControlPointId structure has int operator override...

2. Is my approach acceptable? (I mean simple override of the IsConnectionPointEnabled method) Or I should really create terminal mappings?

Coordinator
Oct 19, 2012 at 8:16 AM

My example was a bit misleading I think. Using Integers as ConnectionPointId is not only ok, it's the only way because you cannot use structs for the defiition of constant fields.

I'm not sure if your solution works in all situations. For example when connecting two shapes, the model objects are connected, too. Template.GetMappedTerminalId() is used for retrieving the terminls corresponding to the connection points.

Oct 19, 2012 at 10:33 AM
KurtHolzinger wrote:

when connecting two shapes, the model objects are connected, too.

Does this happen automatically?

Do we need to implement something "special" in such model objects?

How should we retrieve the list of connected models for the given model?

Coordinator
Oct 19, 2012 at 12:26 PM

Yes, this happens automatically in ShapeBase.

How or even if you implement model object connections is up to you - the methods (defined in the interface) should not throw NotImplementedExceptions but the framework does not care about further details.
If you don't care about model object connections, leave the IModelObject.Connect() method empty.

Oct 19, 2012 at 12:35 PM

A-a-ah... that's what the IModelObject.Connect() method is designed for! here we should implement our custom logic for connecting shapes... thank you!

At first, I thought that "model connectivity" already has some built-in (default) implementation, but couldn't find who is responsible for this implementation :) now it's clear.

Thank you again!


Oct 22, 2012 at 11:42 AM
Edited Oct 23, 2012 at 7:42 AM

Strange: I placed a breakpoint in the Connect() method of my Model, but it's not hit when I connect two corresponding Shapes (each using this Model).

Why?!

[UPDATE] Seems that I misunderstood the concept of connections :) I thought that when I'm connecting two Planar Shapes with a Linear Shape (e.g. two Boxes with a Polyline), the two shapes which are called the "connected shapes" are the Planar ones. But having stepped through the following code (of ShapeBase class) in Debug mode

 

protected internal override sealed void AttachGluePointToConnectionPoint(ControlPointId ownPointId, Shape otherShape, ControlPointId gluePointId) {
    ...
    if (!connectionInfos.Contains(connectionInfo)) {
        if (this.ModelObject != null && otherShape.ModelObject != null && otherShape.Template != null)
            ModelObject.Connect(Template.GetMappedTerminalId(ownPointId), otherShape.ModelObject, otherShape.Template.GetMappedTerminalId(gluePointId));
        connectionInfos.Add(connectionInfo);
    }
}

 

I discovered that while this points to the Planar Shape which is the "source" of the connection, otherShape points to the Linear Shape but not to the second Planar Shape (which is the "target" of the connection). And as far as there is no any Model behind my Linear connector (and so otherShape.ModelObject == null), the 2nd if statement blocks ModelObject.Connect(...) from being called.

How should I connect Models of the two Planar Shapes in this case? I do not care about the connector Line, the result that I want to achieve [in the model domain] when creating directed connection Model1-->Model2 is: Model1.Targets[0] = Model2 and Model2.Sources[0] = Model1. No place for Line here! :)

Coordinator
Oct 23, 2012 at 8:18 AM

I think assigning a model object (that does actually nothing) to your line shapes would be the cleanest solution.

If you cannot do that or do not want to do that, you could handle the IRepository.ConnectionInserted / IRepository.ConnectionDeleted events as a workaround - but I would recommend assigning a model object.

Oct 23, 2012 at 9:57 AM
Edited Oct 23, 2012 at 10:07 AM
KurtHolzinger wrote:

I think assigning a model object (that does actually nothing) to your line shapes would be the cleanest solution.

OK, I assigned model to my line shape. However, its Connect() method never gets called - so at the model level I cannot understand which other models are being connected with this line. At the same time, at the shape level I understand this easily (but for shapes, not models, of course):

public class DataFlowLine : PolylineBase
{
    public override void Connect(ControlPointId ownPointId, Shape otherShape, ControlPointId otherPointId)
    {
        if (this.connectionInfos != null && this.connectionInfos.Count > 0)
        {
            // It's easy to understand here (at the shape level)
            // which planar shapes are being connected with this line:
            Shape sourceShape = this.connectionInfos[0].OtherShape;
            Shape targetShape = otherShape;
            ...
        }
        ...
    }
}

public class DataFlowModel : IModelObject
{
    public void Connect(TerminalId ownTerminalId, IModelObject otherModelObject, TerminalId otherTerminalId)
    {
        // Never gets called!
        // So here (at the model level) I cannot understand
        // which other models are being connected.
    }
}

If I implement the Connect() method in the models of my planar shapes (instead of the DataFlowModel, which is model of my DataFlowLine shape) - then it gets called, for each of the two shapes being connected, and the "otherModelObject" parameter contains reference to the model of the DataFlowLine shape used to connect these shapes. For example, if I am connecting planar shapes "A" and "B" with a line shape "L", and their models are "Am", "Bm" and "Lm" correspondingly, then I detect two invocations of the Connect() method:

    Am.Connect() gets invoked: otherModelObject = Lm
    Bm.Connect() gets invoked: otherModelObject = Lm

The problem is that we have TWO invocations here - because in fact TWO connections are happenning here: from "A" to "L" and then from "L" to "B". So, I cannot "catch" the connection from "A" to "B" as an atomic event, although at the shape level it's very easy (please see the DataFlowLine.Connect() method in the code snippet above - there I determine sourceShape and targetShape atomically, i.e. in the same invokation).

So I start thinking that the idea to connect models of my planar shapes in their IModelObject.Connect() method is not a good idea. It seems it's much easier to connect models in the DataFlowLine.Connect() method... although it smells :)

Coordinator
Oct 24, 2012 at 3:02 PM
Edited Oct 24, 2012 at 3:13 PM

Only the Connect methods of the active shape's model object will be called, that's true. The implementation is analogous to the Shape.Connect method.
You can implement internal methods (just like "AttachGluePointToConnectionPoint") or you can get the line shape with IModelObject.Shapes and test whether it is connected to an other shape/model object with the Shape.IsConnected() method:

// In the ModelObject:
Shape myShape = null;
foreach (Shape s in this.Shapes) { myShape = s; break; }
ShapeConnectionInfo firstPtConn = IsConnected(ControlPointId.FirstVertex, null);
ShapeConnectionInfo lastPtConn = IsConnected(ControlPointId.LastVertex, null);
if (firstPtConn.OtherShape == myShape && lastPtConn.OtherShape != null) {
    // ...
} else if (LastPtConn.OtherShape == myShape && firstPtConn.OtherShape != null) {
    // ...
}

Oct 24, 2012 at 3:25 PM

Thank You.

However, Your solution does not completely fit the scope of the model domain: the model delegates most of the job to the shape. As for me, it's the same as using Shape.Connect() method (and accessing models from within this method).

I was simply hoping that there is an elegant "model-only" solution...