Skip to content

Simple DOM Editor Programming Discussion

Gary edited this page Mar 10, 2015 · 2 revisions

Table of Contents

The ATF Simple DOM Editor Sample shows defining a data model in an XML Schema, editing data, and saving the edited data in an XML file. Application data is displayed in two ListView controls, and its properties can be examined in two property editors.

This application's data consist of sequences of events, which can contain resources. The sequence of events is displayed in the main ListView control, and the resources of the selected event are listed in the Resources ListView control.

Note that "event" in this context means application data, not an event that is processed by an event handler. Both meanings of the term are used in this discussion, and the meaning of "event" should be clear from the context.

This sample is especially interesting, because this Programmer's Guide describes converting this particular sample into another ATF application. To walk through this process, see Creating an Application from an ATF Sample.

Programming Overview

SimpleDOMEditor uses an XML Schema to define its data model of sequences of events with resources. Its schema loader also defines DOM adapters, palette items, and property descriptors that determine which properties appear in the property editors.

The application's PaletteClient along with ATF's PaletteService creates a palette of events and resource items.

SimpleDOMEditor provides the DOM adapters Event, EventSequence, and Resource for their corresponding types to provide properties that access information in a DomNode of that type.

SimpleDOMEditor relies heavily on contexts. EventContext handles resources belonging to the selected event and manages the Resources window ListView editing. This context implements a host of interfaces to handle displaying items, selection, and editing. EventSequenceContext does a similar job for a sequence of events in the main window's ListView, including handling subselection of a properties of an event.

EventSequenceDocument extends DomDocument, which implements IDocument. The Editor component is its document client, handling closing and saving documents.

The EventListEditor and ResourceListEditor components edit event sequences and their associated resources in their ListViews, handling drag and drop as well as copy, paste, and delete.

This sample also shows how to search DomNodes, using the components DomNodeNameSearchService and DomNodePropertySearchService. Searching also uses DomNodeQueryable, which is defined as a DOM adapter for the root type in SchemaLoader, so any node can be searched.

Application Initialization

SimpleDOMEditor's initialization is typical for a WinForms application, as described in WinForms Application in the ATF Application Basics and Services section. Its MEF TypeCatalog contains components in the application shell framework, such as SettingsService, StatusService, CommandService, and ControlHostService. It uses components to handle documents: DocumentRegistry, AutoDocumentService, RecentDocumentCommands, StandardFileCommands, StandardFileExitCommand, MainWindowTitleService, and TabbedControlSelector. Editing and context management are handled by ContextRegistry, StandardEditCommands, StandardEditHistoryCommands, and StandardSelectionCommands. How these common components function is discussed elsewhere in this Guide, such as Documents in ATF and ATF Contexts.

The PropertyEditor, GridPropertyEditor, and PropertyEditingCommands components handle property editing. These are well integrated with the ATF DOM, so the application only needs to include these components to be able to edit properties of the application's data. For details on property editing, see Property Editing in ATF.

SimpleDOMEditor adds a few custom components of its own:

  • PaletteClient: populate the palette with items described by DOM types, with help from the ATF component PaletteService. For details, see Using a Palette.
  • DomNodeNameSearchService and DomNodePropertySearchService: provide a GUI for searching and replacement of DomNode names on the currently active document. See Node Searching.
  • Editor: create and save event sequence documents. See Document Handling.
  • SchemaLoader: load the XML Schema for the data model as well as define property descriptors and palette items. See Data Model.
  • EventListEditor: track event sequence contexts and controls that display event sequences. See Working With Contexts.
  • ResourceListEditor: display and edit resources that belong to the most recently selected event. See ResourceListEditor Component.

Data Model

Type Definition

This application creates and edits event sequences. An event has attributes, such as duration, and can contain one or more resources, such as animations. The sample defines its data model in the XML Schema Definition (XSD) language in the type definition file eventSequence.xsd. This figure shows the Visual Studio XML Schema Explorer view of the sample's data definition:

This tree view shows that an "Event", eventType, contains a "Resource", resourceType, and has "name", "time", and "duration" attributes. The XML Schema for this type definition shows a different view of the same thing:

<!--Event, with name, start time, duration and a list of resources-->
<xs:complexType name ="eventType">
  <xs:sequence>
    <xs:element name="resource" type="resourceType" maxOccurs="unbounded"/>
  </xs:sequence>
  <xs:attribute name="name" type="xs:string"/>
  <xs:attribute name="time" type="xs:integer"/>
  <xs:attribute name="duration" type="xs:integer"/>
</xs:complexType>

The data model has a "Resource" type, and the types "Animation" and "Geometry" are based on "Resource". "Resource" types have the attributes "name", "size", and "compressed".

An event sequence is simply a sequence of any number of events, as this type definition shows:

<!--Event sequence, a sequence of events-->
<xs:complexType name ="eventSequenceType">
  <xs:sequence>
    <xs:element name="event" type="eventType" maxOccurs="unbounded"/>
  </xs:sequence>
</xs:complexType>

Note this definition for the root element:

<!--Declare the root element of the document-->
<xs:element name="eventSequence" type="eventSequenceType"/>

The root element is of "EventSequence" type, because a document contains one event sequence. This is very important, because this type has several DOM adapters defined for it, which can apply to the entire document.

Schema Class

The ATF DomGen utility generated a Schema class file from the type definition file. The file GenSchemaDef.bat contains commands for DomGen.

Schema contains subclasses for all the data types and fields for the attributes that are of the appropriate ATF DOM metadata classes.

For example, the "Resource" type has three AttributeInfo fields for its "name", "size", and "compressed" attributes:

public static class resourceType
{
    public static DomNodeType Type;
    public static AttributeInfo nameAttribute;
    public static AttributeInfo sizeAttribute;
    public static AttributeInfo compressedAttribute;
}

The "Event" type also has AttributeInfo fields for its attributes, plus a ChildInfo field for any resources contained in the object:

public static class eventType
{
    public static DomNodeType Type;
    public static AttributeInfo nameAttribute;
    public static AttributeInfo timeAttribute;
    public static AttributeInfo durationAttribute;
    public static ChildInfo resourceChild;
}

Even though an event may contain any number of resources, it needs only one ChildInfo object, because in the ATF DOM, a node can have either a single child or a single list of children in the DOM node tree.

Schema Loader Component

The SchemaLoader component loads the XML schema and does any other initialization required for the data model. It derives from XmlSchemaTypeLoader, which performs a substantial part of the schema loading process.

Its constructor, which is automatically called during MEF initialization when the component object is created, resolves the type definition file address and loads the file:

public SchemaLoader()
{
    // set resolver to locate embedded .xsd file
    SchemaResolver = new ResourceStreamResolver(Assembly.GetExecutingAssembly(), "SimpleDomEditorSample/schemas");
    Load("eventSequence.xsd");
}

SchemaLoader constructors nearly always take this form. Other standard items are the NameSpace and TypeCollection properties:

public string NameSpace
{
    get { return m_namespace; }
}
private string m_namespace;
...
public XmlSchemaTypeCollection TypeCollection
{
    get { return m_typeCollection; }
}
private XmlSchemaTypeCollection m_typeCollection;

The other work to do is to define the method OnSchemaSetLoaded(), which is called after the schema set has been loaded and the DomNodeType objects have been created. This method accomplishes several things, described in the following sections.

Schema.Initialize

The schema loader calls Schema.Initialize() to initialize the Schema class. This method was also created by DomGen when it generated the rest of the metadata classes.

Define DOM Extensions

The loader defines DOM extensions for data types, so that methods in the various DOM adapters are called appropriately:

Schema.eventSequenceType.Type.Define(new ExtensionInfo<EventSequenceDocument>());
Schema.eventSequenceType.Type.Define(new ExtensionInfo<EventSequenceContext>());
Schema.eventSequenceType.Type.Define(new ExtensionInfo<MultipleHistoryContext>());
Schema.eventSequenceType.Type.Define(new ExtensionInfo<EventSequence>());
Schema.eventSequenceType.Type.Define(new ExtensionInfo<ReferenceValidator>());
Schema.eventSequenceType.Type.Define(new ExtensionInfo<UniqueIdValidator>());
Schema.eventSequenceType.Type.Define(new ExtensionInfo<DomNodeQueryable>());

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

Schema.resourceType.Type.Define(new ExtensionInfo<Resource>());

Note that the types used here are the ones defined in the Schema class.

The first group's definitions all apply to Schema.eventSequenceType, which is the type of the document's root. These DOM adapters thus apply to the entire document and accomplish a variety of purposes. A document describes a single sequence of events, so its DomNode tree has only one node of type "EventSequence", the tree root DomNode. This root node can be adapted to all of the adapters listed above. For instance, it's useful at times to treat the document as a EventSequenceContext. For more details, see Working With Contexts.

The Schema.eventType and Schema.resourceType types also have DOM adapters. For more information on these adapters, see DOM Adapters.

Metadata Driven Property Editing

The schema loader enables metadata driven property editing for events and resources by creating an AdapterCreator:

AdapterCreator<CustomTypeDescriptorNodeAdapter> creator =
    new AdapterCreator<CustomTypeDescriptorNodeAdapter>();
Schema.eventType.Type.AddAdapterCreator(creator);
Schema.resourceType.Type.AddAdapterCreator(creator);

If this were not done, property editors would not show properties from the property descriptors, defined later on. For more details, see Metadata Driven Property Editing.

Palette Type Setup

This step adds tag information to the type that is used later to add these types to the palette: "Event", "Animation", and "Geometry". The palette item information is encapsulated in a NodeTypePaletteItem object, a container class:

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

The NodeTypePaletteItem contains all the information needed to display and use the palette item: its type, descriptive text, and an icon image to appear on the palette and in the ListView controls.

For further details on how this sets up the palette, see Using a Palette.

Create Property Descriptors

Property descriptors for types specify the data that shows up in property editors for the types. For details on how this works, see Property Editing in ATF and Property Descriptors in particular.

This NamedMetadata.SetTag() call's first parameter creates a PropertyDescriptorCollection containing an AttributePropertyDescriptor for each attribute of an "Event" type. This information is required for each attribute to appear in a property editor when an event is selected in the main ListView. The AdapterCreator must also be set up, as described previously in Metadata Driven Property Editing.

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

Using a Palette

SimpleDOMEditor uses the ATF PaletteService component, which manages a palette of objects that can be dragged onto other controls. SimpleDOMEditor allows dragging palette items onto the two ListView controls: the main ListView for event sequences and the Resources ListView for resources of the selected event. PaletteService's constructor creates a System.Windows.Forms.UserControl for the palette, configures it, and registers it with the ControlHostService component, placing the control on the left side of the main window.

Add Palette Items

SimpleDOMEditor adds its own PaletteClient component that imports IPaletteService, which is exported by ATF's PaletteService component. The IInitializable.Initialize() method for the PaletteClient component adds items to the palette:

void IInitializable.Initialize()
{
    string category = "Events and Resources";
    m_paletteService.AddItem(Schema.eventType.Type, category, this);
    foreach (DomNodeType resourceType in m_schemaLoader.GetNodeTypes(Schema.resourceType.Type))
    {
        NodeTypePaletteItem paletteItem = resourceType.GetTag<NodeTypePaletteItem>();
        if (paletteItem != null)
            m_paletteService.AddItem(resourceType, category, this);
    }
}

This method adds items to the palette with the IPaletteService.AddItem() method, defined as:

void AddItem(object item, string categoryName, IPaletteClient client);

In the first call to AddItem(), the type for an event is added, Schema.eventType.Type, in the "Events and Resources" category.

The foreach loop iterates through all the types for the "Resource" type, Schema.resourceType.Type. The types "Animation" and "Geometry" are both based on the "Resource" type. For each of these "Resource" types, the loop checks that it can get a NodeTypePaletteItem for the type:

NodeTypePaletteItem paletteItem = resourceType.GetTag<NodeTypePaletteItem>();

Recall that in the SchemaLoader class, this kind of initialization occurs to add information for each "Resource" type for the palette:

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

This call to GetTag() above simply retrieves the above NodeTypePaletteItem information, if present. After it verifies that the type has an associated NodeTypePaletteItem, Initialize() adds the type to the palette.

Looping in this way guarantees that all resource types are added to the palette and makes adding new resource types to the palette later easier.

Implement IPaletteClient

The IPaletteClient.GetInfo() method gets display information for the item. It retrieves the data from the NodeTypePaletteItem that was set in SchemaLoader:

void IPaletteClient.GetInfo(object item, ItemInfo info)
{
    DomNodeType nodeType = (DomNodeType)item;
    NodeTypePaletteItem paletteItem = nodeType.GetTag<NodeTypePaletteItem>();
    if (paletteItem != null)
    {
        info.Label = paletteItem.Name;
        info.Description = paletteItem.Description;
        info.ImageIndex = info.GetImageList().Images.IndexOfKey(paletteItem.ImageName);
    }
}

This method first casts the item as a DomNodeType and creates a DomNode of that type. The item parameter in Convert() is an item in the palette. And the items that were added to the palette are types, as in this line:

m_paletteService.AddItem(Schema.eventType.Type, category, this);

These palette items, like Schema.eventType, as defined in the Schema class:

public static class eventType
{
    public static DomNodeType Type;
    public static AttributeInfo nameAttribute;
    public static AttributeInfo timeAttribute;
    public static AttributeInfo durationAttribute;
    public static ChildInfo resourceChild;
}

So these items are already a DomNodeType, and the cast succeeds.

GetInfo() then simply sets the ItemInfo properties from the fields of the type's NodeTypePaletteItem object, which it obtains by calling GetTag().

IPaletteClient.Convert() takes a palette item and returns an object that can be inserted into an IInstancingContext — a DomNode in this case:

object IPaletteClient.Convert(object item)
{
    DomNodeType nodeType = (DomNodeType)item;
    DomNode node = new DomNode(nodeType);

    NodeTypePaletteItem paletteItem = nodeType.GetTag<NodeTypePaletteItem>();
    if (paletteItem != null)
    {
        if (nodeType.IdAttribute != null)
            node.SetAttribute(nodeType.IdAttribute, paletteItem.Name); // unique id, for referencing

        if (nodeType == Schema.eventType.Type)
            node.SetAttribute(Schema.eventType.nameAttribute, paletteItem.Name);
        else if (Schema.resourceType.Type.IsAssignableFrom(nodeType))
            node.SetAttribute(Schema.resourceType.nameAttribute, paletteItem.Name);
    }
    return node;
}

This method first casts the item as a DomNodeType and creates a DomNode of that type. The item parameter in Convert() is a selected item in the palette.

Next, the method GetTag() is used once again to retrieve a NodeTypePaletteItem containing palette item information.

The rest of the code sets attributes of the DomNode with the DomNode.SetAttribute() method:

public void SetAttribute(AttributeInfo attributeInfo, object value);

Both the ID and Name attributes are set. The type of item is checked, so that the proper nameAttribute can be set.

DOM Adapters

DOM adapters allow a DomNode to be dynamically cast to another object type or interface. SimpleDOMEditor provides the DOM adapters Event, EventSequence, and Resource for their corresponding types. All these adapters do is provide properties that access information in a DomNode. For instance, Event has a Name property:

/// Gets or sets name associated with event, such as a label
public string Name
{
    get { return GetAttribute<string>(Schema.eventType.nameAttribute); }
    set { SetAttribute(Schema.eventType.nameAttribute, value); }
}

The DomNodeAdapter.GetAttribute() method gets the value of a specified attribute, Schema.eventType.nameAttribute, defined in the Schema class as

public static AttributeInfo nameAttribute;

Most of the DOM adapter properties simply access attribute values of the type, but the EventSequence DOM adapter's Events property gets all the events in an event sequence:

/// Gets list of Events in sequence
public IList<Event> Events
{
    get { return GetChildList<Event>(Schema.eventSequenceType.eventChild); }
}

Note that the returned property value is a IList<Event>, that is, a list of the DOM adapter Event objects associated with an event sequence DOM adapter EventSequence.

The DOM adapters in this application provide a simple way to get properties for the event, event sequence, and resource objects, all embodied in DomNode objects.

Working With Contexts

SimpleDOMEditor relies heavily on contexts. A context provides services for operations in certain situations, hence the name context. ATF provides quite a few interfaces and classes for different types of contexts, and this sample uses several. For information about contexts in general, see ATF Contexts.

This section illustrates how contexts control and facilitate editing operations in the main and Resources ListView controls.

EventContext Class

EventContext handles resources belonging to the selected event and manages the Resources ListView's editing. This class adapts the application data to a list and uses contexts to enable data editing as well as undoing and redoing data changes. EventContext implements interfaces to do much of its work.

EventContext has this derivation ancestry: EditingContext, HistoryContext, TransactionContext, and finally DomNodeAdapter, so all these context classes are DOM adapters. EditingContext is a history context with a selection, providing a basic self-contained editing context. Several samples, such as ATF Fsm Editor Sample and ATF State Chart Editor Sample have context classes that extend EditingContext. For more information about this context, see General Purpose EditingContext Class.

As its declaration shows, EventContext also implements several context and other interfaces:

public class EventContext : EditingContext,
    IListView,
    IItemView,
    IObservableContext,
    IInstancingContext,
    IEnumerableContext

EventContext's ancestor classes also implement some useful interfaces, such as IHistoryContext and ITransactionContext, which allow changes in the context, such as deleting resources in an event, to be undone and redone.

OnNodeSet Method

As a DOM adapter, EventContext's OnNodeSet() method subscribes to several events for a DomNode, such as ChildInserted:

DomNode.ChildInserted += new EventHandler<ChildEventArgs>(DomNode_ChildInserted);
...
private void DomNode_ChildInserted(object sender, ChildEventArgs e)
{
    Resource resource = e.Child.As<Resource>();
    if (resource != null)
    {
        ItemInserted.Raise(this, new ItemInsertedEventArgs<object>(e.Index, resource));
    }
}

If this handler can adapt the DomNode to the Resource DOM adapter, it raises the ItemInserted event, which is defined in the IObservableContext interface.

The other event handlers, subscribed to in OnNodeSet(), deal with Resource objects in a similar way.

IListView and IItemView Interfaces

IListView offers a couple of properties with information about the Resources ListView. Items gets the list of resources and Columns provides the column headers.

Note that a DomNode for an Event can have a list of "Resource" DomNode children that belong to the Event. Therefore, the Items property gets a list of children:

public IEnumerable<object> Items
{
    get { return GetChildList<object>(Schema.eventType.resourceChild); }
}

This property value is also returned by the IEnumerableContext.Items property.

ColumnNames simply returns a list of name strings for the columns that are displayed in the ListView:

public string[] ColumnNames
{
    get { return new string[] { Localizer.Localize("Name"), Localizer.Localize("Size") }; }
}

IItemView's GetInfo() method complements IListView by setting an ItemInfo with information for each column in the ListView:

public void GetInfo(object item, ItemInfo info)
{
    Resource resource = Adapters.As<Resource>(item);
    info.Label = resource.Name;
    string type = null;
    if (resource.DomNode.Type == Schema.animationResourceType.Type)
        type = Resources.AnimationImage;
    else if (resource.DomNode.Type == Schema.geometryResourceType.Type)
        type = Resources.GeometryImage;

    info.ImageIndex = info.GetImageList().Images.IndexOfKey(type);
    info.Properties = new object[] { resource.Size };
}

The resource DomNode specified in item is adapted to the Resource DOM adapter. ItemInfo.Label is set to the Name property of the Resource. The resource type is checked to decide which icon to associate with the item. The icon and name go in the "Name" column of the ListView. The "Size" column's value comes from the ItemInfo.Properties value, constructed from the property Resource.Size.

IInstancingContext Interface

IInstancingContext handles instancing in the Resources ListView, that is, working with resource instances in this control, which can be edited using menu items and by drag and drop. You insert resources in the Resources ListView for an event by dragging and dropping a resource from the palette onto the Resources ListView. Such drag and drop events are also handled using the IInstancingContext interface. For a description of how this is done, see the section Drag and Drop and Instancing in the Instancing In ATF topic.

The IInstancingContext interface provides methods to check whether items can be copied, inserted (pasted), or deleted, and performs these actions, too.

Here are the copy related methods:

public bool CanCopy()
{
    return Selection.Count > 0;
}
...
public object Copy()
{
    IEnumerable<DomNode> resources = Selection.AsIEnumerable<DomNode>();
    List<object> copies = new List<object>(DomNode.Copy(resources));
    return new DataObject(copies.ToArray());
}

Selection is defined in the EditingContext class and is an AdaptableSelection representing a collection of selected objects; this class handles basic selection mechanics. CanCopy() uses Selection to simply verify that there is at least one selected item. Copy() adapts the selection to a collection of DomNodes, copies them as a list of objects, and then constructs a System.Windows.Forms.DataObject from the copy, which can be placed on the Windows® clipboard. The StandardEditCommands component actually calls Copy() and puts the copied data onto the clipboard.

CanInsert() and Insert() do the following:

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<Resource>(item))
            return false;

    return true;
}
...
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<Resource> resources = this.Cast<Event>().Resources;
    foreach (Resource resource in Adapters.AsIEnumerable<Resource>(itemCopies))
        resources.Add(resource);

    Selection.SetRange(itemCopies);
}

The first part of both methods is identical, simply verifying that there's data to insert.

StandardEditCommands calls CanInsert() to determine whether the Insert menu item is enabled or not. CanInsert() casts the inserted data as a IDataObject and then casts it to an array of selected objects, each of which is a DomNode of type "Resource". The second part of CanInsert() checks whether all the selected items can be adapted as Resource objects. If so, the method returns true, and false otherwise.

For the insert operation, StandardEditCommands gets the clipboard data and calls Insert() to insert this data. Insert() casts the inserted data as a IDataObject and then casts it to an array of selected objects, just as for CanInsert(). These objects are adapted to DomNodes and then copied.

In this method, this represents an EventContext object,which is an adapted DomNode of type "Event" representing a selected event in the main ListView. Both EventContext and Event are DOM adapters for the "Event" type, as shown in these lines from SchemaLoader where DOM adapters are defined:

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

Because Event is a DOM adapter for a DomNode of type "Event", this DomNode can be adapted to an Event object, and its list of resources can be obtained from the Resources property of the Event DOM adapter:

IList<Resource> resources = this.Cast<Event>().Resources;

Finally, each inserted DomNode is adapted to a Resource object and added to the list of resources for the selected Event in the main ListView.

The deletion operations are simpler:

public bool CanDelete()
{
    return Selection.Count > 0;
}
...
public void Delete()
{
    foreach (DomNode node in Selection.AsIEnumerable<DomNode>())
        node.RemoveFromParent();

    Selection.Clear();
}

CanDelete() is identical to CanCopy(), simply verifying that there's something selected to delete.

Delete() iterates all selected items' underlying DomNodes and removes them from their parents with DomNode.RemoveFromParent(), effectively removing them from the application data. It also clears the selection using the Selection.Clear() method.

EventSequenceContext Class

EventSequenceContext handles a sequence of events in the main ListView, managing editing this list.

EventSequenceContext has many similarities to EventContext, including its derivation ancestry from EditingContext. One of the differences is that EventSequenceContext's constructor creates and configures the ListView instance used to display events. It also creates a GenericSelectionContext as a subselection context.

EventSequenceContext implements all the interfaces that EventContext does. Their implementations are almost identical, but Resource objects are replaced by Event objects in EventSequenceContext. The IInstancingContext implementations look identical except for that substitution, for example.

IListView Interface

The IListView implementation merely has the obvious differences from EventContext. The Items property returns a child list of Event objects, rather than Resource objects. ColumnNames returns the list of headings seen over the main ListView columns.

public IEnumerable<object> Items
{
    get { return GetChildList<object>(Schema.eventSequenceType.eventChild); }
}
...
public string[] ColumnNames
{
    get { return new string[] { Localizer.Localize("Name"), Localizer.Localize("Duration") }; }
}

ISubSelectionContext Interface and GenericSelectionContext Class

ISubSelectionContext is implemented for node search to show which attribute was found in a list of search hits when a hit list entry is selected. The found attribute in the selected node is highlighted in the property editor, which constitutes the subselection. For details, see Node Searching.

EventSequenceContext uses GenericSelectionContext for a subselection context:

m_subSelectionContext = new GenericSelectionContext();

EventSequenceContext implements the property ISubSelectionContext.SubSelectionContext, which returns an ISelectionContext:

public ISelectionContext SubSelectionContext
{
    get { return m_subSelectionContext; }
}

GenericSelectionContext is a straightforward implementation of ISelectionContext. Its constructor creates a Selection object and subscribes to a Changed event on it:

public GenericSelectionContext()
{
    m_selection = new Selection<object>();
    m_selection.Changed += new EventHandler(selection_Changed);

    // suppress compiler warning
    if (SelectionChanging == null) return;
}

The Changed event is raised when a subselection occurs. For details on how this occurs, see Node Searching.

The ISelectionContext implementation simply makes use the Selection object, as in GetSelection<T>() and LastSelected:

public IEnumerable<T> GetSelection<T>()
            where T : class
{
    return m_selection.AsIEnumerable<T>();
}
...
public object LastSelected
{
    get { return m_selection.LastSelected; }
}

Document Handling

Document handling applications typically implement both IDocument for a document object and IDocumentClient for the document client.

EventSequenceDocument Class

EventSequenceDocument extends DomDocument, which implements IDocument.

The DomDocument class provides the basics of a document: IsReadOnly and Dirty properties, a DirtyChanged event, and the OnDirtyChanged() and OnReloaded event handlers. DomDocument extends DomResource and implements IResource to provide the resource properties Type and Uri to describe a document's type and location.

EventSequenceDocument itself implements overrides for the property Type and the methods OnUriChanged() and OnDirtyChanged() for application-specific behavior. These latter event handlers simply execute the base methods, as well as update ControlInfo for the ListView displaying the new document. ControlInfo holds information about controls hosted by ControlHostService.

EventSequenceDocument also implements ISearchableContext to allow searching for data in DomNode objects. For details on searching, see Node Searching.

Editor Component Document Client

The Editor component is the client for event sequence documents.

DocumentClientInfo Class

Editor uses the DocumentClientInfo class to hold document editor information. The Info property gets the DocumentClientInfo from a static variable:

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);

Opening a Document

CanOpen() simply checks that the filename extension is suitable, using the DocumentClientInfo:

public bool CanOpen(Uri uri)
{
    return DocumentClientInfo.IsCompatibleUri(uri);
}

The Open() method reads the document file's data and converts it into a tree of DomNode objects. After that, it sets up the context and other information needed for the new document.

The initial phase of opening creates a DomNode for the tree root and gets the file name:

public IDocument Open(Uri uri)
{
    DomNode node = null;
    string filePath = uri.LocalPath;
    string fileName = Path.GetFileName(filePath);

For an existing document, a FileStream is created and read. Because the document is stored as an XML document, you can use an DomXmlReader to read data and convert it to a tree of DomNodes. DomXmlReader.Read() returns the root DomNode of the tree it creates. For a new document, a DomNode with the DomNodeType of the event sequence root element — from the Schema class — is created.

if (File.Exists(filePath))
{
    // read existing document using standard XML reader
    using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
    {
        DomXmlReader reader = new DomXmlReader(m_schemaLoader);
        node = reader.Read(stream, uri);
    }
}
else
{
    // create new document by creating a Dom node of the root type defined by the schema
    node = new DomNode(Schema.eventSequenceType.Type, Schema.eventSequenceRootElement);
}

Next, Open() performs a series of ATF DOM set up operations to complete the open process:

EventSequenceDocument document = null;
if (node != null)
{
    // Initialize Dom extensions now that the data is complete
    node.InitializeExtensions();

    EventSequenceContext context = node.As<EventSequenceContext>();

    ControlInfo controlInfo = new ControlInfo(fileName, filePath, StandardControlGroup.Center);
    context.ControlInfo = controlInfo;

    // set document URI
    document = node.As<EventSequenceDocument>();
    document.Uri = uri;

    // set document GUIs for search and replace
    document.SearchUI = new DomNodeSearchToolStrip();
    document.ReplaceUI = new DomNodeReplaceToolStrip();
    document.ResultsUI = new DomNodeSearchResultsListView(m_contextRegistry);

    context.ListView.Tag = document;

    // show the ListView control
    m_controlHostService.RegisterControl(context.ListView, controlInfo, this);
}

return document;

First, an EventSequenceDocument object is created.

InitializeExtensions() initializes all the DOM adapters defined in the schema loader, shown in Define DOM Extensions.

An EventSequenceContext is created by adapting the root DomNode to EventSequenceContext, which is a DOM adapter for the "EventSequence" type. Recall that the "EventSequence" type is the type of the document's root.

The event sequence document's data is going to be displayed in a ListView that is part of the EventSequenceContext. A ControlInfo is set up for this control and is placed in the EventSequenceContext.

EventSequenceDocument is also a DOM adapter for the "EventSequence" type, so the tree root DomNode can be adapted to EventSequenceDocument.

The EventSequenceDocument also has some search properties set. For information about searching, see Node Searching.

The document is saved in the context's ListView for future reference:

context.ListView.Tag = document;

ListView is a property of EventSequenceContext that gets the ListView control associated with the context.

Finally, the ListView control is registered with the ControlHostService component.

Saving a Document

Save() uses the capabilities of DomXmlWriter to write the DomNode tree to an XML file. DomXmlWriter gets data model information from the schema so it knows how to write the DomNode tree to XML with its Write() method. The document is cast back to an EventSequenceDocument to get its root DomNode for Write().

public void Save(IDocument document, Uri uri)
{
    string filePath = uri.LocalPath;
    FileMode fileMode = File.Exists(filePath) ? FileMode.Truncate : FileMode.OpenOrCreate;
    using (FileStream stream = new FileStream(filePath, fileMode))
    {
        DomXmlWriter writer = new DomXmlWriter(m_schemaLoader.TypeCollection);
        EventSequenceDocument eventSequenceDocument = (EventSequenceDocument)document;
        writer.Write(eventSequenceDocument.DomNode, stream, uri);
    }
}

Closing a Document

Closing the document entails cleaning up the contexts that were used:

public void Close(IDocument document)
{
    EventSequenceContext context = Adapters.As<EventSequenceContext>(document);
    m_controlHostService.UnregisterControl(context.ListView);
    context.ControlInfo = null;

    // close all active EditingContexts in the document
    foreach (DomNode node in context.DomNode.Subtree)
        foreach (EditingContext editingContext in node.AsAll<EditingContext>())
            m_contextRegistry.RemoveContext(editingContext);

    // close the document
    m_documentRegistry.Remove(document);
}

The ListView control in the EventSequenceContext is unregistered.

Finally, any EditingContexts that were created are removed from the ContextRegistry, and the document is removed from the DocumentRegistry.

Contexts are discussed in Working With Contexts.

Editor Component Control Host Client

Editor also implements IControlHostClient for the ListView that is in the EventSequenceContext. This requires it to handle the control's activation, deactivation, and close events.

The Activate() method retrieves the EventSequenceDocument from the Tag in the ListView it was stored in and sets it as the active document in the DocumentRegistry component. It gets the EventSequenceContext by adapting the document again and sets it as the active context with the ContextRegistry:

void IControlHostClient.Activate(Control control)
{
    EventSequenceDocument document = control.Tag as EventSequenceDocument;
    if (document != null)
    {
        m_documentRegistry.ActiveDocument = document;

        EventSequenceContext context = document.As<EventSequenceContext>();
        m_contextRegistry.ActiveContext = context;
    }
}

Close() performs a similar operation to Activate(), but with the aim of closing things out. To close the document, it uses the IDocumentService in the variable m_documentService, provided by the StandardFileCommands component in this sample. If it succeeds, it then removes the context associated with the document from the ContextRegistry.

bool IControlHostClient.Close(Control control)
{
    bool closed = true;

    EventSequenceDocument document = control.Tag as EventSequenceDocument;
    if (document != null)
    {
        closed = m_documentService.Close(document);
        if (closed)
            m_contextRegistry.RemoveContext(document);
    }

    return closed;
}

Editors

Two components handle editing event sequences and their associated resources: EventListEditor and ResourceListEditor. Events and resources are edited by either dragging and dropping them from the palette onto a ListView or deleting them from the ListView. You can also edit events and resources with context menus in the ListView or the application's Edit menu. Event and resource attributes can also be edited, but that falls under property editing, which is all performed by the PropertyEditor, GridPropertyEditor, and PropertyEditingCommands components.

Document data is held in a tree of DomNodes. The editor components freely adapt the document's root DomNode to the various DOM adapters for the "EventSequence" type, such as EventSequenceDocument and EventSequenceContext, depending on what capabilities are needed.

EventListEditor Component

EventListEditor edits the event sequence document in a ListView on a tab. Several documents can be opened at a time, one to each tab in the central window. There is an EventSequenceContext for each document, and it owns the document's ListView.

The EventListEditor constructor sets up event handling for context changes, that is, tracking which ListView is active:

m_contextRegistry.ActiveContextChanged += new EventHandler(contextRegistry_ActiveContextChanged);
m_contextRegistry.ContextAdded += new EventHandler<ItemInsertedEventArgs<object>>(contextRegistry_ContextAdded);
m_contextRegistry.ContextRemoved += new EventHandler<ItemRemovedEventArgs<object>>(contextRegistry_ContextRemoved);

The ActiveContextChanged event handler, contextRegistry_ActiveContextChanged, gets the current EventSequenceContext and, if non-null, uses it to obtain the ListView settings and apply them to the newly active ListView:

private void contextRegistry_ActiveContextChanged(object sender, EventArgs e)
{
    if (m_eventSequenceContext != null)
        m_listViewSettings = m_eventSequenceContext.ListViewAdapter.Settings;

    m_eventSequenceContext = m_contextRegistry.GetMostRecentContext<EventSequenceContext>();
    if (m_eventSequenceContext != null)
        m_eventSequenceContext.ListViewAdapter.Settings = m_listViewSettings;
}

When an EventSequenceContext is added for a new document, this event's handler subscribes the new context's ListView to editing events:

private void contextRegistry_ContextAdded(object sender, ItemInsertedEventArgs<object> e)
{
    EventSequenceContext context = Adapters.As<EventSequenceContext>(e.Item);
    if (context != null)
    {
        context.ListView.DragOver += new DragEventHandler(listView_DragOver);
        context.ListView.DragDrop += new DragEventHandler(listView_DragDrop);
        context.ListView.MouseUp += new MouseEventHandler(listView_MouseUp);

        context.ListViewAdapter.LabelEdited +=
            new EventHandler<LabelEditedEventArgs<object>>(listViewAdapter_LabelEdited);
    }
}

When an EventSequenceContext is removed, all these events are unsubscribed from.

These editing event handlers do the actual editing. For instance, this is the listView_DragDrop method that takes care of a DragDrop event on the 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"));
    }
}

This method can treat the current EventSequenceContext as an IInstancingContext, because EventSequenceContext implements IInstancingContext. It uses this IInstancingContext to check if the drag and drop editing operation can occur, that is, can the type represented by the dragged icon be inserted into the ListView? If so, it uses a ITransactionContext to perform the insertion, so that it can be undone if desired. Note that both the CanInsert() and Insert() methods are invoked on the IInstancingContext. The methods actually invoked are in the IInstancingContext implementation of EventSequenceContext, discussed in EventSequenceContext Class. Also note that the ITransactionContext can be used here, because one of the classes EventSequenceContext derives from implements ITransactionContext, as mentioned in EventSequenceContext Class.

The mouse up event is handled by listView_MouseUp and checks for a right-button click to determine whether to display a context menu:

private void listView_MouseUp(object sender, MouseEventArgs e)
{
    Control control = sender as Control;
    object target = null; // TODO
    if (e.Button == MouseButtons.Right)
    {
        IEnumerable<object> commands =
            ContextMenuCommandProvider.GetCommands(
                Lazies.GetValues(m_contextMenuCommandProviders), m_eventSequenceContext, target);

        Point screenPoint = control.PointToScreen(new Point(e.X, e.Y));
        m_commandService.RunContextMenu(commands, screenPoint);
    }
}

The method uses ContextMenuCommandProvider.GetCommands() to get any context commands. This is an extension method for the IContextMenuCommandProvider interface for providers of context menu commands. There are two providers implementing IContextMenuCommandProvider in this sample: StandardEditCommands that implements the standard Edit menu's Cut, Copy, Paste, and Delete commands; and PropertyEditingCommands that provides context commands for property editors. In this case, StandardEditCommands is the provider; PropertyEditingCommands provides context menu items in the property editors. These commands are displayed in a context menu by invoking RunContextMenu() on the CommandService component.

ResourceListEditor Component

This component performs editing operations on the Resources ListView control in the dialog, displaying the resources for the currently selected event. You can also drag resources from the palette onto this ListView and edit them.

ResourceListEditor is very similar to EventListEditor and does pretty much the same things. This editor tracks editing events on a ListView, such as drag and drop, and performs the operation when valid.

What differs is that there is only one ListView control. Instead of belonging to a context, this ListView is owned by the editor itself. This component's IInitializable.Initialize() method creates the ListView and initializes it: sets its properties, subscribes to editing events, and registers it with the ControlHostService component, very similarly to how EventSequenceContext sets up its ListView. It also uses the SettingsServices component to persist its ListView settings.

void IInitializable.Initialize()
{
    m_resourcesListView = new ListView();
    m_resourcesListView.AllowDrop = true;
    m_resourcesListView.MultiSelect = true;
    m_resourcesListView.AllowColumnReorder = true;
    m_resourcesListView.LabelEdit = true;
    m_resourcesListView.Dock = DockStyle.Fill;

    m_resourcesListView.DragOver += new DragEventHandler(resourcesListView_DragOver);
    m_resourcesListView.DragDrop += new DragEventHandler(resourcesListView_DragDrop);
    m_resourcesListView.MouseUp += new MouseEventHandler(resourcesListView_MouseUp);
    m_resourcesListViewAdapter = new ListViewAdapter(m_resourcesListView);
    m_resourcesListViewAdapter.LabelEdited +=
        new EventHandler<LabelEditedEventArgs<object>>(resourcesListViewAdapter_LabelEdited);

    m_resourcesControlInfo = new ControlInfo(
        Localizer.Localize("Resources"),
        Localizer.Localize("Resources for selected Event"),
        StandardControlGroup.Bottom);

    m_controlHostService.RegisterControl(m_resourcesListView, m_resourcesControlInfo, this);

    if (m_settingsService != null)
    {
        SettingsServices.RegisterSettings(
            m_settingsService,
            this,
            new BoundPropertyDescriptor(this, () => ListViewSettings, "ListViewSettings", "", "")
        );
    }
}

Because this ListView shows the resources for the currently selected event, this component must track context changes. The ResourceListEditor constructor subscribes to the ActiveContextChanged event on the ContextRegistry. Its handler, contextRegistry_ActiveContextChanged, gets the current EventSequenceContext and subscribes to its SelectionChanged event, so that it is informed of event selection changes in the currently active document. It then calls UpdateEvent() to update the ResourceListEditor's ListView. UpdateEvent() determines the last selected event in the current event sequence document:

private void UpdateEvent()
{
    Event nextEvent = null;
    if (m_eventSequenceContext != null)
        nextEvent = m_eventSequenceContext.Selection.GetLastSelected<Event>();

    if (m_event != nextEvent)
    {
        // remove last event's editing context in case it was activated
        if (m_event != null)
            m_contextRegistry.RemoveContext(m_event.Cast<EventContext>());

        m_event = nextEvent;

        // get next event's editing context and bind to resources list view
        EventContext eventContext = null;
        if (nextEvent != null)
            eventContext = nextEvent.Cast<EventContext>();

        m_resourcesListViewAdapter.ListView = eventContext;
    }
}

In this method, Event refers to event data that is edited by this sample, not a subscribable event.

Because application data is in an ATF DOM, an event object is represented by a DomNode. Such a DomNode can be adapted to both Event and EventContext DOM node adapters, as indicated by this definition in the SchemaLoader for the "Event" type:

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

Both these adaptations are used in the UpdateEvent() method. This method gets the last selected event item in the current EventSequenceContext's ListView as an Event in this line:

nextEvent = m_eventSequenceContext.Selection.GetLastSelected<Event>();

If nextEvent is different from the previous Event, m_event, then m_event is adapted to EventContext and removed from the ContextRegistry:

m_contextRegistry.RemoveContext(m_event.Cast<EventContext>());

Finally, the last selected event, nextEvent, is adapted to EventContext and used to update the ResourceListEditor's ListView:

if (nextEvent != null)
    eventContext = nextEvent.Cast<EventContext>();

m_resourcesListViewAdapter.ListView = eventContext;

This last line sets the ListView property, the list data in the ListView. This property is a IListView and EventContext implements IListView, so this assignment works.

Node Searching

SimpleDOMEditor employs two forms of DomNode searching, provided by two components:

  • DomNodeNameSearchService: a component in SimpleDOMEditor for searching and replacing DomNode names in the currently active document.
  • DomNodePropertySearchService: an ATF component that searches for and replaces DomNode names and attribute values using a regular expression in the currently active document.
Both these components use ATF DomNode searching controls. Searching also uses the ATF DomNodeQueryable DOM adapter.

DomNodeQueryable DOM Adapter

DomNodeQueryable is defined as a DOM adapter for the "EventSequence" type in SchemaLoader, so any node in the tree can be searched:

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

DomNodeQueryable implements several context interfaces to handle searching, displaying results, and replacing results:

  • IQueryableContext: Define a Query() method to enumerate objects that satisfy the conditions of search predicates.
  • IQueryableResultContext: Access the results of a query and be notified when these results change.
  • IQueryableReplaceContext: Interface for classes in which containing objects can be replaced.
There are three interfaces that relate to these previous three:
  • ISearchUI: Interface for a client-defined UI that provides user controls for specifying search criteria, and for triggering a search on that criteria.
  • IResultsUI: Interface for a client-defined control that displays search results.
  • IReplaceUI: Interface for a client-defined control that allows a user to apply replacement data to search results.
These interfaces all define a Bind() method that binds a control to the data the context represents: the above contexts are DOM adapters for the root DomNode of the document.

DomNodeQueryable uses DomNodeQueryMatch to hold matching properties of a given DomNode.

DomNodeNameSearchService Component

DomNodeNameSearchService uses the DomNodeNameSearchControl for its UI. This control was created in the Forms Designer using ATF controls. This control's InitializeComponent() is, in part:

private void InitializeComponent()
{
    this.components = new System.ComponentModel.Container();
    System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DomNodeNameSearchControl));
    this.domNodeSearchTextBox1 = new Sce.Atf.Dom.DomNodeNameSearchTextBox();
    this.domNodeReplaceTextBox1 = new Sce.Atf.Applications.ReplaceTextBox();
    this.domNodeSearchResultsListView1 = new Sce.Atf.Dom.DomNodeSearchResultsListView();
    ...

These new controls are added to the main control. DomNodeNameSearchTextBox, for instance, is a simple TextBox for specifying a DomNode name search. DomNodeSearchResultsListView (derived from SearchResultsListView), also used by DomNodePropertySearchService, displays search results in a ListBox.

When the active context changes, this method is called:

private void contextRegistry_ActiveContextChanged(object sender, EventArgs e)
{
    SearchUI.Bind(m_contextRegistry.GetActiveContext<IQueryableContext>());
    ResultsUI.Bind(m_contextRegistry.GetActiveContext<IQueryableResultContext>());
    ReplaceUI.Bind(m_contextRegistry.GetActiveContext<IQueryableReplaceContext>());
}

This method binds the controls to the data of the currently active context, so the controls work with the active data.

DomNodePropertySearchService Component

DomNodePropertySearchService offers a more powerful search than DomNodeNameSearchService. Its constructor creates a UserControl and adds ATF controls that perform the DomNode search: DomNodeSearchToolStrip, DomNodeReplaceToolStrip, and DomNodeSearchResultsListView. For instance, DomNodeSearchToolStrip provides a ToolStrip for specifying a search on DomNodes for both the name and other attributes, using regular expressions.

public DomNodePropertySearchService(
    IContextRegistry contextRegistry,
    IControlHostService controlHostService)
{
    m_contextRegistry = contextRegistry;
    m_controlHostService = controlHostService;

    // define root control
    m_rootControl = new UserControl();
    m_rootControl.Name = "Search and Replace";
    m_rootControl.SuspendLayout();
    m_rootControl.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;

    // Create and add the search input control
    SearchUI = new DomNodeSearchToolStrip();
    SearchUI.Control.Dock = DockStyle.None;
    m_rootControl.Controls.Add(SearchUI.Control);
    SearchUI.UIChanged += UIElement_Changed;

    // Create and add the replace input control
    ReplaceUI = new DomNodeReplaceToolStrip();
    ReplaceUI.Control.Dock = DockStyle.None;
    m_rootControl.Controls.Add(ReplaceUI.Control);
    ReplaceUI.UIChanged += UIElement_Changed;

    // Create and add the results output control
    ResultsUI = new DomNodeSearchResultsListView(m_contextRegistry);
    ResultsUI.Control.Dock = DockStyle.None;
    m_rootControl.Controls.Add(ResultsUI.Control);
    ResultsUI.UIChanged += UIElement_Changed;
    ...

Using a Sub Selection Context

Search results are displayed in the ListBox of a DomNodeSearchResultsListView for both search components.

The following illustration shows a name search using DomNodeNameSearchService, finding one DomNode and displaying it in its ListView. When the found node is selected in this ListView hit list, the node's properties are displayed in both property editors. In addition, the "Name" value of the event that was found is highlighted in the property editors, which is the subselection.

These search components use the EventSequenceContext for a subselection context, so an EventSequenceContext must be the active context for these components' controls to be active. This means the main ListView must be active, rather than the Resources ListView, because the main ListView uses an EventSequenceContext, but the Resources ListView uses EventContext.

The PropertyView class provides much of the capability of the property editors, as noted in Property Editor Containers. When a search result is selected in DomNodeSearchResultsListView, this selection change event results in the following code being executed in PropertyView:

ISubSelectionContext selectionContext = m_editingContext.As<ISubSelectionContext>();
ISelectionContext subSelectionContext = (selectionContext != null) ? selectionContext.SubSelectionContext : null;
if (subSelectionContext != null)
    subSelectionContext.SelectionChanged += subSelectionContext_SelectionChanged;

The EventSequenceContext in m_editingContext is the currently active context, and it implements ISubSelectionContext. This means that neither selectionContext nor subSelectionContext is null, so the (sub)selection event is subscribed to, with the handler subSelectionContext_SelectionChanged. This subselection change event is raised by the selection_Changed method in GenericSelectionContext when the selection changes in the DomNodeSearchResultsListView. For details on GenericSelectionContext, see ISubSelectionContext Interface and GenericSelectionContext Class.

The handler subSelectionContext_SelectionChanged invalidates and refreshes the editing controls. It also calls PropertyView.OnSubSelectionChanged(), which selects the property that is in the subselection, that is, the property that was found in the search. This results in the attribute standing out in the property editor, as seen in the previous illustration. In the Property Editor window, the name of the attribute ("Name") is highlighted; in the Grid Property Editor window, the attribute's value is marked bold.

Topics in this section

Clone this wiki locally