Skip to content

DOM Use in Simple DOM Editor

Gary edited this page Mar 10, 2015 · 2 revisions

Table of Contents

This section shows how the ATF Simple DOM Editor Sample uses DOM concepts and operations discussed in the previous sections of DOM in a Nutshell.

The Simple DOM Editor Programming Discussion section in ATF Code Samples Discussions explains how this sample works in general. The topic here goes into the specifics of how this sample uses the DOM, and references the appropriate parts of the more general section.

The SimpleDOMEditor sample edits sequences of events and can save an event sequence to a document file — and then read it back for further editing. Its UI is seen in the figure below.

This sample application can have multiple documents open. Each event has attributes and can also contain any number of animation and geometry resources. Event sequences are created by dragging event objects from the palette onto the ListView control associated with a document, in the "Untitled.xml" tab in the figure. Resources are added to an event by dragging a resource from the palette to the Resources tab (also a ListView) when an event is selected. When an event or resource object is selected in either ListView, its attributes can be edited in either a PropertyEditor or GridPropertyEditor; both editors show the same attributes but organize them differently. Selected objects can also be copied, cut, deleted, and pasted. Each event sequence, event, and resource object is represented by a DomNode.

This sample provides two ways of editing application data: creating objects by dragging them from the palette, and editing these objects' attributes in a property editor. This section demonstrates how this editing takes place using the DOM.

Data Model

The data model defines types for the event, event sequence, and resource objects using an XML Schema in the type definition file eventSequence.xsd. The types are:

  • "eventType": For an event.
  • "resourceType": For a resource, and there are two types based on this:
    • "animationResourceType": For an animation resource.
    • "geometryResourceType": For a geometry resource.
  • "eventSequenceType": For a sequence of "eventType" objects.
An "eventSequenceType" object can contain multiple "eventType" child objects — this is the event sequence. An "eventType" object can contain multiple "resourceType" child objects.

In terms of the DOM node tree, the root DomNode has the type "eventSequenceType", because the root object contains the events. The root DomNode has a child DomNode of type "eventType" for every event in the sequence. Each "eventType" DomNode can have child DomNodes of type "animationResourceType" or "geometryResourceType".

For more details on this data model, see Type Definition in Simple DOM Editor Programming Discussion.

This sample uses DomGen to create a Schema class referencing the metadata class objects that the type loader creates. For information on what's in this class in SimpleDOMEditor, see Schema Class in Simple DOM Editor Programming Discussion. For general information on DomGen and the Schema class, see Using the DomGen Utility to Create a Schema Class in the Type Loaders section.

Schema Type Loader

SimpleDOMEditor derives its type loader from XmlSchemaTypeLoader, and its constructor loads the type definition file eventSequence.xsd. Creating the type loader is straightforward, because this sample uses an XML Schema.

The type loader's OnSchemaSetLoaded() method performs the various functions discussed in Type Loaders. Most of these functions are discussed further here, as noted:

  • Initialize the Schema class by calling Schema.Initialize().
  • Define various DOM adapters, most on the type of the root DomNode "eventSequenceType". For this discussion, see DOM Adapters.
  • Create NodeTypePaletteItem objects and add them to DomNodeType metadata objects by calling NamedMetadata.SetTag for the types "eventType", "animationResourceType", and "geometryResourceType". This provides the information needed for adding objects of these types to the palette. For further details, see Palette Handling.
  • Create collections of property descriptors to enable editing object's attributes. For a description, see Editing Attributes Using Property Descriptors.
For more discussion of the sample's schema loader, see Schema Loader Component in Simple DOM Editor Programming Discussion.

DOM Adapters

The SimpleDOMEditor sample creates simple DOM adapters for its basic types:

  • Event: Adapter for "eventType". Has Name, Time, and Duration properties for attributes and the Resources property for a list of the child resources in an event.
  • EventSequence: Adapter for "eventSequenceType". Its Events property gets the list of events in the sequence.
  • Resource: Adapter for "resourceType". Has Name and Size properties for attributes.
These adapters simply provide properties to access type attributes and get a list of child nodes. For more discussion of these basic adapters, see DOM Adapters in Simple DOM Editor Programming Discussion.

The other DOM adapters do considerably more and are defined on the root DomNode's type "eventSequenceType". Several of these adapters are general ATF DOM adapters, not particular to this sample:

  • MultipleHistoryContext: DOM adapter for multiple history contexts, so that the undo/redo history stack for each document is distinct.
  • ReferenceValidator: DOM validator for internal references between DOM nodes and references to external resources.
  • UniqueIdValidator: Adapter that ensures that every DOM node in the tree has a unique ID.
  • DomNodeQueryable: Adapter enabling DomNodes to be searched, to supply the search results, and to have those results replaced with other data. For more details, see Node Searching in Simple DOM Editor Programming Discussion.
Note that several of these DOM adapters, such as UniqueIdValidator, are DOM validators, discussed in DOM Adapters Validate in DOM Adapters.

The other DOM adapters are specific to this sample. These are workhorses, performing a variety of functions because they implement several interfaces:

  • EventSequenceDocument: Serves as a document for the application. For a description of how this adapter functions as a document, see Document Handling in Simple DOM Editor Programming Discussion.
  • EventSequenceContext: Provides an event sequence context for the application. In particular, it implements IInstancingContext to copy, insert, and delete items, so it is fundamental to editing. For more on editing, see Application Data Creation.
The use of DOM adapters in this sample and others demonstrate the versatility of DOM adapters. They can serve many functions, and their adaptability along with the capability of monitoring events on a whole tree of DomNodes make them phenomenally useful.

Palette Handling

SimpleDOMEditor sets up a palette that is integrated with the DOM.

The sample's PaletteClient component populates the palette with objects and imports IPaletteService. This import is satisfied by the ATF PaletteService component, which manages a palette of objects that can be dragged onto other controls.

In its schema loader, the sample specifies which objects go on the palette by constructing a NodeTypePaletteItem object for each object, and placing this information in the DomNodeType metadata object for the type:

Schema.eventType.Type.SetTag(
    new NodeTypePaletteItem(
        Schema.eventType.Type,
        "Event".Localize(),
        "Event in a sequence".Localize(),
        Resources.EventImage));

Schema.animationResourceType.Type.SetTag(
    new NodeTypePaletteItem(
        Schema.animationResourceType.Type,
        "Animation".Localize(),
        "Animation resource".Localize(),
        Resources.AnimationImage));

Schema.geometryResourceType.Type.SetTag(
    new NodeTypePaletteItem(
        Schema.geometryResourceType.Type,
        "Geometry".Localize(),
        "Geometry resource".Localize(),
        Resources.GeometryImage));

The NodeTypePaletteItem specifies the object type, a name, and an image that appears on the palette. For more information on specifying these palette items, see Adding Palette Information in Using DOM Metadata for Palettes and Other Items.

The PaletteClient component's IInitializable.Initialize() method uses these NodeTypePaletteItem items added to the DomNodeType metadata objects to determine which objects go on the palette: if the DomNodeType has a NodeTypePaletteItem associated with it, the DomNodeType is added to the palette. As a result, event, animation, and geometry objects appear on the palette.

PaletteClient implements the IPaletteClient interface. IPaletteClient.Convert() takes a palette item and returns an object that can be inserted into an IInstancingContext. In this case, it is a DomNode with the DomNodeType associated with the palette item. In other words, the item dragged is a DomNode that could serve as new application data. To find out how data is created this way, see Application Data Creation.

For more details on palette operation, see Using a Palette in Simple DOM Editor Programming Discussion.

Application Data Creation

SimpleDOMEditor allows creating new objects by dragging objects from a palette onto ListView controls. The sample's EventSequenceContext DOM adapter shows how you create data by adding new DOM nodes to the tree representing an event sequence.

The EventListEditor component edits event sequences in ListViews on document tabs in the editor. There is a tab and an EventSequenceContext for each document, and each EventSequenceContext owns the ListView for each document.

EventListEditor keeps track of the active context with the context registry component. It knows when a given EventSequenceContext and its ListView are active, because it tracks context changes. For more information, see Context Registry.

EventListEditor subscribes to drag events on the ListView associated with a document, and its listView_DragDrop() method is called when an object is dropped onto this ListView:

private void listView_DragDrop(object sender, DragEventArgs e)
{
    IInstancingContext context = m_eventSequenceContext;
    if (context.CanInsert(e.Data))
    {
        ITransactionContext transactionContext = context as ITransactionContext;
        TransactionContexts.DoTransaction(transactionContext,
            delegate
            {
                context.Insert(e.Data);
            },
            Localizer.Localize("Drag and Drop"));

        if (m_statusService != null)
            m_statusService.ShowStatus(Localizer.Localize("Drag and Drop"));
    }
}

The field m_eventSequenceContext contains the active EventSequenceContext and was set when the context changed. EventSequenceContext implements IInstancingContext, and listView_DragDrop() calls IInstancingContext.CanInsert() on this context to see if the object can be inserted:

public bool CanInsert(object insertingObject)
{
    IDataObject dataObject = (IDataObject)insertingObject;
    object[] items = dataObject.GetData(typeof(object[])) as object[];
    if (items == null)
        return false;

    foreach (object item in items)
        if (!Adapters.Is<Event>(item))
            return false;

    return true;
}

CanInsert() casts the data as an IDataObject and then casts it to an array of objects, each of which is a DomNode. Recall that a dragged palette object is a DomNode with the DomNodeType of the palette object, as described in Palette Handling previously.

Only event objects can be dragged onto the ListView associated with documents of event sequences. The second part of CanInsert() checks whether each dragged item is a DomNode with the DomNodeType for "eventType" with this line:

if (!Adapters.Is<Event>(item))

The method Adapters.Is() returns true if and only if the object can be adapted to the given type, Event in this case. The DOM adapter Event is defined on "eventType" in SchemaLoader:

Schema.eventType.Type.Define(new ExtensionInfo<Event>());

This means that any object of type "eventType" can be adapted to Event. Because "eventType" is the only type that Event is defined for, only "eventType" DomNodes meet this condition.

Assuming that the object can be inserted, listView_DragDrop() gets an ITransactionContext so the insertion can take place in a transaction. And where does it get this context? It adapts the EventSequenceContext to ITransactionContext, because EventSequenceContext derives from EditingContext, which implements ITransactionContext (through a class it derives from). For more on this versatile context class, see Sce.Atf.Dom.EditingContext Class in Context Classes. The extension method TransactionContexts.DoTransaction is called using the adapted ITransactionContext. DOM adapters are frequently adapted to useful classes like this.

During the transaction, the IInstancingContext.Insert() method actually creates the new data as DomNodes and adds them to the event sequence:

public void Insert(object insertingObject)
{
    IDataObject dataObject = (IDataObject)insertingObject;
    object[] items = dataObject.GetData(typeof(object[])) as object[];
    if (items == null)
        return;

    DomNode[] itemCopies = DomNode.Copy(Adapters.AsIEnumerable<DomNode>(items));
    IList<Event> events = this.Cast<EventSequence>().Events;
    foreach (Event _event in Adapters.AsIEnumerable<Event>(itemCopies))
        events.Add(_event);

    Selection.SetRange(itemCopies);
}

The first part of Insert() is the same as CanInsert(): casting the data as a IDataObject and then casting to an array of objects. These objects are actually DomNodes with the DomNodeType for "eventType". The inserted objects are copied to itemCopies, an array of DomNodes.

The next line shows adaptation in action in the DOM adapter. Here this, which is an EventSequenceContext, is adapted to EventSequence. This works, because both the EventSequenceContext and EventSequence DOM adapters are defined on the type "eventSequenceType" in SchemaLoader:

Schema.eventSequenceType.Type.Define(new ExtensionInfo<EventSequence>());
...
Schema.eventSequenceType.Type.Define(new ExtensionInfo<EventSequence>());

The type "eventSequenceType" is the root DomNode's type, so this DomNode is the root and can be adapted to any DOM adapter defined for its type, as previously discussed.

Once adapted to EventSequence, the root object's Events property can provide a list of events, which is saved in the IList<Event> events variable. Finally, Insert() adds each new Event in itemCopies to this event list.

The key thing to note here is that the creation of event data is done by creating DomNodes. An event is added to the sequence by adding a DomNode to the list of DomNodes maintained by the EventSequence DomNode, which is the root DomNode. This DomNode's type has numerous DOM adapters defined on it, so this node can function as an EventSequenceContext, which also allows it to function as a ITransactionContext. This is a common paradigm in the ATF DOM: define DOM adapters implementing any needed interfaces on the root DomNode's type so the root DomNode can take on all these roles. Recall that when multiple DOM adapters are defined for a type, an object of that type can be adapted to any interface implemented by any of these DOM adapters; for details, see Adapting to All Available Interfaces.

For more information about how this context functions in the sample, see EventSequenceContext Class in Simple DOM Editor Programming Discussion.

You add resources to an event by dragging a resource object from the palette to the Resources ListView. In this operation, the ResourceListEditor component and EventContext class function very similarly to EventListEditor and EventSequenceContext in adding a DomNode of the appropriate resource type as a child to an Event DomNode. For further details, see ResourceListEditor Component in Simple DOM Editor Programming Discussion.

You can also remove event and resource objects by selecting them and deleting them. This simply removes the associated DomNodes from the tree.

Editing Attributes Using Property Descriptors

Besides defining DOM adapters, SchemaLoader creates property descriptors for several types to make it easy to edit their attributes/properties in ATF property editors.

For example, here are the property descriptors constructed for the attributes "Name", "Time", and "Duration" of "eventType":

Schema.eventType.Type.SetTag(
    new PropertyDescriptorCollection(
        new PropertyDescriptor[] {
            new AttributePropertyDescriptor(
                "Name".Localize(),
                Schema.eventType.nameAttribute,
                null,
                "Event name".Localize(),
                false),
            new AttributePropertyDescriptor(
                "Time".Localize(),
                Schema.eventType.timeAttribute,
                null,
                "Event starting time".Localize(),
                false),
            new AttributePropertyDescriptor(
                "Duration".Localize(),
                Schema.eventType.durationAttribute,
                null,
                "Event duration".Localize(),
                false),
    }));

These descriptors provide basic information about each attribute. In this case, the property value editing controls appropriate to the data type are used, such as a text box for a string attribute. However, you can specify more information by using a different form of the AttributePropertyDescriptor constructor, as in this descriptor for the "Compressed" attribute of "animationResourceType":

new AttributePropertyDescriptor(
    "Compressed".Localize(),
    Schema.animationResourceType.compressedAttribute,
    null,
    "Whether or not animation is compressed".Localize(),
    false,
    new BoolEditor()),

In this case, the constructor specifies BoolEditor as the value editor, which results in a check box being used as the value editor for this Boolean attribute.

In addition, the sample imports the MEF components PropertyEditor, GridPropertyEditor, and PropertyEditingCommands to provide these two property editors and context menu commands for them. These editors automatically display the properties for attributes that were defined in the property descriptors when an object is selected — no further programming effort required.

For a DOM related discussion, see DOM Property Descriptors. For a broader description of how properties are edited in ATF, see the Property Editing in ATF section.

Saving Event Sequence Data

Because SimpleDOMEditor uses an XML Schema to specify its data model and a schema loader derived from XmlSchemaTypeLoader, it can easily save its application data and read it back later using ATF classes and XML.

SimpleDOMEditor uses a DomXmlWriter object to write application data — in a tree of DomNodes — to an XML file. It reads the file contents back to a DomNode tree with the same data using DomXmlReader.

For a general description of these classes, see DOM Persistence. For details on how SimpleDOMEditor uses DomXmlReader to read a document from XML, see Opening a Document in Simple DOM Editor Programming Discussion. For the sample's writing the document to XML with DomXmlWriter, see Saving a Document in Simple DOM Editor Programming Discussion.

Topics in this section

Clone this wiki locally