Skip to content

State Chart Editor Programming Discussion

Gary edited this page Mar 10, 2015 · 6 revisions

Table of Contents

The ATF State Chart Editor Sample shows another example of using the ATF graph framework to create a graph editor for a statechart, which is a way of graphically presenting a state machine. It bears a strong resemblance to the ATF Fsm Editor Sample and somewhat to ATF Circuit Editor Sample, which are also graph editors based on ATF graph facilities. This figure shows the statechart editor sample application:

ATF offers support for statecharts in its graph classes. For more details, see Statechart Graph Support.

Statechart concepts are described in the paper by David Harel A Visual Formalism for Complex Systems at http://www.wisdom.weizmann.ac.il/~dharel/SCANNED.PAPERS/Statecharts.pdf. A statechart oriented state machine tool based on ATF has also been developed.

Programming Overview

StateChartEditor is, in several ways, a little more complex version of the ATF Fsm Editor Sample, and the two samples have much in common. You can learn about that sample's internals in FSM Editor Programming Discussion; this topic often references sections in this discussion.

StateChartEditor uses the ATF graph interfaces and classes, with its statechart version of the graph interface IGraph. For information about graph support for statecharts, see Statechart Graph Support.

The data model takes into account the containment hierarchy: an ordinary state can contain other states and transitions. A statechart can have pseudo-states, such as the start state, as well as a normal state, and these all extend a base state type.

Like the FsmEditor sample, most of StateChartEditor's classes are DOM adapters, one for each type of object a statechart can contain, for instance. Other DOM adapters handle documents, contexts, and statechart validation.

The Editor component is the document client and control host client for the control that holds the statechart, a D2dAdaptableControl. These clients are implemented very similarly to those in the Editor class of the FsmEditor. You can also print statecharts with the PrintableDocument class.

The contexts in StateChartEditor also strongly resemble those of FsmEditor. The EditingContext class implements interfaces similarly to the corresponding class in FsmEditor; however, it also implements the IGraph interface containing properties listing statechart objects.

This sample's validator classes check that parent states' bounds are adjusted when child states change, that only legitimate transitions are added, and that locked states can't be edited.

Statecharts and Graphs

Statecharts specialize the ATF graph model embodied by the IGraph interface: IGraph<IState, IGraphEdge<IState, BoundaryRoute>, BoundaryRoute>. For a description of this specialized interface, see Statechart Graph Support.

StateChartEditor Data Model

StateChartEditor's data model is defined in the XML Schema file Statechart.xsd. Because both are state machine editors, StateChartEditor's data model resembles that of the ATF Fsm Editor Sample. Both have state, transition, annotation, prototype, and prototype folder types. Unlike ATF Fsm Editor Sample, StateChartEditor has inheritance in its types. Here are the types and types based on them:

  • annotationType: annotation on a statechart.
  • prototypeFolderType: prototype folder containing prototypes and other prototype folders.
  • prototypeType: prototype containing states and transitions that can be reused.
  • reactionType: event/guard/action triple; a guard is a condition for a state transition.
    • transitionType: transition connecting a source state to a destination state.
  • stateBaseType: base state.
    • conditionalStateType: conditional pseudo-state.
    • finalStateType: final pseudo-state.
    • historyStateType: history pseudo-state.
    • startStateType: start pseudo-state.
    • stateType: normal state (not a pseudo-state) that can contain statecharts.
  • statechartType: statechart that contains states.
    • statechartDocumentType: root statechart, containing all transitions, annotations, and prototypes. It also contains states by virtue of being based on statechartType.
The most used parent type is "stateBaseType", on which all the other state types are based. This figure from the Visual Studio XML Schema Explorer of the schema file shows types' base types and also has parent types' nodes opened to show their child types:

This differs from the FsmEditor in its hierarchy. The "stateType" type can contain "reactionType" and "statechartType" types. The "statechartType" type contains only "stateBaseType", representing all the various state types, including "stateType" the only non-psedudo-state type. This means that a state can contain states (all the different ones) and transitions, since a transition is a "reactionType". The type "statechartDocumentType", based on "statechartType", is the root type and can contain "transitionType", "annotationType", and "prototypeFolderType", which allows it to contain everything. Thus, the "stateType" type does not need to contain annotations or prototypes, which simplifies it.

StateChartEditor uses the GenSchemaDef.bat command file for DomGen to create the Schema class (containing type metadata classes) from Statechart.xsd.

StateChartEditor has a SchemaLoader class, derived from XmlSchemaTypeLoader as usual. Its overridden OnSchemaSetLoaded() also performs the typical tasks of adding data to the Schema class's metadata class objects:

StateChartEditor DOM Adapters

Like other graph editor samples, most of the classes are DOM adapters. There is a DOM adapter for every type in the data model, for instance; for details, see StateChartEditor Types DOM Adapters.

StatechartDocumentType DOM Adapters

Most of the DOM adapters are defined for the "statechartDocumentType" type, which represents the whole statechart. The root element of the DomNode tree is of "statechartDocumentType". These adapters fall in a few categories:

StateChartEditor Types DOM Adapters

Each of the types in the data model, such as "stateType" and "transitionType", has a DOM adapter defined for it. Here are their definitions in SchemaLoader.OnSchemaSetLoaded():

// register the statechart model interfaces
Schema.prototypeFolderType.Type.Define(new ExtensionInfo<PrototypeFolder>());
Schema.prototypeType.Type.Define(new ExtensionInfo<Prototype>());
Schema.annotationType.Type.Define(new ExtensionInfo<Annotation>());
Schema.statechartType.Type.Define(new ExtensionInfo<Statechart>());
Schema.stateType.Type.Define(new ExtensionInfo<State>());
Schema.conditionalStateType.Type.Define(new ExtensionInfo<ConditionalState>());
Schema.finalStateType.Type.Define(new ExtensionInfo<FinalState>());
Schema.historyStateType.Type.Define(new ExtensionInfo<HistoryState>());
Schema.reactionType.Type.Define(new ExtensionInfo<Reaction>());
Schema.startStateType.Type.Define(new ExtensionInfo<StartState>());
Schema.transitionType.Type.Define(new ExtensionInfo<Transition>());

The following table describes the DOM adapter for each type. Most of these DOM adapters are very simple. They have properties that get and set type attribute values, using the DomNode.GetAttribute() and DomNode.SetAttribute() methods on the attribute metadata class members, defined in the Schema class. Some of the DOM adapters use the DomNodeAdapter.GetChildList() to get a list of attributes.

Type DOM Adapter Derives from, Implements Properties, Methods Notes
annotationType Annotation DomNodeAdapter, IAnnotation Bounds, Location, Text, SetTextSize() Comment on the canvas. Identical to Annotation in ATF Fsm Editor Sample. For more details, see Annotation DOM Adapter in FSM Editor Programming Discussion.
conditionalStateType ConditionalState StateBase Type A conditional pseudostate, which indicates a state with conditions to reduce the number of transitions.
finalStateType FinalState StateBase Type The final pseudostate, which is the last state that a state machine can be in.
historyStateType HistoryState StateBase Type A history pseudostate, which may be shallow or deep. History determines which states become active in a destination state's child state machines when the transition is taken to this destination. For a discussion of how history works in state machines, see State Machine History.
prototypeFolderType PrototypeFolder DomNodeAdapter Folders, Name, Prototypes Folder of prototypes. Identical to PrototypeFolder in ATF Fsm Editor Sample. For details, see Prototype and PrototypeFolder DOM Adapters in FSM Editor Programming Discussion.
prototypeType Prototype DomNodeAdapter Name, States, Transitions Prototype containing states and transitions that can be reused. Almost identical to Prototype in ATF Fsm Editor Sample. For details, see Prototype and PrototypeFolder DOM Adapters in FSM Editor Programming Discussion.
reactionType Reaction DomNodeAdapter Action, Event, Guard, ToString() Adapts DomNodes to reactions, which are contained in states. For more information, see Reaction DOM Adapter.
startStateType StartState StateBase Type The start pseudostate, which points to the first state that is active when the state machine starts running.
statechartType Statechart DomNodeAdapter AllStates, Bounds, Parent, States Statechart that contains states. The AllStates property gets the states in the statechart and all sub-statecharts.
stateType State StateBase. IComplexState<StateBase, Transition> EntryAction, ExitAction, IsPseudoState, IsSimple, Name, Reactions, Size, Statecharts, SubStates, Type Regular state machine state. For details, see State DOM Adapter. This adapter is not very similar to State in ATF Fsm Editor Sample.
transitionType Transition Reaction, IGraphEdge<StateBase, BoundaryRoute> FromPosition, FromState, ToPosition, ToState Transition from a state to a state. For details, see Transition DOM Adapter. Also, Transition is similar to Transition in ATF Fsm Editor Sample; for details, see Transition DOM Adapter in FSM Editor Programming Discussion.
N/A StateBase DomNodeAdapter, IState Bounds, Indicators, IsPseudoState, Locked, Parent, Position, Size, Type Base for other state DOM adapters, such as State and ConditionalState. For more information, see StateBase DOM Adapter.

State DOM Adapter

Much of this adapter is devoted to the task of many adapters: setting up type attribute values as gettable and/or settable properties.

State also implements IComplexState<StateBase, Transition>, the interface for states in statechart diagrams that are non-pseudo-states. State is the only state in StateChartEditor that is not a pseudo-state. IComplexState's only property is Text, which gets the state's interior text, displayed as a popup when the state is hovered over. This is a concatenation of text from EntryAction, ExitAction, and the Reactions associated with the state.

IComplexState implements IHierarchicalGraphNode<StateBase, Transition, BoundaryRoute>, and its property SubNodes, the sequence of nodes that are children of this hierarchical graph node. Because a non-psudo-state can contain a state machine, this interface provides a way to access that information. SubNodes simply gets the property SubStates, an enumeration of the states inside the state, also known as substates.

Reaction DOM Adapter

A reaction tells how a state reacts to events. Its properties, all string values, are:

  • Event: event the state sees.
  • Guard: what triggers a transition from the state.
  • Action: action associated with an event.
The ToString() method returns a string containing all these properties' values.

Note that State's Reactions property lists all the Reaction objects associated with the state.

Transition DOM Adapter

Transition derives from Reaction and adds properties:

  • FromState, ToState: source and destination states of transition.
  • FromPosition, ToPosition: the BoundaryRoute route position on the perimeter of the state at which this transition begins and ends. Its range is \[0..4\[,]and]it starts and ends at the top-left, going in a clockwise direction. The range from 0 to 1 is on the right side, from 1 to 2 on the bottom, and so forth.
Transition also implements IGraphEdge&amp;lt&#59;StateBase, BoundaryRoute&amp;gt&#59;, which implements IGraphEdge&amp;lt&#59;StateBase&amp;gt&#59;. The first interface provides the properties FromRoute and ToRoute that get the BoundaryRoute at each end of the transition; these are the same as FromPosition and ToPosition. For more details, see BoundaryRoute Class. The second interface provides FromNode, ToNode, and Label: the from and to State and the label on the state displayed to the user.

StateBase DOM Adapter

StateBase is the base class for all state type DOM adapters and provides their common properties:

  • Bounds: Get or set the state's bounding rectangle.
  • IsPseudoState: Get whether this is a pseudo-state.
  • Locked: Get or set the locked state, used in locking states. For further discussion, see LockingValidator Class.
  • Parent: Get or set the state's parent Statechart.
  • Position: Get or set the state position, which is at the state's upper-left corner.
  • Size: Get or set the state size.
StateBase also implements IState, the interface for states in state-transition diagrams, and IState implements IGraphNode. For more information, see IState Interface.

StateChartEditor Documents

StateChartEditor implements both document and document client interfaces. For a general discussion of documents, see Implementing a Document and Its Client.

Document Class

Document, a DOM adapter defined for the "statechartDocumentType", derives from DomDocument that implements IDocument. Document is very similar to the Document class in ATF Fsm Editor Sample. It implements the same methods in an almost identical way and is very simple.

Where it differs is that StateChartEditor's Document implements IAnnotatedDiagram, for diagrams that contain annotations. Its Annotations property gets the list of annotations on the statechart from the root DomNode, which has the type "statechartDocumentType", because this type contains the type "annotationType":

public IList<IAnnotation> Annotations
{
    get { return GetChildList<IAnnotation>(Schema.statechartDocumentType.annotationChild); }
}

Editor Component

The Editor component is the document client and control host client for the control that holds the statechart, and so it implements both IDocumentClient and IControlHostClient:

public class Editor : IDocumentClient, IControlHostClient, IInitializable

These clients also bear a strong resemblance to the clients in ATF Fsm Editor Sample. For a discussion of the FsmEditor clients, see Editor Component.

Graph and Other Control Adapters

The differences from FsmEditor are primarily in the IDocumentClient.Open() method. Like FsmEditor, this method creates a D2dAdaptableControl and constructs a set of control adapters for it. One simple addition is to draw a grid on the canvas with D2dGridAdapter:

var gridAdapter = new D2dGridAdapter();

Another difference is the graph adapter:

var statechartAdapter = new StatechartGraphAdapter(m_statechartRenderer, transformAdapter);

First, notice the renderer, constructed like this:

m_diagramTheme = new D2dDiagramTheme();

D2dGradientStop[] gradStops =
{
    new D2dGradientStop(Color.WhiteSmoke,0),
    new D2dGradientStop(Color.LightGray,1)
};
m_diagramTheme.FillGradientBrush  = D2dFactory.CreateLinearGradientBrush(gradStops);
m_diagramTheme.FillBrush = D2dFactory.CreateSolidBrush(Color.WhiteSmoke);
m_statechartRenderer = new D2dStatechartRenderer<StateBase, Transition>(m_diagramTheme);

D2dStatechartRenderer , which derives from D2dGraphRenderer, is the class to handle rendering and hit testing on statechart graphs. For information about this renderer, see D2dStatechartRenderer Class. D2dStatechartRenderer takes a D2dDiagramTheme parameter to specify its theme. FsmEditor's renderer also used a D2dDiagramTheme; for more information about D2dDiagramTheme, see Document Client. Here the D2dDiagramTheme is configured by setting its FillGradientBrush and FillBrush properties. The FillGradientBrush draws a gradient, using the specified D2dGradientStop array, between the colors specified in this array.

To return to the adapter, StatechartGraphAdapter is a custom class deriving from D2dGraphAdapter:

private class StatechartGraphAdapter : D2dGraphAdapter<StateBase, Transition, BoundaryRoute>

D2dGraphAdapter is a general control adapter to reference and render a graph diagram. All StatechartGraphAdapter does is to override its base class's OnRender() method to draw the statechart the way it wants, using the D2dStatechartRenderer. For information on D2dGraphAdapter, see D2dGraphAdapter Class.

This sample, like FsmEditor, uses the control adapters D2dGraphNodeEditAdapter and D2dGraphEdgeEditAdapter to allow dragging states and transitions. These control adapters use the renderer D2dStatechartRenderer and the graph adapter StatechartGraphAdapter defined here; these adapters' constructors take a D2dGraphRenderer and D2dGraphAdapter.

Hover Adapter

When the cursor moves over a state, this information window displays:

Both this sample and FsmEditor use a HoverAdapter to display information when the cursor hovers over canvas items:

var hoverAdapter = new HoverAdapter();
hoverAdapter.HoverStarted += control_HoverStarted;
hoverAdapter.HoverStopped += control_HoverStopped;

Hovering sheds light on both the hover adapter and property descriptors.

The HoverStarted event handler simply gets the hover form when the cursor is over an object on the canvas:

private void control_HoverStarted(object sender, HoverEventArgs<object, object> e)
{
    m_hoverForm = GetHoverForm(e.Object);
}

The e.Object object is the object hovered over, which is passed to GetHoverForm().

HoverStopped's handler closes and disposes of this form:

private void control_HoverStopped(object sender, EventArgs e)
{
    if (m_hoverForm != null)
    {
        m_hoverForm.Close();
        m_hoverForm.Dispose();
    }
}

The GetHoverForm() method creates the form (based on information from the hovered over object) and displays it:

private HoverBase GetHoverForm(object hoverItem)
{
    HoverBase result = CreateHoverForm(hoverItem);
    if (result != null)
    {
        Point p = Control.MousePosition;
        result.Location = new Point(p.X - (result.Width + 12), p.Y + 12);
        result.ShowWithoutFocus();
    }
    return result;
}

Finally, CreateHoverForm() gets a property descriptor associated with the hovered over object, gets its attributes, and formats the text to show in the hover window:

// create hover form for primitive state or transition
private static HoverBase CreateHoverForm(object hoverTarget)
{
    // handle states and transitions
    StringBuilder sb = new StringBuilder();
    ICustomTypeDescriptor customTypeDescriptor = Adapters.As<ICustomTypeDescriptor>(hoverTarget);
    if (customTypeDescriptor != null)
    {
        // Get properties interface
        foreach (System.ComponentModel.PropertyDescriptor property in customTypeDescriptor.GetProperties())
        {
            object value = property.GetValue(hoverTarget);
            if (value != null)
            {
                sb.Append(property.Name);
                sb.Append(": ");
                sb.Append(value.ToString());
                sb.Append("\n");
            }
        }
    }

    HoverBase result = null;
    if (sb.Length > 0) // remove trailing '\n'
    {
        sb.Length = sb.Length - 1;
        result = new HoverLabel(sb.ToString());
    }

    return result;
}

The parameter hoverTarget is the object hovered over, which is a State object of "stateType". State derives from StateBase, which derives from DomNodeAdapter, which adapts a DomNode, so ultimately, this object is a DomNode representing a state.

To understand what the CreateHoverForm() method is doing, consider this line in Program.cs, which nearly all samples, including StateChartEditor, have:

DomNodeType.BaseOfAllTypes.AddAdapterCreator(new AdapterCreator<CustomTypeDescriptorNodeAdapter>());

AddAdapterCreator() adds an adapter creator onto every type for CustomTypeDescriptorNodeAdapter, which implements ICustomTypeDescriptor. This means that an adapter can be found for ICustomTypeDescriptor for every type in StateChartEditor. That is, every DomNode can be adapted to ICustomTypeDescriptor. This is useful in extracting attribute information from the DomNode.

CreateHoverForm() first tries to adapt the hovered over object to ICustomTypeDescriptor, that is, to determine if the object implements ICustomTypeDescriptor and to get an object on which this interface can be used:

ICustomTypeDescriptor customTypeDescriptor = Adapters.As<ICustomTypeDescriptor>(hoverTarget);

This works because an adapter for CustomTypeDescriptorNodeAdapter was added for every type of DomNode. So the variable customTypeDescriptor is not null and the loop iterates on the property collection obtained by customTypeDescriptor.GetProperties(). The SchemaLoader.OnSchemaSetLoaded() method created this property descriptor the the "stateType":

Schema.stateType.Type.SetTag(
    new PropertyDescriptorCollection(
        new PropertyDescriptor[] {
            new AttributePropertyDescriptor(
                Localizer.Localize("Label"),
                Schema.stateType.labelAttribute, // 'nameAttribute' is unique id, label is user visible name
                null,
                Localizer.Localize("State label"),
                false),
            new AttributePropertyDescriptor(
                Localizer.Localize("Entry Action"),
                Schema.stateType.entryActionAttribute,
                null,
                Localizer.Localize("Action taken when state is entered"),
                false),
            new AttributePropertyDescriptor(
                Localizer.Localize("Exit Action"),
                Schema.stateType.exitActionAttribute,
                null,
                Localizer.Localize("Action taken when state is exited"),
                false),
            new AttributePropertyDescriptor(
                Localizer.Localize("Width"),
                Schema.stateType.widthAttribute,
                null,
                Localizer.Localize("Width of state"),
                false),
            new AttributePropertyDescriptor(
                Localizer.Localize("Height"),
                Schema.stateType.heightAttribute,
                null,
                Localizer.Localize("Height of state"),
                false),
    }));

The PropertyDescriptor collection for the state object thus includes each of the properties listed above. The value can then be obtained for each property:

object value = property.GetValue(hoverTarget);

The subsequent code uses a StringBuilder to build the hover text from the property's Name and its value text obtained from value.ToString(). The assembled string is then encapsulated in a HoverLabel object, which CreateHoverForm() returns. GetHoverForm() then displays this value in the hover window by invoking ShowWithoutFocus() on the HoverLabel.

When the state object is selected, these property values are shown in the property editor:

This is the same object that was hovered over in the previous figure, and the property values are the same as shown in the hover window, just as expected, because they come from the same source.

PrintableDocument Class

PrintableDocument provides what's needed to print statecharts. This class is almost the same as PrintableDocument in ATF Fsm Editor Sample. For information on this FsmEditor class, see PrintableDocument Class.

StateChartEditor Contexts

StateChartEditor has the same contexts as FsmEditor, and their functions and implementations have a lot in common.

EditingContext Class

Like the EditingContext in FsmEditor, this one derives from Sce.Atf.Dom.EditingContext and implements many of the same interfaces:

public class EditingContext : Sce.Atf.Dom.EditingContext,
    IEnumerableContext,
    IObservableContext,
    INamingContext,
    ILockingContext,
    IInstancingContext,
    IGraph<StateBase, Transition, BoundaryRoute>,
    IEditableGraph<StateBase, Transition, BoundaryRoute>

The implementations of IEnumerableContext, IObservableContext, INamingContext, and IInstancingContext resemble their FsmEditor counterparts. Some of the differences are due to the type hierarchy differences, discussed in StateChartEditor Data Model.

StateChartEditor includes the StandardLockCommands component, which creates Lock and Unlock items under the Edit menu. This permits locking states so they can't be changed. Supporting this also requires implementing the ILockingContext, which EditingContext does. StateBase has a Locked property, which is handled by the methods in ILockingContext that test whether an item can be or is locked and also set the locked state. For more information on how locking works, see LockingValidator Class.

Because this sample uses the ATF graph facilities, IGraph must be implemented somewhere. In this case, EditingContext implements IGraph&amp;lt&#59;StateBase, Transition, BoundaryRoute&amp;gt&#59;. For a description of this incarnation of the IGraph interface, see Statechart Graph Support. Its implementation consists simply of creating the properties Nodes and Edges, to get lists of StateBase and Transition objects in the entire statechart.

Like FsmEditor, StateChartEditor's EditingContext uses IEditableGraph&amp;lt&#59;State, Transition, NumberedRoute&amp;gt&#59; to make and break connections between states, that is, to add and remove transitions. The methods CanConnect, Connect, CanDisconnect, and Disconnect test whether transitions can be created or removed, and make and destroy transitions. Here's the most complicated method:

Transition IEditableGraph<StateBase, Transition, BoundaryRoute>.Connect(
    StateBase fromNode, BoundaryRoute fromRoute, StateBase toNode, BoundaryRoute toRoute, Transition existingEdge)
{
    DomNode domNode = new DomNode(Schema.transitionType.Type);
    Transition transition = domNode.As<Transition>();

    transition.FromState = fromNode;
    transition.FromPosition = fromRoute.Position;
    transition.ToState = toNode;
    transition.ToPosition = toRoute.Position;

    if (existingEdge != null)
    {
        Transition existingTransition = existingEdge as Transition;
        transition.Event = existingTransition.Event;
        transition.Guard = existingTransition.Guard;
        transition.Action = existingTransition.Action;
    }

    m_transitions.Add(transition);

    return transition;
}

In this sequence, a DomNode is created of the "transitionType" and adapted to Transition. Its FromState, FromPosition, ToState, and ToPosition properties are set from the method's parameters. If the transition already existed, it is adapted to Transition and the previous Event, Guard, and Action properties copied over. Finally, the new Transition is added to the transition list m_transitions, created in the OnNodeSet() method for EditingContext:

m_transitions = new DomNodeListAdapter<Transition>(DomNode, Schema.statechartDocumentType.transitionChild);

PrototypingContext Class

PrototypingContext is almost identical to PrototypingContext in the FsmEditor and also to Sce.Atf.Controls.Adaptable.Graphs.PrototypingContext, differing only in some DOM adapter names. For a discussion of FsmEditor's PrototypingContext, see PrototypingContext Class. For information on Sce.Atf.Controls.Adaptable.Graphs.PrototypingContext, see PrototypingContext Class.

ViewingContext Class

Again, this context is almost identical to the ViewingContext in FsmEditor. For information on that sample's context, see ViewingContext Class.

Validating StateCharts

SchemaLoader.OnSchemaSetLoaded() defines several validating DOM adapters on the "statechartDocumentType" type:

Schema.statechartDocumentType.Type.Define(new ExtensionInfo<BoundsValidator>());
Schema.statechartDocumentType.Type.Define(new ExtensionInfo<StatechartValidator>());
...
Schema.statechartDocumentType.Type.Define(new ExtensionInfo<UniqueIdValidator>());
Schema.statechartDocumentType.Type.Define(new ExtensionInfo<ReferenceValidator>());
Schema.statechartDocumentType.Type.Define(new ExtensionInfo<LockingValidator>());

All validating DOM adapters are defined on the root element type, so the entire tree of DomNodes is checked.

UniqueIdValidator and ReferenceValidator have pretty much the same aim as in ATF Fsm Editor Sample: to make sure that IDs for states are unique and all references to these IDs are valid. This XML Schema from Statechart.xsd shows that "stateBaseType" has an ID attribute ("xs:ID") and "transitionType" references this ID ("xs:IDREF"):

<!--
A state has a name (id), position, and can be referenced by transitions.
-->
<xs:complexType name ="stateBaseType" abstract="true">
  <xs:attribute name="name" type="xs:ID" use="required" />
  <xs:attribute name="x" type="xs:int" use="required" />
  <xs:attribute name="y" type="xs:int" use="required" />
</xs:complexType>
...
<!--
A transition is a reaction, and connects the source state to the destination state.
The positions of the transition ends are determined by the fromPosition and toPosition
values, which are in the range [0..3].
-->
<xs:complexType name="transitionType">
  <xs:complexContent>
    <xs:extension base="reactionType">
      <xs:attribute name="fromState" type="xs:IDREF" use="required" />
      <xs:attribute name="fromPosition" type="xs:float" />
      <xs:attribute name="toState" type="xs:IDREF" use="required" />
      <xs:attribute name="toPosition" type="xs:float" />
    </xs:extension>
  </xs:complexContent>
</xs:complexType>

UniqueIdValidator guarantees that every StateBase element has a unique ID. ReferenceValidator makes sure that the references to these IDs are valid as the DomNode tree changes by adding and removing states and transitions. For more details on these validator's use in FsmEditor, see Validating State Machines.

BoundsValidator Class

When a child state is moved around inside its container state, the container resizes to accommodate its child state. BoundsValidator derives from Validator and tracks changes to states and updates their bounds to be big enough to hold any child states.

More precisely, BoundsValidator monitors changes to states with various event handlers, such as OnAttributeChanged() and OnChildInserted(). These handlers are called as a result of BoundsValidator being derived from Validator and Observer, which track changes to the tree of DomNodes.

These event handlers may invalidate the layout by setting m_layoutInvalid true to force a redraw when validation is occurring:

protected override void OnAttributeChanged(object sender, AttributeEventArgs e)
{
    if (Validating)
        m_layoutInvalid = true;

    base.OnAttributeChanged(sender, e);
}

Validating is a property of Validator that is set true when a validation context changes.

At the end of the change, OnEnding() is called. If m_layoutInvalid is true, it calls the Layout() method for the entire statechart, which calls the Layout() method for each StateBase in the statechart. This latter Layout() method gets the union of its state's child states' bounds and resizes its state's bounds when necessary.

StatechartValidator Class

StatechartValidator tracks changes to the statechart and enforce constraints on states and transitions. In its OnNodeSet() method, it subscribes to the ChildInserting event, so it can check any newly added DomNode. The event handler checks for various constraint violations, such as trying to create a transition from a final state, raising an InvalidTransactionException in such cases.

LockingValidator Class

LockingValidator is in the namespace Sce.Atf.Dom and not part of StateChartEditor, per se. It derives from Validator and tracks locked data in the tree of DomNodes. Its purpose is to prevent changing locked StateBase objects.

Recall that EditingContext implements ILockingContext, as described in EditingContext Class.

LockingValidator's OnNodeSet() method is:

protected override void OnNodeSet()
{
    m_lockingContext = this.Cast<ILockingContext>(); // required ILockingContext

    // receive notification before attribute changes, to handle changes to lock state
    DomNode.AttributeChanging += OnAttributeChanging;

    base.OnNodeSet();
}

The first line gets an adapter for LockingValidator to the interface ILockingContext; EditingContext is obtained. Because this class is not part of StateChartEditor, it does not know which classes implement ILockingContext, so it uses adaptation to find a suitable object. For details on how this adaptation functions, see Adapting to All Available Interfaces. Support for the ILockingContext interface provided by m_lockingContext is required in several methods to test whether a DomNode is locked, so it can't be edited.

OnNodeSet() subscribes to AttributeChanging, so it can monitor all DomNode attribute changes in the tree; StatechartValidator is defined on the root type, so the whole DomNode tree is checked.

When a change begins, OnBeginning() is called:

protected override void OnBeginning(object sender, EventArgs e)
{
    m_modified = new HashSet<DomNode>();
}

The field m_modified holds a set of all DomNodes modified during a validation event. When events occur, like adding or removing a DomNode, the DomNode is added to the set, as in OnChildInserted():

protected override void OnChildInserted(object sender, ChildEventArgs e)
{
    if (Validating)
        m_modified.Add(e.Child);
}

Finally, OnEnding() is called when the change ends:

protected override void OnEnding(object sender, EventArgs e)
{
    try
    {
        HashSet<DomNode> knownUnlocked = new HashSet<DomNode>();
        List<DomNode> discoveredUnlocked = new List<DomNode>();
        foreach (DomNode modified in m_modified)
        {
            foreach (DomNode node in modified.Lineage)
            {
                if (knownUnlocked.Contains(node))
                    break;
                if (m_lockingContext.IsLocked(node))
                    throw new InvalidTransactionException("item is locked");
                discoveredUnlocked.Add(node);
            }

            foreach (DomNode node in discoveredUnlocked)
                knownUnlocked.Add(node);

            discoveredUnlocked.Clear();
        }
    }
    finally
    {
        m_modified = null;
    }
}

This method iterates through the DomNode set in m_modified and throws InvalidTransactionException if a locked DomNode was modified.

Topics in this section

Clone this wiki locally