My bad I forgot the stack trace, here it is:
at MindFusion.Diagramming.Diagram.CreateItem(int clsId)
at MindFusion.Diagramming.PersistContext.CreateObject(int clsId)
at MindFusion.Diagramming.PersistContext.TryLoadRemainingObjects()
at MindFusion.Diagramming.PersistContext.LoadRemainingObjects()
at MindFusion.Diagramming.Diagram.LoadFromStream(Stream stream, bool handleCorruption)
at MindFusion.Diagramming.WinForms.DiagramView.PasteFromClipboard(float dx, float dy, bool unconnectedLinks)
at OaSfcEditor.SfcEditorCtl.OnDiagramView_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
at System.Windows.Forms.Control.OnKeyDown(System.Windows.Forms.KeyEventArgs e)
at MindFusion.Diagramming.WinForms.DiagramView.OnKeyDown(System.Windows.Forms.KeyEventArgs e)
See the following table for the custom classes with their base class:
CustomClass | BaseClass |
StepNode | CompositeNode |
TransNode | CompositeNode |
ActionTableNode | TableNode |
ParBeginNode | CompositeNode |
ParEndNode | CompositeNode |
CommentNode | ShapeNode |
JumpLabelNode | ShapeNode |
JumpRefNode | ShapeNode |
StepsLabelNode | ShapeNode |
We have a CodeNode class ontop of the CompositeNode base class that looks like this:
public class CodeNode : CompositeNode
{
public CodeNode( Diagram diagram )
: base(diagram)
{
}
public CodeNode( CodeNode src )
: base( src )
{
}
protected override bool SerializeComponents()
{
return false;
}
public virtual string MemberName
{
get { return null; }
}
public virtual string MethodName
{
get { return null; }
}
public virtual string MethodDef
{
get { return null; }
}
public SfcDiagram ParentSfcDiagram => this.Parent as SfcDiagram;
}
And for the Par*Node classes we have a ParallelNode class on top of CodeNode:
public enum ParType { Begin, End };
// distinct classes for parallel begin & end nodes
// (mostly just to allow easier "node is ..." tests)
class ParBeginNode : ParallelNode
{
public ParBeginNode( Diagram diagram )
: base(diagram)
{
LoadContentForParType( ParType.Begin );
}
public ParBeginNode( ParBeginNode src )
: base( src )
{
LoadContentForParType( ParType.Begin );
}
}
class ParEndNode : ParallelNode
{
public ParEndNode( Diagram diagram )
: base( diagram )
{
LoadContentForParType( ParType.End );
}
public ParEndNode( ParEndNode src )
: base( src )
{
LoadContentForParType( ParType.End );
}
}
class ParallelNode : CodeNode
{
// interactive add of new parallel rail
public static ParallelNode NewNode( SfcDiagram diagram, PointF pt, ParType parType, int parNum )
{
ParallelNode node;
if( parType == ParType.Begin )
node = new ParBeginNode( diagram );
else
node = new ParEndNode( diagram );
node.Id = MakeName( parType, parNum ); // "PBxx" or "PExx"
const float INITIAL_WIDTH = 60;
var root = node.Components[0]; //outermost panel
MindFusion.Drawing.IGraphics graphics = node.Parent.CreateMeasureGraphics();
SizeF desired = root.GetDesiredSize( new SizeF(INITIAL_WIDTH, 6), graphics );
graphics.Dispose();
node.Bounds = new RectangleF( pt.X, pt.Y, INITIAL_WIDTH, desired.Height );
diagram.Nodes.Add( node );
if( parType == ParType.End )
{
// set refs to each other
ParallelNode parbegin = node.FindOtherRail();
node.OtherRail = parbegin;
parbegin.OtherRail = node;
}
return node;
}
private ParType _parType; // to simplify type testing in common code
private static string MakeName( ParType partype, int parnum )
{
return (partype == ParType.Begin ? "PB" : "PE") + parnum.ToString( "D2" );
}
public ParallelNode OtherRail { get; set; }
private ParallelNode FindOtherRail()
{
string id = this.Id as string; // eg "PB01", "PE01" (or "xPB01" during copy/paste)
string otherId;
if( this._parType == ParType.Begin )
{
otherId = id.Replace( "PB", "PE" );
}
else
{
otherId = id.Replace( "PE", "PB" );
}
return this.Parent.FindNodeById( otherId ) as ParallelNode;
}
protected ParallelNode( Diagram diagram )
: base( diagram )
{
this.EnabledHandles = AdjustmentHandles.ResizeMiddleLeft |
AdjustmentHandles.ResizeMiddleRight |
AdjustmentHandles.Move;
this.HandlesStyle = HandlesStyle.RoundAndSquare;
}
protected override void SaveToXml( XmlElement xmlElement, XmlPersistContext context )
{
base.SaveToXml( xmlElement, context );
}
protected override void LoadFromXml( XmlElement xmlElement, XmlPersistContext context )
{
base.LoadFromXml( xmlElement, context );
var other = this.FindOtherRail();
if( other != null )
{
this.OtherRail = other;
other.OtherRail = this;
}
}
// "copy ctor" & binary serialization is required for copy/paste
// (and is used ONLY then in the SfcEditor. normal diagram save/load uses Xml)
public ParallelNode( ParallelNode src )
: base( src )
{
this.Id = "x" + this.Id;
}
protected override void SaveTo( BinaryWriter writer, PersistContext context )
{
base.SaveTo( writer, context );
}
protected override void LoadFrom( BinaryReader reader, PersistContext context )
{
base.LoadFrom( reader, context );
}
// called after all pasted nodes have been created (apparently)
internal void OnNodePasted()
{
if( this.OtherRail == null )
{
// other hasn't set us yet, so we're first
var other = FindOtherRail();
if( other != null )
{
// found our mate. make sure he has us
this.OtherRail = other;
other.OtherRail = this;
// now generate a new Id for both us & our mate
int newNum = this.ParentSfcDiagram.GetUniqueNodeNumber( "PB" );
this.Id = MakeName( this._parType, newNum );
OtherRail.Id = MakeName( OtherRail._parType, newNum );
}
}
}
// content XML for each ParType
// surely there's a simpler way to draw 2 lines & a triangle
private static readonly string BeginContent = @"
<StackPanel Orientation='Vertical' Spacing='0.0' VerticalAlignment='Top'>
<Shape Name='TriAnchor' Shape='M0,100 L50,0 L100,100'
Height='1' Width='4' Pen='Black' />
<StackPanel Orientation='Vertical' Spacing='1.0'>
<Shape Name='Line1' Shape='M0,0 L100,0'
Height='0.1' Pen='Black' />
<Shape Name='Line2' Shape='M0,0 L100,0'
Height='0.1' Pen='Black' />
</StackPanel>
</StackPanel>";
private static readonly string EndContent = @"
<StackPanel Orientation='Vertical' Spacing='0.0' VerticalAlignment='Bottom'>
<StackPanel Orientation='Vertical' Spacing='1.0'>
<Shape Name='Line1' Shape='M0,0 L100,0'
Height='0.1' Pen='Black' />
<Shape Name='Line2' Shape='M0,0 L100,0'
Height='0.1' Pen='Black' />
</StackPanel>
<Shape Name='TriAnchor' Shape='M0,0 L50,100 L100,0'
Height='1' Width='4' Pen='Black' />
</StackPanel>";
// anchor points for each ParType
// single in on top, outs along bottom
static private AnchorPattern _beginAnchors = new AnchorPattern(
new AnchorPoint[]
{
// 1 incoming at middle top. outgoing from anywhere
new AnchorPoint(50, 0, true, false, MarkStyle.None)
{ XUnit = UnitType.Percentage },
}, "parbeginAnchors" );
// ins on top, single out at bottom
static private AnchorPattern _endAnchors = new AnchorPattern(
new AnchorPoint[]
{
// 1 outgoing at middle bottom. incoming to anywhere
new AnchorPoint(50, 100, false, true, MarkStyle.Custom)
{ XUnit = UnitType.Percentage },
}, "parendAnchors" );
// gotta find a better way (to set pen color for the shapes)
ShapeComponent _line1;
ShapeComponent _line2;
ShapeComponent _trianchor;
static MindFusion.Drawing.Pen _normalPen;
// called from subclass ctors
protected void LoadContentForParType( ParType parType )
{
this._parType = parType;
if( parType == ParType.Begin )
{
this.Components.Add( XmlLoader.Load( BeginContent, this ) );
AnchorPattern = _beginAnchors;
}
else
{
this.Components.Add( XmlLoader.Load( EndContent, this ) );
AnchorPattern = _endAnchors;
}
// things we want to change color of (to avoid repeated recursive traversal of content tree)
_line1 = FindComponent( "Line1" ) as ShapeComponent;
_line2 = FindComponent( "Line2" ) as ShapeComponent;
_trianchor = FindComponent( "TriAnchor" ) as ShapeComponent;
if( _normalPen == null )
_normalPen = _line1.Pen;
}
// ParEnd "step" is active (when all parallel branch end steps are active
// & we're waiting on an outgoing transition from the ParEnd)
private bool _isStepActive;
internal void ShowStepActive( bool isActive )
{
if( this._isStepActive == isActive )
return; // no change
this._isStepActive = isActive;
if( isActive )
{
// color the node & outgoing links
_line1.Pen = StepNode.activePen;
_line2.Pen = StepNode.activePen;
_trianchor.Pen = StepNode.activePen;
foreach( var link in this.OutgoingLinks )
{
link.Pen = StepNode.activePen;
link.HeadBrush = StepNode.activeBrush;
//link.HeadPen = activePen;
}
}
else
{
// reset node & outgoing links to default color
_line1.Pen = _normalPen;
_line2.Pen = _normalPen;
_trianchor.Pen = _normalPen;
foreach( var link in this.OutgoingLinks )
{
link.Pen = null;
link.HeadBrush = null;
link.HeadPen = null;
}
}
this.Repaint( true ); // repaint step node & connected things
}
// overrides for code generation
public override string MemberName => (string)this.Id;
// called on diagram.NodeModified event (after node moved/resized by mouse)
internal void OnNodeModified()
{
this.StraightenInsideLinks();
if( OtherRail != null )
{
var otherBounds = OtherRail.Bounds;
otherBounds.X = this.Bounds.X;
otherBounds.Width = this.Bounds.Width;
OtherRail.Bounds = otherBounds;
OtherRail.StraightenInsideLinks(); // other doesn't get OnNodeModified
}
}
private void StraightenInsideLinks()
{
if( this._parType == ParType.Begin )
{
foreach( var link in this.OutgoingLinks )
{
this.ParentSfcDiagram.StraightenUnanchoredLink( link );
}
}
else
{
foreach( var link in this.IncomingLinks )
{
this.ParentSfcDiagram.StraightenUnanchoredLink( link );
}
}
}
internal void OnNodeDeleted( NodeEventArgs e )
{
if( OtherRail != null )
{
Parent.Nodes.Remove( OtherRail );
}
}
public override void DrawShadow( IGraphics graphics, RenderOptions options )
{
base.DrawShadow( graphics, options );
if( this.Selected )
ParentSfcDiagram.DrawSelectShadow( this, graphics, options );
}
}
Hopefully this will provide enough information.