This project has moved and is read-only. For the latest updates, please go here.

Prevent connection from ModelObject

Apr 24, 2014 at 12:45 PM
Hi again,
I wanted to know if there was any way to prevent a connection from being created from the ModelObject point of view.

If I throw an exception from the Connect() method (of my ModelObject), it is not catched at all so it crashes the application.
I also saw on another discussion that the AttachGluePointToConnectionPoint() method could be a good candidate for this but it is sealed.

Do you have any idea on how I could perform this?
Apr 24, 2014 at 2:54 PM
You could override the shape's CanConnect method:
CanConnect(ControlPointId ownPointId, Shape otherShape, ControlPointId otherPointId);
I'm not sure if this suits your needs but as you can access the shapes from the model and the model from the shape, I think it might be suitable.
Apr 28, 2014 at 3:35 PM
Edited Apr 28, 2014 at 3:37 PM
I'm not really sure that it will work with the kind of test I need to check if a connection is authorized...

Here is the deal:
  • 2 Shapes, each one with a ModelObject, need to be connected together.
  • Each shape has a predefined number of Inputs and Outputs (2 Inputs cannot be connected together and the same applies for outputs).
  • The first shape on which the connection is created does not need to check anything.
  • However the second shape cannot be connected to the first one if the connection match one of the below criterias:
    • Shape1 connection point is an Input and Shape2 connection point is an Input;
    • Shape1 connection point is an Output and Shape2 connection point is an Output;
    • Shape1 == Shape2.
It is the ModelObject that contains the information about a Port "direction".
Apr 29, 2014 at 8:12 AM
Edited Apr 29, 2014 at 8:13 AM
I assume the connector shape is not one of the two shapes mentioned above, right?

The Connect and CanConnect methods are called on the active shape (the one with the glue point, usually a linear shape).
I thought of something like
class BaseConnector {

    public override bool CanConnect(ControlPointId ownPointId, Shape otherShape, 
                                ControlPointId otherPointId) 
        {
        if (!base.CanConnect(ownPointId, otherShape, otherPointId))
            return false;

        ControlPointId otherGluePoint = (ownPointId == ControlPointId.FirstVertex) 
            ? ControlPointId.LastVertex : ControlPointId.FirstVertex;
        ShapeConnectionInfo connInfo = GetConnectionInfo(otherGluePoint, null);
        if (connInfo != ShapeConnectionInfo.Empty) {
            // Shape is connected on the other side, perform additional checks
            if (this == connInfo.OtherShape)
                return false;
            else if (...)
                // Check the other conditions using the connInfo struct and 
                // the model objects
        } else {
            // Shape is not yet connected -> No checks needed
            return true;
        }
    }

}
May 6, 2014 at 4:07 PM
Edited May 6, 2014 at 4:10 PM
Hi Kurt,

It took me a long time to come back on this problem but I tried to solve it today thanks to your answer. It seems that there is problem with the CanConnect() function:

Even when I return false, the user is still able to connect the linear shape to the planar shape. The connection points are not shown but if you try to connect the shape, it actually works (or at least it "glues" the linear shape to the planar shape).

I used the NShape Designer with my custom linear shape (containing only the code you gave me) and a planar shape from the GeneralShape library.
Am I missing something or is there really something wrong with this behaviour?

PS: You are right, my connector shape was not one of those mentioned above.
May 6, 2014 at 4:43 PM
Edited May 6, 2014 at 4:59 PM
It also seems that the CanConnect() method has a great impact on the display performances. The user experience is really bad with around 8 if statements in the CanConnect() method. I am not sure if it is the best way to achieve what I want to do.

[EDIT]
That's actually pretty weird but I have no performances problem with the NShape Designer even when using my Shape and Model library...
I will have to dig into it.
May 7, 2014 at 11:05 AM
Edited May 7, 2014 at 1:18 PM
[Updated]
Regarding the CanConnect implementation:
The current implementation only calls the CanConnect method of the active shape (the one with the glue point, typically the line shape).
If the CanConnect logic is in the passive shape, you will have to override the CanConnect method of all your active shapes (the line shapes, perhaps the labels, too) like this:
// CanConnect of line shapes
public override bool CanConnect(ControlPointId ownPointId, Shape otherShape, 
                        ControlPointId otherPointId) 
{
    // The current implementation of the base class will *always* call the line 
    // shape's CanConnect method.
    if (base.CanConnect(ownPointId, otherShape, otherPointId)) {
        // In case this line shape's base implementation thinks connecting is 
        // a good idea, we better ask the partner shape about its opinion, too...
        return otherShape.CanConnect(ownPointId, otherShape, otherPointId);
    } else 
        return false;
}


// CanConnect of planar shapes
public override bool CanConnect(ControlPointId ownPointId, Shape otherShape, 
                        ControlPointId otherPointId) 
{
    bool result = true;

    // Example for testing: Deactivate POint-To-Shape connections
    result = (ownPointId != ControlPointIds.Reference);

    return result;
}
Currently, we are discussing this behaviour. We will probably change it in one of the next releases probably such that each shape decides for itself and the caller has to call CanConnect on both shapes. The outcome of the discussion can be found in a future Changes.txt file... ;-)


Regarding the performance problem:
Start digging with running your application in release mode... in debug mode, the framework performas additional checks and draw calls.
In addition to that, optimization will speed up the general performance of the framework (or the debugger will slow down it, don't know).
In case you are using WPF with windows forms host, the things mentioned before get even worse in debug mode (I assume that is because of the additional UI layers the debugger has to cross).
If switching to release mode does not eliminate your performance problem:
Keep in mind that this method will be called very often (in worst case on every mouse move). If there are expensive calls, think about buffering the result implementing a notification pattern.
May 7, 2014 at 11:18 AM
Edited May 7, 2014 at 11:28 AM
Thanks, I will probably override the passive shape CanConnect() method so that if the active shape CanConnect() method return false the connection is aborted.
Regarding the performance problem I will keep you up to date about my discoveries :-)

If you could share the Changes.txt file (once its done) it would be great for everyone I think.

[EDIT]
Ok, it seems that I didn't fully understand your previous answer about the CanConnect() method. The passive shape CanConnect() is never called, that is what you were trying to tell me...I get it now.
So it does not solve my problem: my active shape CanConnect() method returns false but the connection is still created.
May 7, 2014 at 1:17 PM
I've updated my answer above - hoping it my message is more understandable... :-)
To put it in a nutshell:
  • You have to override CanConnect on both, line shapes and planar shapes.
  • The line shapes have to call the planar shap's CanConnect method.
  • The planar shapes have to implement your custom logic.
May 7, 2014 at 1:30 PM
Well, what I am trying to say here is that it doesn't matter what is the result of the CanConnect() method, the active shape is always created and "glued" to the passive shape connection point.

Here is the code I am using to test it:
public override bool CanConnect(ControlPointId ownPointId, Shape otherShape, ControlPointId otherPointId)
{
  ControlPointId otherGluePoint = (ownPointId == ControlPointId.FirstVertex) ? ControlPointId.LastVertex : ControlPointId.FirstVertex;
  ShapeConnectionInfo connInfo = this.GetConnectionInfo(otherGluePoint, null);

  if (connInfo != ShapeConnectionInfo.Empty)
    return false;
  else
    return true;
}
And here are the steps for triggering this behaviour:
  • Drag 2 planar shapes on the Display;
  • Select my custom linear shape in the toolbox;
  • Click on one of the connection points of the first planar shape;
  • Click on one of the (hidden) connection points of the second shape (you have to be accurate enough to do it without seing the connection point actually);
  • The linear shape is created and "glued" to both the first and second planar shape even if the CanConnect() method returned false.
May 7, 2014 at 2:55 PM
Aahh! That was the missing piece of information.
Seems to be a bug in the LinearShapeCreationTool. I have to investigate this issue.

Until I can provide a workaround, try this:
  • Drag 2 planar shapes on the Display
  • Select your custom linear shape in the toolbox
  • Create a line by not clicking on a planar shape two times
  • Drag'n'Drop one of the line ends over the first planar shape
  • Drag'n'Drop the other end of the line over the second planar shape
  • => The CanConnect implementation of your custom line shape will be called and the connection should be refused.
Marked as answer by roux1max on 5/7/2014 at 7:02 AM
May 7, 2014 at 3:02 PM
Ok thanks, I was starting to think that my app was completely bugged.
May 9, 2014 at 9:17 AM
A fix for the bug is on the way via email.
It will also be included in the next release.
May 12, 2014 at 8:12 AM
Thanks, I received it!