Skip to content

Circuit Editor Programming Discussion

Gary edited this page Mar 11, 2015 · 8 revisions

Table of Contents

The ATF Circuit Editor Sample allows constructing circuits by dragging circuit items from the palette onto a document canvas and connecting their pins. You can edit the circuit, including grouping items, as well as creating reusable prototypes and templates from groups of elements. The editor allows collections of items to be added to layers, and the visibility of of layers and their individual items can be toggled.

This sample makes heavy use of the graph interfaces and classes in the Sce.Atf.Controls.Adaptable.Graphs namespace, especially the circuit related ones.

Programming Overview

CircuitEditor shows how to use the graph interfaces and classes in the Sce.Atf.Controls.Adaptable.Graphs namespace. A circuit is simply a generalization of a graph, and this sample implements the IGraph interface with appropriate parameters for circuits.

Graph support requires the ATF DOM, and this application defines its data model using an XML schema, which makes it easier to support the DOM and also makes it easy to persist data in XML.

Much of the function of CircuitEditor comes from the DOM adapters defined for most of the application data types. ATF provides a particularly rich set of DOM adapters for circuit graphs, and CircuitEditor needs to do very little in some cases to derive from these classes.

The Editor class handles circuit editing and is the document client for circuit documents. This client looks very similar to the document client for the ATF Simple DOM Editor Sample. Editor is also the control host client for a D2dAdaptableControl control. Circuits are displayed using the D2dAdaptableControl, employing Direct2D for GPU-accelerated rendering. A set of control adapters is created for each D2dAdaptableControl that performs many functions, including rendering, view changes, and selection.

The CircuitEditingContext is the editing context for containers, such as circuits and groups. Other contexts handle layering and prototypes.

CircuitEditor contains several windows, mainly to handle ways of grouping and reusing controls: Prototypes, Templates, and Layers. Templates and Layers can have folders, which are handled as types with their own DOM adapters. CircuitEditor also provides a Mastering facility for reusable items. Some of these facilities also need their own contexts and command clients.

Graphs and Circuits

A circuit is a graph, and so this sample uses the graphical classes in the Sce.Atf.Controls.Adaptable.Graphs namespace. These classes include ones specifically tailored to circuit graphs, and these classes do most of the work of the sample application. In fact, about half the classes in CircuitEditor are derived from classes of the same name in Sce.Atf.Controls.Adaptable.Graphs, such as Circuit, CircuitEditingContext, and Pin. In this discussion, the class in CircuitEditor is referred to by its name, such as Circuit; the class it derives from in Sce.Atf.Controls.Adaptable.Graphs is referred to as the base class, to avoid having to specify the namespace each time.

In the most general form, ATF supports graphs using nodes, edges, and routes in its IGraph<IGraphNode, IGraphEdge<IGraphNode, IEdgeRoute>, IEdgeRoute> interface:

public interface IGraph<out TNode, out TEdge, out TEdgeRoute>
    where TNode : class, IGraphNode
    where TEdge : class, IGraphEdge<TNode, TEdgeRoute>
    where TEdgeRoute : class, IEdgeRoute

where the elements are:

  • IGraphNode: Interface for a node in a graph; nodes are connected by edges.
  • IGraphEdge<TNode, TEdgeRoute>: Interface for routed edges that connect nodes and have a defined source and destination route from and to the nodes.
  • IEdgeRoute: Interface for edge routes, which act as sources and destinations for graph edges.
The general Sce.Atf.Controls.Adaptable.Graphs.Circuit class is specific to circuits and uses this variant of the IGraph interface:
public abstract class Circuit : DomNodeAdapter, IGraph<Element, Wire, ICircuitPin>, IAnnotatedDiagram, ICircuitContainer

The parameters in the IGraph<Element, Wire, ICircuitPin> interface are:

  • Element: Adapts DomNode to a circuit element with pins. Implements ICircuitElement, which implements IGraphNode.
  • Wire: Adapts DomNode to a connection in a circuit. Implements IGraphEdge<Element, ICircuitPin>.
  • ICircuitPin: Interface for pins, which are the sources and destinations for wires between Elements.
Finally, the CircuitEditor's Circuit class, derived from the above, uses its own IGraph variant:
public class Circuit : Sce.Atf.Controls.Adaptable.Graphs.Circuit, IGraph<Module, Connection, ICircuitPin>

The parameters in the IGraph<Module, Connection, ICircuitPin> interface are:

  • Module: Adapter for circuit modules. Derives from Element.
  • Connection: Adapter for connections in a circuit. Derives from Wire.
  • ICircuitPin: Interface for pins.
For more information about IGraph and other graph interfaces, see ATF Graph Interfaces.

CircuitEditor Data Model

CircuitEditor defines its data model with an XML Schema in the Circuit.xsd type definition file. The DomGen tool is used to generate a Schema class containing metadata classes for the types, as in many of the samples, like ATF Simple DOM Editor Sample. CircuitEditor also defines a SchemaLoader class that uses these metadata classes in a variety of ways, to define DOM adapters, for instance. For general information about data modeling in graphs, see Graph Data Model.

Main Types

Most of CircuitEditor's types have associated classes that are DOM adapters. Thus each item in a circuit is represented by a DomNode in a tree of DomNodes in the application data. The key circuit item classes are:

  • Module ("moduleType"): Represents all the circuit modules found on the palette, such as AND gates. Derives from Sce.Atf.Controls.Adaptable.Graphs.Element.
  • Connection ("connectionType"): A connection between two module pins. Derives from Sce.Atf.Controls.Adaptable.Graphs.Wire.
  • Pin ("pinType"): A pin on a module. Derives from Sce.Atf.Controls.Adaptable.Graphs.Pin.
  • Circuit ("circuitType"): A circuit, containing modules, connections among them, and annotations. Derives from Sce.Atf.Controls.Adaptable.Graphs.Circuit.
  • Group ("groupType"): Collection of modules, connections between them, input and output pins (group pins), and annotations. Derives from Sce.Atf.Controls.Adaptable.Graphs.Group.
Note that these classes all derive from classes in the Sce.Atf.Controls.Adaptable.Graphs namespace.

Circuit and Group both behave as circuit containers, and each container can be displayed separately in a window. A circuit is a (document) top level container, and it can contain any other type of element (including a group), except for another circuit. A group can be a child node of the circuit, and it can contain other group child nodes hierarchically.

Relationships Between Data Types

All the sample's data types are shown in this figure from the Visual Studio XML Schema Explorer, which shows that some types are extensions of others:

Several types are based on the "circuitType", "pinType", and "moduleType" types, whose nodes are open to show their attributes.

In this list, the second level entries are listed under the type they extend:

  • annotationType: comment on the circuit canvas.
  • circuitType: circuit, containing modules, connections among them, and annotations.
    • circuitDocumentType: document holding subcircuits (masters), prototype folders, and template folders.
    • subCircuitType: circuit that defines a master module type, with custom input and output pins and can be referenced by instances, which are all equivalent.
  • connectionType: connects the output pin on the output module to the input pin of the input module.
  • layerFolderType: hierarchy containing layers.
  • moduleRefType: reference to a module in the circuit, used to build lists of module references in a layer.
  • moduleType: module with name, label, and position, and can be referenced by connections.
    • groupTemplateRefType: reference instance to a group.
    • groupType: collection of modules, internal connections between them, input and output pins (group pins), and annotations.
    • missingModuleType: module for external template that is missing.
    • moduleTemplateRefType: reference instance to a module.
    • subCircuitInstanceType: module whose type is defined by a subcircuit (master).
  • pinType: pin with name and type that determines connection type.
    • groupPinType: pin on a group, preserving the internal pin/module which is connected to the outside circuit.
  • prototypeFolderType: hierarchy containing prototypes.
  • prototypeType: set of modules and connections among them that can be copied and pasted into a circuit.
  • templateFolderType: hierarchy containing templates.
  • templateType: module that can be referenced in a circuit.
    • missingTemplateType: module for external template that is missing.

Circuit XML Data

Examining the XML in a circuit file saved from the application illustrates the circuit data types. Here is a circuit graph in CircuitEditor:

Here's is its .circuit file, with some data omitted (...) to clarify the data hierarchy:

<circuit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://sony.com/gametech/circuits/1_0">
    <module xsi:type="buttonType" name="Button_3" label="In1" x="64" y="352" />
    <module xsi:type="andType" name="And_5" x="192" y="352" />
    ...
    <module xsi:type="groupType" name="Group_1" label="Group" x="768" y="128" showExpandedGroupPins="true">
        <input name=":In2" type="boolean" module="And_9" pin="1" />
        <output name=":Out" type="boolean" module="Button_9" />
        <output name=":Out_1" type="boolean" module="And_9" index="1" pinY="64" />
        <module xsi:type="buttonType" name="Button_9" />
        <module xsi:type="andType" name="And_9" x="128" y="64" />
        <module xsi:type="lightType" name="Light_9" x="256" />
        <connection outputModule="And_9" inputModule="Light_9" />
        <connection outputModule="Button_9" inputModule="And_9" />
    </module>
    <connection outputModule="Button_3" inputModule="And_5" />
    <connection outputModule="And_5" inputModule="Light_3" />
    ...
    <layerFolder name="Layer1">
        <layerFolder name="Layer2">
            <moduleRef ref="Button_3" />
            <moduleRef ref="And_5" />
        ...
        </layerFolder>
        <moduleRef ref="Light_7" />
        <moduleRef ref="And_7" />
        <moduleRef ref="Button_7" />
    </layerFolder>
    <annotation text="Try dragging some circuit modules
from the Circuits Tab in the Palette.
    ...
    <annotation text="Complex circuits can be also be simplified
by creating layers. Copy some
    ...
    <subCircuit name="MasterInstance_1">
        <module xsi:type="lightType" name="Light_2" x="272" y="16" />
        <module xsi:type="andType" name="And_2" x="144" y="80" />
    ...
    </subCircuit>
    <prototypeFolder>
        <prototype name="Prototype1">
            <module xsi:type="buttonType" name="Button_5" label="In1" x="64" y="352" />
            <module xsi:type="andType" name="And_3" x="192" y="352" />
            ...
            <connection outputModule="Button_5" inputModule="And_3" />
            <connection outputModule="And_3" inputModule="Light_5" />
            ...
        </prototype>
        <prototype name="Prototype2">
            <module xsi:type="lightType" name="Light_8" x="320" y="64" />
            <module xsi:type="andType" name="And_8" x="192" y="128" />
            ...
            <connection outputModule="And_8" inputModule="Light_8" />
            <connection outputModule="Button_8" inputModule="And_8" />
        </prototype>
    </prototypeFolder>
    <templateFolder name="_TemplateRoot_">
        <template guid="852d657f-c7ef-4474-933f-743737be1b59" label="Group">
            <module xsi:type="groupType" name="Group" label="Group" x="96" y="96" showExpandedGroupPins="true">
                <input name=":In2" type="boolean" module="And_1" pin="1" />
                <output name=":Out" type="boolean" module="Button_1" />
                ...
                <module xsi:type="buttonType" name="Button_1" />
                <module xsi:type="andType" name="And_1" x="128" y="64" />
                ...
                <connection outputModule="Button_1" inputModule="And_1" />
                <connection outputModule="And_1" inputModule="Light_1" />
            </module>
        </template>
    </templateFolder>
</circuit>

The main container is the "circuitDocument" type. Its name is "circuit" and it contains all the other items. In this case, the circuitDocument contains these type items:

  • module
  • connection
  • layerFolder
  • annotation
  • subCircuit
  • prototypeFolder
  • templateFolder

DOM Adapters

As previously mentioned, nearly all the types in CircuitEditor have DOM adapters (missingTemplateType is the only exception). Several types have more than one adapter, such as circuitDocumentType (the root element type) and circuitType.

Some adapters are defined in SchemaLoader.OnSchemaSetLoaded(). These adapters are illustrated in the following table that lists

  • Type.
  • Type's DOM adapter (in CircuitEditorSample unless noted otherwise).
  • Interfaces the adapter implements.
  • Base class the adapter derives from in Sce.Atf.Controls.Adaptable.Graphs (unless indicated otherwise).
  • Class this base derives from (not shown if it's DomNodeAdapter), plus any interfaces this class implements.
These DOM adapters perform a variety of functions. For instance, CategoryUniqueIdValidator ensures locally unique IDs, which is necessary because the template tree has mixed internal and external (imported from other document) nodes, where the node IDs in external documents may collide with the IDs of internal nodes.
Type DOM Adapter Adapter implements Adapter derives from
(its base)
Base derives from,
implements
circuitDocumentType CircuitDocument CircuitDocument Sce.Atf.Dom.DomDocument
circuitDocumentType Sce.Atf.Dom.MultipleHistoryContext Sce.Atf.Dom.Observer
circuitDocumentType PrototypingContext PrototypingContext Sce.Atf.Dom.SelectionContext, IInstancingContext, IPrototypingContext, IObservableContext, INamingContext
circuitDocumentType TemplatingContext Sce.Atf.Dom.TemplatingContext Sce.Atf.Dom.SelectionContext, IInstancingContext, ITemplatingContext, IObservableContext, INamingContext
circuitDocumentType MasteringValidator Sce.Atf.Dom.Validator Sce.Atf.Dom.Observer
circuitDocumentType CategoryUniqueIdValidator Sce.Atf.Dom.CategoryUniqueIdValidator Sce.Atf.Dom.IdValidator
circuitDocumentType CircuitValidator CircuitValidator Sce.Atf.Dom.Validator
circuitDocumentType ReferenceValidator Sce.Atf.Dom.Validator Sce.Atf.Dom.Observer
circuitType GlobalHistoryContext Sce.Atf.Dom.Observer
circuitType ViewingContext IViewingContext, ILayoutContext Sce.Atf.Dom.Validator Sce.Atf.Dom.Observer
circuitType LayeringContext LayeringContext Sce.Atf.Dom.SelectionContext, IInstancingContext, IHierarchicalInsertionContext, ILayeringContext, IObservableContext, INamingContext
circuitType PrintableDocument IPrintableDocument DomNodeAdapter
groupType CircuitEditingContext IEditableGraphContainer<Module, Connection, ICircuitPin> CircuitEditingContext EditingContext, IEnumerableContext, INamingContext, IInstancingContext, IObservableContext, IColoringContext, IEditableGraphContainer<Element, Wire, ICircuitPin>
groupType Group ICircuitGroupType<Module, Connection, ICircuitPin>, IGraph<Module, Connection, ICircuitPin> Group Element, ICircuitGroupType<Element, Wire, ICircuitPin>, IGraph<Element, Wire, ICircuitPin>, IAnnotatedDiagram, ICircuitContainer
groupType ViewingContext IViewingContext, ILayoutContext Validator Sce.Atf.Dom.Observer
subCircuitType SubCircuit SubCircuit Circuit, ICircuitElementType
subCircuitInstanceType SubCircuitInstance SubCircuitInstance Sce.Atf.Controls.Adaptable.Graphs.Element
connectionType WireStyleProvider<Module, Connection, ICircuitPin> IEdgeStyleProvider DomNodeAdapter

Other adapters are defined in SchemaLoader.RegisterCircuitExtensions(), which adapts the default implementation of circuit types for most of the types. These adapters are illustrated in the following table, which has the same format as the preceding table.

Type DOM Adapter Adapter implements Adapter derives from
(its base)
Base derives from,
implements
annotationType Annotation Annotation IAnnotation
circuitType Circuit IGraph<Module, Connection,
ICircuitPin>
Circuit IGraph<Element, Wire, ICircuitPin>,
IAnnotatedDiagram, ICircuitContainer
circuitType CircuitEditing-
Context
IEditableGraphContainer
<Module, Connection, ICircuitPin>
CircuitEditingContext EditingContext,
IEnumerableContext,
INamingContext,
IInstancingContext,
IObservableContext,
IColoringContext,
IEditableGraphContainer
<Element, Wire,
ICircuitPin>
connectionType Connection IGraphEdge<Module,
ICircuitPin>
Wire IGraphEdge<Element, ICircuitPin>
groupPinType GroupPin ICircuitGroupPin<Module> GroupPin Pin,
ICircuitGroupPin<Element>
groupTemplateRefType GroupInstance ICircuitGroupType
<Module,
Connection, ICircuitPin>,
IReference<Module>,
IReference<DomNode>,
IReference
<Sce.Atf.Controls.
Adaptable.
Graphs.Group>
CircuitEditorSample.Module
groupType Group ICircuitGroupType
<Module, Connection,
ICircuitPin>,
IGraph
<Module, Connection,
ICircuitPin>
Group Element,
ICircuitGroupType
<Element, Wire, ICircuitPin>,
IGraph<Element, Wire, ICircuitPin>,
IAnnotatedDiagram, ICircuitContainer
layerFolderType LayerFolder LayerFolder
moduleRefType ModuleRef ElementRef
moduleTemplateRefType ModuleInstance IReference<Module>,
IReference<DomNode>
CircuitEditorSample.Module
moduleType Module Element ICircuitElement, IVisible
pinType Pin Pin ICircuitPin
prototypeFolderType PrototypeFolder PrototypeFolder
prototypeType Prototype Prototype
subCircuitType SubCircuit SubCircuit Circuit,
ICircuitElementType
subCircuitInstanceType SubCircuit-
Instance
SubCircuitInstance Element
templateFolderType TemplateFolder Sce.Atf.Dom.TemplateFolder
templateType Template Sce.Atf.Dom.Template IReference<DomNode>

All the derived from classes are in Sce.Atf.Controls.Adaptable.Graphs, except as noted. All these base classes derive directly from DomNodeAdapter, except for CircuitEditingContext, which derives from EditingContext, which ultimately derives from DomNodeAdapter. For more information on the DOM adapter base classes, see Circuit DOM Adapters.

Functions of DOM Adapters

What do these DOM adapters do?

Override Base Class Properties

DOM adapters typically implement properties containing information about the object.

For instance, the Annotation DOM adapter derives from Sce.Atf.Controls.Adaptable.Graphs.Annotation, which defines these properties:

// required  DOM attributes info
protected abstract AttributeInfo TextAttribute { get; }
protected abstract AttributeInfo XAttribute { get; }
protected abstract AttributeInfo YAttribute { get; }
protected abstract AttributeInfo WidthAttribute { get; }
protected abstract AttributeInfo HeightAttribute { get; }
protected abstract AttributeInfo BackColorAttribute { get; }

These properties provide the kind of information needed for an annotation, such as its location and text. These properties are all overridden in Annotation, typically using the data in the DOM metadata classes defined in Schema, as in:

protected override AttributeInfo TextAttribute
{
    get { return Schema.annotationType.textAttribute; }
}

Nearly all CircuitEditor's DOM adapters override base class properties in this way. Half the adapters, such as Annotation, LayerFolder, Module, and Pin, do nothing more than this.

Call OnNodeSet Method

Every DOM adapter has an OnNodeSet() method. Most of the DOM adapters in CircuitEditor use their base class's OnNodeSet(), but some of the DOM adapters provide their own, such as Group:

protected override void OnNodeSet()
{
    m_modules = new DomNodeListAdapter<Module>(DomNode, Schema.groupType.moduleChild);
    m_connections = new DomNodeListAdapter<Connection>(DomNode, Schema.groupType.connectionChild);
    m_annotations = new DomNodeListAdapter<Annotation>(DomNode, Schema.groupType.annotationChild);
    m_inputs = new DomNodeListAdapter<GroupPin>(DomNode, Schema.groupType.inputChild);
    m_outputs = new DomNodeListAdapter<GroupPin>(DomNode, Schema.groupType.outputChild);
    m_thisModule = DomNode.Cast<Module>();

    base.OnNodeSet();
}

The types used in this method are all the types that can be children of a "groupType" DomNode. This OnNodeSet() method saves a DomNodeListAdapter list of all the DomNode's child objects. The Circuit DOM adapter does the same for its child objects in its OnNodeSet() method.

Implement Interfaces

For example, the Circuit DOM adapter implements IGraph:

#region IGraph Members

/// <summary>
/// Gets all visible nodes in the circuit</summary>
///<remarks>IGraph.Nodes is called during circuit rendering, and picking(in reverse order). </remarks>
IEnumerable<Module> IGraph<Module, Connection, ICircuitPin>.Nodes
{
    get
    {
        return m_modules.Where(x => x.Visible);
    }
}

/// <summary>
/// Gets all connections between visible nodes in the circuit</summary>
IEnumerable<Connection> IGraph<Module, Connection, ICircuitPin>.Edges
{
    get
    {
        return m_connections.Where(x => x.InputElement.Visible && x.OutputElement.Visible);
    }
}
...

Circuit Documents

CircuitEditor's document class is CircuitDocument, which merely overrides the SubCircuitChildInfo property in its derived class Sce.Atf.Controls.Adaptable.Graphs.CircuitDocument. This base class, in turn, derives from DomDocument, which implements IDocument. This base class is also a DOM adapter, and it implements an OnNodeSet() method to initialize the editor type and other information.

Several samples implement IDocument through DomDocument, including ATF Fsm Editor Sample, ATF Simple DOM Editor Sample, and ATF State Chart Editor Sample. For details on how the ATF Simple DOM Editor Sample uses DomDocument, see EventSequenceDocument Class, which is the document class in that sample.

Circuit Document Display and Editing

The Editor class handles circuit editing and is the document client for circuit documents. Editor is also the control host client for a D2dAdaptableControl control, which displays circuit documents.

Circuit Document Client

CircuitEditor's implementation of IDocumentClient has a lot of similarities to the document client of ATF Simple DOM Editor Sample, which is discussed in Document Handling.

The implementations of IDocumentClient are very similar in the two samples. For example, here are the CircuitEditor Info property and CanOpen() method:

public DocumentClientInfo Info
{
    get { return EditorInfo; }
}

/// <summary>
/// Document editor information for circuit editor</summary>
public static DocumentClientInfo EditorInfo =
    new DocumentClientInfo(Localizer.Localize("Circuit"), ".circuit", null, null);

/// <summary>
/// Returns whether the client can open or create a document at the given URI</summary>
/// <param name="uri">Document URI</param>
/// <returns>True iff the client can open or create a document at the given URI</returns>
public bool CanOpen(Uri uri)
{
    return EditorInfo.IsCompatibleUri(uri);
}

And the same property and method for SimpleDOMEditor:

public DocumentClientInfo Info
{
    get { return DocumentClientInfo; }
}

/// <summary>
/// Information about the document client</summary>
public static DocumentClientInfo DocumentClientInfo = new DocumentClientInfo(
    Localizer.Localize("Event Sequence"),
    new string[] { ".xml", ".esq" },
    Sce.Atf.Resources.DocumentImage,
    Sce.Atf.Resources.FolderImage,
    true);

/// <summary>
/// Returns whether the client can open or create a document at the given URI</summary>
/// <param name="uri">Document URI</param>
/// <returns>True iff the client can open or create a document at the given URI</returns>
public bool CanOpen(Uri uri)
{
    return DocumentClientInfo.IsCompatibleUri(uri);
}

The IDocumentClient.Open() method also has many commonalities in the two samples. For instance, both use DomXmlReader to read the file and create a tree of DomNodes, because both samples use the ATF DOM for data persistence. Both set up an editing context derived from the EditingContext class. A notable difference is that CircuitEditor's Open() method creates a D2dAdaptableControl to display circuits, as discussed in Circuit Document Display and Control Adapters.

The IDocumentClient.Show(), IDocumentClient.Save(), and IDocumentClient.Close() methods are all very similar in the two samples. Both samples' Save() methods use DomXmlWriter to write the document to XML, although CircuitEditor does this through a private class CircuitWriter derived from DomXmlWriter.

Circuit Document Display and Control Adapters

Circuits are displayed using a D2dAdaptableControl employing Direct2D for GPU-accelerated rendering. A set of control adapters is created for each D2dAdaptableControl.

Control adapters reside in several namespaces. Some are not circuit or even graph specific, such as HoverAdapter.

Although a typical circuit control has more than a dozen control adapters, each adapter has well defined, unique responsibilities. The adapters' code is relatively short and easy to follow and extend. Divide and conquer is a good strategy here. Note that:

  • Viewing related operations (pan and zoom) use ITransformAdapter, implemented in TransformAdapter.
  • Mouse click and drag selection use ISelectionAdapter, implemented in SelectionAdapter.
  • Rendering, picking and selection is delegated to a circuit rendering class, such as D2dCircuitRenderer, which is a parameter in the D2dGraphAdapter constructor. For information on circuit renderers, see Circuit Rendering Classes.
Client applications can mostly use the standard control adapters provided in ATF, occasionally extending a few adapters. CircuitEditor uses the adapters as is.

A D2dAdaptableControl is used as a canvas for displaying a main graph (circuit) or a subgraph (group, or master of a sub-circuit) in:

  • A circuit document.
  • A subgraph displayed in a tab after double-clicking on a group or master instance.
  • A subgraph displayed when hovering over a master instance.
The first two D2dAdaptableControl controls are in tab windows, are fully editable, and are created by the CreateCircuitControl() method. The D2dAdaptableControl for the hover is created in Editor's constructor:
m_d2dHoverControl = new D2dAdaptableControl();
m_d2dHoverControl.Dock = DockStyle.Fill;
var xformAdapter = new TransformAdapter();
xformAdapter.EnforceConstraints = false;//to allow the canvas to be panned to view negative coordinates
m_d2dHoverControl.Adapt(xformAdapter, new D2dGraphAdapter<Module, Connection, ICircuitPin>(m_circuitRenderer, xformAdapter));
m_d2dHoverControl.DrawingD2d += new EventHandler(m_d2dHoverControl_DrawingD2d);

Here the D2dAdaptableControl is created and configured, adapters are created and configured, and then AdaptableControl.Adapt() sets the D2dAdaptableControl's adapters. This process is also performed in CreateCircuitControl() with many more adapters.

Editor's constructor also creates two objects that are used later in the D2dAdaptableControl's adapters created in CreateCircuitControl() to render circuits:

  • D2dCircuitRenderer: Graph renderer that draws graph nodes as circuit elements, and edges as wires. Elements have zero or more output pins where wires originate, and zero or more input pins where wires end. Input pins are on the left side and output pins are on the right of elements. For more information, see D2dCircuitRenderer Class.
  • D2dSubCircuitRenderer: Subgraph (group or master) renderer that draws subnodes as circuit elements, and subedges as wires. Also draws virtual representations of group pins for editing. For more details, see D2dSubCircuitRenderer Class.
The rest of this section explores the CreateCircuitControl() method, because D2dAdaptableControl is central to the sample's operation.

For general information on adaptation, including adapting controls, see Adaptation in ATF and Adaptable Controls.

Control Adapter Creation and Configuration

After the D2dAdaptableControl is created and configured, its adapters are created. These adapters all derive from ControlAdapter, the base class for control adapters.

internal D2dAdaptableControl CreateCircuitControl(DomNode circuitNode)
{
    var control = new D2dAdaptableControl();
    control.SuspendLayout();
    control.BackColor = SystemColors.ControlLight;
    control.AllowDrop = true;

    var transformAdapter = new TransformAdapter();
    transformAdapter.EnforceConstraints = false; //to allow the canvas to be panned to view negative coordinates
    transformAdapter.UniformScale = true;
    transformAdapter.MinScale = new PointF(0.25f, 0.25f);
    transformAdapter.MaxScale = new PointF(4, 4);
    var viewingAdapter = new ViewingAdapter(transformAdapter);
    viewingAdapter.MarginSize = new Size(25, 25);
    var canvasAdapter = new CanvasAdapter();
    ((ILayoutConstraint) canvasAdapter).Enabled = false; //to allow negative coordinates for circuit elements within groups

    var autoTranslateAdapter = new AutoTranslateAdapter(transformAdapter);
    var mouseTransformManipulator = new MouseTransformManipulator(transformAdapter);
    var mouseWheelManipulator = new MouseWheelManipulator(transformAdapter);
    var scrollbarAdapter = new ScrollbarAdapter(transformAdapter, canvasAdapter);

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

    var annotationAdaptor = new D2dAnnotationAdapter(m_theme); // display annotations under diagram

These adapters allow the control to be used in all sorts of ways:

  • TransformAdapter: implement ITransformAdapter for scaling and translating the control's contents, and is also needed by several other adapters. This adapter is configured by setting its properties. EnforceConstraints is false to allow the canvas to be panned to view negative coordinates. UniformScale is set true to guarantee that the canvas is scaled the same on both the x- and y-axis. MinScale and MaxScale enforce a minimum and maximum scaling. MarginSize indicates the margin used when framing a selection.
  • ViewingAdapter: implement methods in IViewingContext to frame the control's items and ensure their visibility.
  • CanvasAdapter: allow setting the canvas bounds and visible window size. Seeting Enabled false allows negative coordinates for circuit elements within groups.
  • MouseTransformManipulator: convert mouse drags into translation and scaling the canvas using the TransformAdapter. This allows panning items on the canvas with Alt+Left button mouse dragging and scaling with Alt+Right button mouse dragging.
  • MouseWheelManipulator: convert mouse wheel rotation into scaling using the TransformAdapter.
  • ScrollbarAdapter: add horizontal and vertical scrollbars to the adapted control, using the TransformAdapter and CanvasAdapter.
  • HoverAdapter: add hover events over pickable items. This allows seeing the items inside a master element in a window when hovering over the master. The adapter subscribes to HoverStarted and HoverStopped events.
  • D2dAnnotationAdapter: display and edit annotations on the control.
In addition, the following adapters are used and instantiated in the call to AdaptableControl.Adapt():
  • RectangleDragSelector: allow user to drag-select rectangular regions on the adapted control to modify the selection.
  • KeyboardIOGraphNavigator: navigate an "input-output" graph using the arrow keys. This kind of graph has inputs on only one side of a node and outputs on another side, like a circuit.
  • LabelEditAdapter: edit element labels in place.
  • SelectionAdapter: add mouse click and drag selection of canvas items. The context must be convertible to ISelectionContext.
  • DragDropAdapter: add drag and drop support for dragging circuit elements onto the canvas from the palette, prototype window, or template window.
  • ContextMenuAdapter: support a context menu on right button mouse-up on circuit elements.

Circuit Display

CreateCircuitControl()'s DomNode parameter indicates what kind of circuit is being displayed: Circuit for an open circuit document, or Group if a group was double-clicked on. The control functions slightly differently in these cases, defining different additional adapters. For a circuit:

if (circuitNode.Is<Circuit>())
    {
        var circuitAdapter = new D2dGraphAdapter<Module, Connection, ICircuitPin>(m_circuitRenderer, transformAdapter);
        var circuitModuleEditAdapter = new D2dGraphNodeEditAdapter<Module, Connection, ICircuitPin>(
            m_circuitRenderer, circuitAdapter, transformAdapter);
        circuitModuleEditAdapter.DraggingSubNodes = false;

        var circuitConnectionEditAdapter =
            new D2dGraphEdgeEditAdapter<Module, Connection, ICircuitPin>(m_circuitRenderer, circuitAdapter, transformAdapter);

        control.Adapt(
            hoverAdapter,
            scrollbarAdapter,
            autoTranslateAdapter,
            new RectangleDragSelector(),
            transformAdapter,
            viewingAdapter,
            canvasAdapter,
            mouseTransformManipulator,
            mouseWheelManipulator,
            new KeyboardIOGraphNavigator<Module, Connection, ICircuitPin>(),
            new D2dGridAdapter(),
            annotationAdaptor,
            circuitAdapter,
            circuitModuleEditAdapter,
            circuitConnectionEditAdapter,
            new LabelEditAdapter(),
            new SelectionAdapter(),
            new DragDropAdapter(m_statusService),
            new ContextMenuAdapter(m_commandService, m_contextMenuCommandProviders)
            );
    }

The additional adapters are:

  • D2dGraphAdapter: provide support for graph drawing, viewing, and hit testing on the canvas.
  • D2dGraphNodeEditAdapter: add node dragging to move circuit elements. This adapter is instantiated with a D2dCircuitRenderer. Pressing the Shift key constrains dragging to be parallel to the x- or y-axis. Its DraggingSubNodes property is set false to prevent moving subnodes.
  • D2dGraphEdgeEditAdapter: add graph edge editing capabilities to make and move connections between pins in the circuit. This adapter is created with a D2dCircuitRenderer.

Group Display

This portion of code runs when creating a control for a Group:

else if (circuitNode.Is<Group>())
    {
        var circuitAdapter = new D2dSubgraphAdapter<Module, Connection, ICircuitPin>(m_subGraphRenderer,
                                                                                transformAdapter);
        var circuitModuleEditAdapter = new D2dGraphNodeEditAdapter<Module, Connection, ICircuitPin>(
            m_subGraphRenderer, circuitAdapter, transformAdapter);
        circuitModuleEditAdapter.DraggingSubNodes = false;

        var circuitConnectionEditAdapter =
            new D2dGraphEdgeEditAdapter<Module, Connection, ICircuitPin>(m_subGraphRenderer, circuitAdapter, transformAdapter);

        var groupPinEditor = new GroupPinEditor(transformAdapter);
        groupPinEditor.GetPinOffset = m_subGraphRenderer.GetPinOffset;

        canvasAdapter.UpdateTranslateMinMax = groupPinEditor.UpdateTranslateMinMax;

        control.Adapt(
            hoverAdapter,
            scrollbarAdapter,
            autoTranslateAdapter,
            new RectangleDragSelector(),
            transformAdapter,
            viewingAdapter,
            canvasAdapter,
            mouseTransformManipulator,
            mouseWheelManipulator,
            new KeyboardIOGraphNavigator<Module, Connection, ICircuitPin>(),
            new D2dGridAdapter(),
            annotationAdaptor,
            circuitAdapter,
            circuitModuleEditAdapter,
            circuitConnectionEditAdapter,
            new LabelEditAdapter(),
            groupPinEditor,
            new SelectionAdapter(),
            new DragDropAdapter(m_statusService),
            new ContextMenuAdapter(m_commandService, m_contextMenuCommandProviders)
            );
    }
    else throw new NotImplementedException(
        "graph node type is not supported!");

Additional adapters for a Group are:

  • D2dSubgraphAdapter: reference and render a subgraph, that is, a group diagram. Also provides hit testing and viewing support on the canvas.
  • D2dGraphNodeEditAdapter: add node dragging capabilities to move circuit elements. This object is instantiated with a D2dSubCircuitRenderer. Pressing the Shift key constrains dragging to be parallel to the x- or y-axis. DraggingSubNodes is false to prevent moving subnodes.
  • D2dGraphEdgeEditAdapter: add graph edge editing capabilities to make and move connections between pins in the circuit. This adapter is instantiated with a D2dSubCircuitRenderer.
  • GroupPinEditor: add floating group pin location and label editing capabilities to a subgraph control. The GetPinOffset property is set to a callback from D2dSubCircuitRenderer to compute the group pin y offset.

Completion

CreateCircuitControl() finishes up with this:

control.ResumeLayout();

    control.DoubleClick += new EventHandler(control_DoubleClick);
    control.MouseDown += new MouseEventHandler(control_MouseDown);
    return control;
}

The DoubleClick event handler displays a window when a group or master is double-clicked. It first ascertains whether the hit element is a subcircuit (master) or group.

If the clicked on item is a master, a new control is created to display the content of the master. It creates an editing context in which the schema loader is specified, to enable copying and pasting to other applications. It creates a ControlInfo for the new control and registers it with the CircuitControlRegistry. For information about this component, see CircuitControlRegistry Component.

If the clicked on item is a group, the process is similar, but a document is not created.

In both cases, CreateCircuitControl() itself is called to create a control to display the circuit elements.

The MouseDown handler does different things, depending on which part of a circuit element was clicked:

  • Group expander button: toggle the group contents' visibility.
  • Pin: allow dragging a connection to another pin without having to keep the mouse button pressed.
  • Group pin visibility icon ( ): toggle the group pin's visibility.
All these mouse down actions are performed as transactions, in an ITransactionContext adapted from the DomNode tree's root DomNode, ultimately provided by the CircuitEditingContext, described in CircuitEditingContext Class.

Context Classes

CircuitEditor uses several context classes, most of which are derived from Sce.Atf.Controls.Adaptable.Graphs classes.

An editing context is usually associated with a container (i.e., a circuit or group), so editing is constrained to items in the container and currently selected items. The current active context is usually associated with the current active window in the user interface. For instance, when the circuit canvas is the active window, the CircuitEditingContext is the active context.

CircuitEditingContext Class

CircuitEditingContext derives from Sce.Atf.Controls.Adaptable.Graphs.CircuitEditingContext, which provides all its function. The base class cannot be used directly, because it is abstract. This base class itself derives from EditingContext, which implements ISelectionContext to enable selecting circuit elements. This base class also implements several interfaces, including IInstancingContext to allow editing selected items. For information on the base CircuitEditingContext class, see CircuitEditingContext Class in Circuit Graph Support.

A circuit and group both behave as containers, and each can be displayed and edited separately in a graph window. Therefore, both "circuitType" and "groupType" have the DOM adapter CircuitEditingContext defined in SchemaLoader.

CircuitEditingContext contains the property WireType that must be overridden, which CircuitEditor does in the usual fashion:

protected override DomNodeType WireType
{
    get { return Schema.connectionType.Type; }
}

CircuitEditingContext implements IEditableGraphContainer<Module, Connection, ICircuitPin> to allow editing an expanded group by dragging items in and out of the group. The methods in this implementation simply call methods in the base CircuitEditingContext's IEditableGraphContainer<Element, Wire, ICircuitPin> implementation.

For example, here is the CircuitEditingContext implementation of IEditableGraphContainer.CanMove()

bool IEditableGraphContainer<Module, Connection, ICircuitPin>.CanMove(object newParent, IEnumerable<object> movingObjects)
{
    if (newParent.Is<IReference<Module>>())
        return false;
    var editableGraphContainer =
        DomNode.Cast<CircuitEditingContext>() as IEditableGraphContainer<Element, Wire, ICircuitPin>;
    return editableGraphContainer.CanMove(newParent, movingObjects);
}

The editableGraphContainer variable is an instance of the base class, Sce.Atf.Controls.Adaptable.Graphs.CircuitEditingContext, because this base class implements IEditableGraphContainer<Element, Wire, ICircuitPin>. Thus the next line's call editableGraphContainer.CanMove(newParent, movingObjects) is to the CanMove() method in the base class.

The other methods that CircuitEditingContext implements in IEditableGraphContainer<Module, Connection, ICircuitPin> also use the IEditableGraphContainer<Element, Wire, ICircuitPin> methods in the base class.

IEditableGraphContainer also derives from IEditableGraph, which governs making graph connections:

public interface IEditableGraphContainer<in TNode, TEdge, in TEdgeRoute>:
    IEditableGraph<TNode, TEdge, TEdgeRoute>
    where TNode : class, IGraphNode
    where TEdge : class, IGraphEdge<TNode, TEdgeRoute>
    where TEdgeRoute : class, IEdgeRoute

CircuitEditingContext also implements this interface, again by calling the corresponding methods in the base class. For example, here is IEditableGraph.CanConnect():

public bool CanConnect(Module fromNode, ICircuitPin fromRoute, Module toNode, ICircuitPin toRoute)
{
    var editableGraphContainer =
        DomNode.Cast<CircuitEditingContext>() as IEditableGraphContainer<Element, Wire, ICircuitPin>;
    return editableGraphContainer.CanConnect(fromNode, fromRoute, toNode, toRoute);
}

This method's code is very similar to IEditableGraphContainer.CanMove() shown previously: the editableGraphContainer variable is created exactly the same way. As a result, this method calls the IEditableGraph.CanConnect() method in the base CircuitEditingContext class.

CircuitEditingContext is the DOM adapter defined for both the "circuit" and "group" types. CircuitEditor allows you to open multiple separate windows to view a group, in addition to the main document (circuit) window. Each opened graph window has an associated editing context with its own undo history. However, GlobalHistoryContext is also defined as a DOM adapter for the "circuit" type. GlobalHistoryContext is an adapter that implements a single global history on a DOM node tree containing multiple local HistoryContexts. This adapter tracks all other HistoryContexts in the subtree rooted at DomNode and passes their transactions to a global HistoryContext, which must be on the same DomNode as this adapter. As a result, there is only one undo/redo history stack for all circuits, including groups.

LayeringContext and PrototypingContext Classes

Both classes derive from classes of the same name in Sce.Atf.Controls.Adaptable.Graphs. These classes simply override properties in the base class to get type metadata from the Schema class. For details on what these base context classes do, see the sections PrototypingContext_Class_in_Circuit_Graph_Support._For_details_on_how_prototyping_works_in_Circuit_Editor,_see_Prototype_Handling and PrototypingContext Class in Circuit Graph Support. For details on how prototyping works in CircuitEditor, see [Prototype]].

TemplatingContext Class

TemplatingContext derives from Sce.Atf.Dom.TemplatingContext. It is the editing context for the templates library, and is the context bound to the TemplateLister component when a circuit document becomes the active context. For details on working with templates in CircuitEditor, see Template Handling.

Reusable Circuit Facilities and Windows

CircuitEditor has several windows that handle ways of grouping and reusing controls, such as the Prototypes window. Mastering also provides re-usability, although masters are not listed in a window. The Layering window resembles the Templates window, but provides a mechanism for layering circuits and controlling their visibility.

Prototype Handling

You create a prototype by copying circuit items and pasting them into the Prototypes window. The prototype can then be dragged back onto the canvas.

CircuitEditor does very little itself to implement prototyping. As with many of the other classes in CircuitEditor, its prototype classes all derive from classes with the same name in the Sce.Atf.Controls.Adaptable.Graphs namespace. These classes are all DOM adapters and override properties in the base class to get type metadata from the Schema class:

  • Prototype: Circuit prototype, which contains Elements and Wires that can be copied into a circuit.
  • PrototypeFolder: Folder of Prototypes.
  • PrototypingContext: Editing context for prototypes in the circuit document. For a discussion of this context's base class, see PrototypingContext Class.
DOM adapters, such as Prototype, are discussed in DOM Adapters.

The PrototypeLister component completes the prototype implementation, providing a tree control in which to list prototypes. CircuitEditor simply includes PrototypeLister in its MEF TypeCatalog.

Template Handling

Templates are similar to prototypes in that both allow saving parts of a circuit and organizing these items in a window with a tree control, from which the item can be dragged back onto the circuit canvas. The two differ in what can be copied and how it is copied: a template can be made only from module or group using a menu item. A template may also reference a module or group in an external file. (Provision is made for these files being missing.) Note that promoting a module or group to a template changes the selected item into a template. In addition, changing one instance of a template changes them all.

Like prototypes, most of the template classes derive from classes by the same name in another namespace: Sce.Atf.Dom in this case. These classes are all DOM adapters.

The TemplateLister component provides a tree control in which to list templates. CircuitEditor simply includes TemplateLister in its MEF TypeCatalog.

Template and TemplateFolder Classes

Template adapts a template, containing information about the group or module in the template. The Template and TemplateFolder DOM adapters are discussed briefly in DOM Adapters. The Template base class contains several abstract properties that describe the template and must be overridden. For instance, the Name property:

public override string Name
{
    get { return (string)DomNode.GetAttribute(Schema.templateType.labelAttribute); }
    set { DomNode.SetAttribute(Schema.templateType.labelAttribute, value); }
}

In the usual fashion of such overrides, the information is obtained from the data model's metadata classes in the Schema class.

There is also a Guid property with a GUID identifying the template:

public override Guid Guid
{
    get { return new Guid((string)DomNode.GetAttribute(Schema.templateType.guidAttribute)); }
    set { DomNode.SetAttribute(Schema.templateType.guidAttribute, value.ToString()); }
}

TemplateFolder has a similar, simple implementation, overriding the properties in the base TemplateFolder class, getting information from the metadata classes.

GroupInstance and ModuleInstance Classes

A template instance references a module or group, so the data model defines two types: "moduleTemplateRefType" and "groupTemplateRefType". If a circuit contains a template instance, the DomNode for the instance in the application data is a module with a "moduleTemplateRefType" or "groupTemplateRefType" type.

GroupInstance and ModuleInstance represent instances of a template. They are actually implemented in CircuitEditor and both derive from Module. These classes are DOM adapters for the template data types:

Schema.moduleTemplateRefType.Type.Define(new ExtensionInfo<ModuleInstance>());
Schema.groupTemplateRefType.Type.Define(new ExtensionInfo<GroupInstance>());

Both classes implement IReference<Module> and IReference<DomNode>. IReference has a method and a property:

  • CanReference(): Can the given DomNode be referenced, that is, is it of the right type?
  • Target: Actual target Module of the reference.
TemplatingContext uses GroupInstance or ModuleInstance to create a template instance for a group or module.

Keep in mind that these instances reference a module or group. The information for the circuit items in a template module instance resides in the template itself, not in the template instance.

ProxyGroup Class

ProxyGroup delegates communications with the targeted group on behalf of a GroupInstance. ProxyGroup's constructor is

public ProxyGroup(GroupInstance owner, Group target)

and ProxyGroup is constructed by GroupInstance in its ProxyGroup property:

m_proxyGroup = new ProxyGroup(this, Target.As<Group>());

so the ProxyGroup is constructed using the underlying Group the group template instance refers to. ProxyGroup handles functions for a group template instance that are different from a regular group.

TemplatingCommands Component

TemplatingCommands is a command client that provides these commands:

  • Add Template Folder: Add a template folder to the Templates window.
  • Import Templates from Document...: Import templates from an external .circuit document.
  • Promote To Template Library: Make the selected module a template, copying it to the Templates window. The selected item is copied and through the TemplatingContext's Insert() method, added to the list of templates. The original item is replaced by a template instance.
  • Demote To Copy Instance: Make the selected template instance a module that is no longer a template.
The base TemplatingCommands sets up the commands, implements ICommandClient, and implements the command to add a template folder.

The TemplatingCommands class in CircuitEditor implements the promote and demote commands, which is the bulk of the work for templating.

TemplatingContext Class

TemplatingContext provides the editing context for the Templates window. The base class's definition is:

public abstract class TemplatingContext : SelectionContext,
    IInstancingContext,
    ITemplatingContext,
    IObservableContext,
    INamingContext

SelectionContext is a DOM adapter that implements ISelectionContext to allow selecting templates in the Templates window. The other interfaces perform a similar role to the interfaces implemented for PrototypingContext. For a discussion of these, see PrototypingContext Class in Circuit Graph Support.

ITemplatingContext, similarly to IPrototypingContext, helps present a tree view of the templates and creates IDataObject instances from template items. ITemplatingContext also checks to see if a reference can reference the specified target item and actually get the reference. ITemplatingContext's implementation is split between the TemplatingContext class and its base. The main work is done by the non-base TemplatingContext, which implements the referencing methods, CanReference() and CreateReference(). CreateReference() creates a new GroupInstance or ModuleInstance as a template instance for a group or module.

TemplatingContext also gets notified if a template is removed and turns any template references into instances in that case.

Master Handling

Mastering is similar to templating, in that it creates a master from selected circuit items, that is, a subcircuit, creating a new type of circuit element. Any changes to this master element affect all instances of it. Mastering differs from templating in that any selection of items in the circuit can be converted to a master, not just a module or group. The master appears as a module displaying whatever pins were not internally connected. There is no Mastering window.

Mastering is implemented almost entirely in CircuitEditor, except for the SubCircuit and SubCircuitInstance classes, which are DOM adapters derived from classes of the same name in Sce.Atf.Controls.Adaptable.Graphs.

SubCircuit Class

SubCircuit adapts a DomNode to a mastered subcircuit. Like many DOM adapters in CircuitEditor, it simply overrides the base class's properties, getting information from the Schema class's metadata classes.

SubCircuitInstance Class

SubCircuitInstance adapts a DomNode to an instance of a mastered subcircuit. Like SubCircuit, it simply overrides the base class's properties, getting information from the Schema class's metadata classes.

Note that SubCircuitInstance derives from Element — not Circuit as the SubCircuit DOM adapter does. A SubCircuitInstance represents a single element in a circuit, whereas a SubCircuit represents one or more elements in a circuit. These types have adapters defined in this way:

Schema.subCircuitType.Type.Define(new ExtensionInfo<SubCircuit>());
Schema.subCircuitInstanceType.Type.Define(new ExtensionInfo<SubCircuitInstance>());

The Editor component uses SubCircuitInstance when creating an adaptable control for a master subcircuit instance, either when the master is double-clicked or hovered over.

MasteringCommands Component

MasteringCommands is a command client implementing these commands under the Edit menu:

  • Master: Create a custom module type from the selection.
  • Unmaster: Expand the last selected custom module into the original circuit elements, no longer a master instance.
The mastering process entails copying all the selected circuit elements to a master copy, removing the original selected items corresponding DomNodes, setting up the pins correctly for the new master, and then creating a DomNode for the master module, represented by a SubCircuitInstance DomNode. The canvas is updated accordingly. Unmastering is a simpler process, simply removing the existing master DomNode and creating a new list of DomNodes from copies of the master's items.

MasteringValidator Class

MasteringValidator tracks changes to subcircuits and subcircuit instances in the document and throws an InvalidTransactionException for certain errors. MasteringValidator is a DOM adapter that is defined for the circuit document type in SchemaLoader:

Schema.circuitDocumentType.Type.Define(new ExtensionInfo<MasteringValidator>());        // validates sub-circuits

The allows the validator to track changes throughout the entire circuit document, that is, whenever the circuit changes. The OnNodeSet() subscribes to changes to the DomNode tree:

protected override void OnNodeSet()
{
    base.OnNodeSet();
    DomNode.ChildInserting += DomNodeChildInserting;
    DomNode.ChildRemoving += DomNodeOnChildRemoving;
}

These event handlers show examples of the kind of circuit checking that can be done. For instance:

private void DomNodeChildInserting(object sender, ChildEventArgs e)
{
    if (Validating)
    {
        // inserting an instance of a sub-circuit into itself?
        SubCircuitInstance subCircuitInstance = e.Child.As<SubCircuitInstance>();
        SubCircuit subCircuit = e.Parent.As<SubCircuit>();
        if (subCircuitInstance != null &&
            subCircuit != null &&
            subCircuitInstance.SubCircuit == subCircuit)
        {
            throw new InvalidTransactionException(
                "Can't use a sub-circuit inside itself".Localize());
        }
    }
}

Attempting to insert a SubCircuitInstance that is an instance of the SubCircuit into the SubCircuit fails.

Layer Handling

You can copy and paste circuit items into a layer to control their visibility. The layering classes in CircuitEditor all derive from classes of the same name (except for ModuleRef) in the Sce.Atf.Controls.Adaptable.Graphs namespace that provide their functionality. These classes are all DOM adapters:

  • LayerFolder: Adapter for layer folders.
  • LayeringCommands: Component for layering commands. For details, see LayeringCommands Component.
  • LayeringContext: Context for layering. For details on this context, see LayeringContext Class.
  • ModuleRef: Derives from Sce.Atf.Controls.Adaptable.Graphs.ElementRef and is used within layer folders to represent circuit modules that belong to that layer.
CircuitEditor also imports the LayerLister component, which uses the LayeringContext to present a tree view of the layers with a TreeControl.

Miscellaneous Classes

These classes provide a variety of capabilities.

CircuitValidator Class

CircuitValidator derives from the DOM adapter Sce.Atf.Controls.Adaptable.Graphs.CircuitValidator and overrides its properties with data it gets from the metadata classes in Schema. For details on the base class, see CircuitValidator Class.

GroupingCommands Component

The GroupingCommands component is the command client for grouping commands and derives from Sce.Atf.Controls.Adaptable.Graphs.GroupingCommands. For more information on this base component, see GroupingCommands Component.

ModulePlugin Component

ModulePlugin is a palette client component that creates the circuit items palette. In some ways, it functions like other palette clients, such as the one for ATF Simple DOM Editor Sample described in Using a Palette.

The component's IInitializable.Initialize() method sets up palette data using its DefineModuleType() method, which performs several functions, including adding an item to the palette. It also adds type metadata information by calling the NamedMetadata.SetTag() method for the types on the palette.

PrintableDocument Class

PrintableDocument is an implementation of IPrintableDocument to print the circuit canvas. This implementation uses a private class CircuitPrintDocument, which derives from CanvasPrintDocument. CanvasPrintDocument is an abstract base class for canvas printing. PrintableDocument overrides its methods to get "page" bounds for the various PrintRange values, as well as the Render() method to actually render a circuit to the page.

PrintableDocument is defined as the DOM adapter for the circuit type, because the document represents a circuit.

Topics in this section

Clone this wiki locally