Skip to content

Context Registry

Gary edited this page Aug 27, 2014 · 1 revision

Table of Contents

The context registry tracks all context interface or class objects in an application, maintaining a collection of contexts. In particular, the registry tracks the active context, which is usually determined by the currently active control. The IContextRegistry interface provides the framework to do all of this.

IContextRegistry Interface

IContextRegistry contains the following items that help an application keep track of contexts, including the active one:

  • Contexts property: Get an enumeration of all open contexts, that is, all objects that implement context interfaces and have been added to the context registry.
  • ActiveContext property: Get or set the active context.
  • GetActiveContext<T>(): Get the active context as the given type.
  • GetMostRecentContext<T>(): Get the most recently active context of the given type, which may not be the same as ActiveContext.
  • RemoveContext(): Remove the given context from the registry.
  • ActiveContextChanging and ActiveContextChanged: Events before and after the active context changes.
  • ContextAdded and ContextRemoved: Events to indicate a context has been added to or removed from the registry.
Setting the ActiveContext property also adds the context to the registry; there is no method to simply add a context to the registry.

ContextRegistry Component

ATF's ContextRegistry component implements the IContextRegistry interface. Most applications need to use this component or one like it, so they can determine what operations are meaningful in any given context. You can create your own ContextRegistry component by implementing the IContextRegistry interface.

ContextRegistry uses AdaptableActiveCollection, an extension of ActiveCollection, as a container for contexts. ActiveCollection handles the collection per se. AdaptableActiveCollection allows adapting contexts to other types, because a given context object may implement several context interfaces.

The ContextRegistry is often passed as one of the parameters in the ImportingConstructor of components used in an application. The constructor can then save this instance for later use. The ContextRegistry object can also be obtained other ways, as with the System.ComponentModel.Composition.Hosting.ExportProvider class, discussed in How MEF is Used in ATF.

Adding Contexts

As previously mentioned, IContextRegistry doesn't have a method to add contexts to the registry; setting the active context adds that context to the registry.

For instance, the Editor class of the ATF Simple DOM Editor Sample creates and saves documents that contain sequences of events. Editor also serves as the control host client for the ListView control that displays document data. When the ListView is activated by the user clicking on it, the control's Activate() method is called:

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

This method adapts the EventSequenceDocument document to a context for the event sequence, EventSequenceContext, and then sets the IContextRegistry.ActiveContext property to this context to make it the active context. If this context is not already in the context registry, it is added.

A control becoming active is a common way to set the active context

Removing Contexts

When a context is no longer needed, it should be removed from the registry. For instance, a context associated with a document may be removed from the registry once the document is closed. The Editor class in the ATF Simple DOM Editor Sample does precisely that when the document successfully closes:

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

Subscribing to IContextRegistry Events

An application often needs to track context changes, so it subscribes to IContextRegistry events. A component's constructor or IInitializable.Initialize() method could subscribe to events in IContextRegistry.

The EventListEditor component of the ATF Simple DOM Editor Sample tracks contexts and also handles drag and drop and right-click context menus for a ListView control. In this sample, the component subscribes to several IContextRegistry events in its constructor:

public EventListEditor(
    IControlHostService controlHostService,
    ICommandService commandService,
    IContextRegistry contextRegistry)
{
    m_controlHostService = controlHostService;
    m_commandService = commandService;
    m_contextRegistry = contextRegistry;

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

In this case, the ContextAdded event handler, contextRegistry_ContextAdded, takes the opportunity to subscribe to various events on the ListView control associated with the context. The ContextRemoved handling method unsubscribes from these events.

For more information on context handling in this sample, see Simple DOM Editor Programming Discussion.

In the following example, the DomRecorder component initially obtains the ContextRegistry instance using a MEF import:

[Import(AllowDefault = true)]
public IContextRegistry ContextRegistry
{
    get { return m_contextRegistry; }
    set
    {
        if (m_contextRegistry != null)
            m_contextRegistry.ActiveContextChanged -= m_contextRegistry_ActiveContextChanged;

        m_contextRegistry = value;

        if (m_contextRegistry != null)
            m_contextRegistry.ActiveContextChanged += m_contextRegistry_ActiveContextChanged;
    }
}
...
private void m_contextRegistry_ActiveContextChanged(object sender, EventArgs e)
{
    ValidationContext = m_contextRegistry.ActiveContext.As<IValidationContext>();
}

After the active context changes, the handler m_contextRegistry_ActiveContextChanged sets the ValidationContext property to the active context, adapted to an IValidationContext. Instead of using the ActiveContext property, this event handler could also have called GetActiveContext<IValidationContext>().

Testing For Contexts

It is useful to determine whether the active context supports a given context interface, so the application can determine what operations are meaningful in the current context. For example, the RenameCommand component tests whether it can perform a rename command by checking the available context interfaces:

bool ICommandClient.CanDoCommand(object commandTag)
{
    // The dialog box is modal and not dockable, so only allow it to pop up if it can be used.
    bool canDo = false;
    if (Command.Rename.Equals(commandTag))
    {
        // Note that the ITransactionContext can be null, so we don't need to check it here.
        var selectionContext = m_contextRegistry.GetActiveContext<ISelectionContext>();
        var namingContext = m_contextRegistry.GetActiveContext<INamingContext>();

        if (selectionContext != null &&
            namingContext != null)
        {
            foreach (object item in selectionContext.Selection)
            {
                if (namingContext.CanSetName(item))
                {
                    canDo = true;
                    break;
                }
            }
        }
    }
    return canDo;
}

This method calls GetActiveContext() to adapt the active context to selection and naming context interfaces. It checks that both these contexts be non-null to be able to rename objects, which is a sensible requirement.

In this example from the StandardEditCommands component, GetActiveContext() returns an IInstancingContext interface object, which is then adapted to an ITransactionContext, so that context interface is required, too:

public void Paste()
{
    IInstancingContext instancingContext = m_contextRegistry.GetActiveContext<IInstancingContext>();
    if (instancingContext != null &&
        instancingContext.CanInsert(Clipboard))
    {
        ITransactionContext transactionContext = instancingContext.As<ITransactionContext>();
        transactionContext.DoTransaction(
            delegate
            {
                instancingContext.Insert(Clipboard);
            }, CommandInfo.EditPaste.MenuText);

        OnPasted(EventArgs.Empty);
    }
}

Note that GetActiveContext() returns the actual context object. It is first adapted to an IInstancingContext and then adapted to ITransactionContext. The object's underlying class must therefore implement ITransactionContext for this to work.

Topics in this section

Clone this wiki locally