How to take charge of DSL Tools swimlane ordering

In a question on our forum, Michael Ruck asks:

I have the following situation: A neural network consists of multiple layers (modelled using swimlanes.) There are three types of layers: Input, Hidden and Output layers. I'm basically done with the modelling, but want to restrict the order of these layers in the diagram: The order should always be fixed: Input, 0..n Hidden, Output.

E.g. if the user adds more hidden layers, these should be inserted between the last hidden and the output layer

A designer might look something like this:

Picture of neural net designer swimlanes

He and some of the other folks on the forum hadn't managed to get it going and it looked like a bug, so I did a little investigation and it turns out that there is a clean way to make it work, it's just that it is undocumented and a bit of a pain to get at.

Essentially, the swimlanes are just NestedChildShapes of the Diagram, and in this case, the way I set up my test, the Input and Output swimlanes are unmapped to any underlying MEL whereas the Hidden ones in the middle are each mapped to a layer MEL.

 

For swimlane ordering, the right technique is to intercept the SwimlaneFixUpContext, which is a well-known slot in the transaction context.
This context is set when a swimlane is first inserted using the swimlane menus and acted upon at Diagram.OnChildConfiguring time.

The context is stored as an array containing two objects, where the items in the array are:
[0]: (SwimLaneShape) The swimlane that the new swimlane should be placed relative to
[1]: (bool) Whether the new swimlane should be placed after it (true) or before it (false)

Manipulating this context allows you to influence where in the swimlane order a new one gets put.

If the ordering happens at this point in the process, then it will be respected by drawing and invalidation code and all will be well.

However in V1, the SwimlaneFixupContext is not exposed publically - so I guess its only internally well-known :-)
The context GUID you need to access it today from the TransactionContext is in the code sample below.
In Orcas we'll add a public API to read and set this data explicitly to make this a bit more discoverable. 

You cna instead of this , choose to use rules to order the shapes yourself - however, typically this will be tricky because it's difficult to get the rule timing right.  If you do get the timing right and then call SwimLaneHelper.AnchorAllSwimLanes(diagram) you may stll get invalidation problems, as the Invalidation logic deliberately ignores swimlanes and diagrams by default as they're generally part of the furniture of the diagram.

If you really want to change this, you cna also override

bool Diagram.ShouldExcludeFromInvalidationTracking(ShapeElement shape);

and return true for just diagrams, not diagrams and swimlanes.

Here's some sample code overriding OnChildConfiguring on a diagram that causes non-menu added swimlanes to always go just before the Outer swimlane.

 

 public partial class Language2Diagram
{
    private const string SwimLaneFixUpContext = "0111ed36-b759-4a03-bac2-cda1aef1c9d6";

    protected override void OnChildConfiguring(ShapeElement child, bool createdDuringViewFixup)
    {
        SwimlaneShape swimLaneShape = child as SwimlaneShape;
        Debug.Assert(swimLaneShape == null || this.NestedChildShapes.Contains(swimLaneShape));
        if (swimLaneShape != null)
        {
            // When swimlane shape is added NOT via a context menu, we should put it before the outer simlane
            // Manipulate the swimlane context to do this, then call teh base class to let teh work happen.
            TransactionContext context = this.Store.TransactionManager.InTransaction ? this.Store.TransactionManager.CurrentTransaction.Context : null;
            
            // Don't interfere with the order when we're loading from file.
            if (context != null && !this.Store.TransactionManager.CurrentTransaction.IsSerializing)
            {
                // If there is no context then this is either the first auto-created swimlane in an otherwise empty diagram
                // or it is a swimlane being added by some other means than the swimlane menus (e.g. toolbox perhaps)
                if (!context.ContextInfo.ContainsKey(SwimLaneFixUpContext))
                {
                    // First find the outer swimlane
                    Outer outerSwimLane = null;
                    foreach (ShapeElement shape in this.NestedChildShapes)
                    {
                        outerSwimLane = shape as Outer;
                        if (outerSwimLane != null)
                            break;
                    }

                    if (outerSwimLane != null)
                    {
                        Transaction t = this.Store.TransactionManager.CurrentTransaction;
                        if (t != null)
                        {
                             // Set the context to specify insert before the outer swimlane
                            t.Context.ContextInfo[SwimLaneFixUpContext] = new object[] { outerSwimLane, false };
                        }
                    }
                }
                else // Swimlane context IS present 
                     // This insert therefore came from a swimlane menu - modify the context as necessary.
                {
                    // Impose your constraints as desired on context.
                }
            }
        }
        // Now let the base do its work of repositioning the swimlanes.
        base.OnChildConfiguring(child, createdDuringViewFixup);
    }
}
  

Technorati Tags: dsl - dsl tools - domain specific language - code sample