Wednesday, July 4, 2012

Eclipse E4 Selections, Events, Context Variables

Eclipse 4.2 includes the new "E4" APIs which offer different ways of communication between portions of your Eclipse application.
The Lars Vogel article http://www.vogella.com/articles/Eclipse4Services/article.html nicely lists these, and here are some comments from trying them out.

Selections

In Eclipse 3.x, you used the ISelectionProvider:

ISelectionProvider selections = null;
selections.setSelection(new StructuredSelection(selected_item));

Obtaining the selection provider could be circuitous, and the selection had to be published as an ISelection, typically a StructuredSelection.

On the receiving end, you needed to register a listener to the selection provider and unpack the ISelection.
In Eclipse E4, there is @Inject magic at play that can be difficult to debug, but the resulting code is sure nice:

// In a "GUI creation" routine invoked via @PostConstruct,
// arguments are injected because the @Inject is implied.
// To get an ESelectionService injected into other method,
// a specific @Inject would be required.
@PostConstruct
public void createControls(final Composite parent,
    final ESelectionService selections)
{
    // Create GUI...
    // ... monitor for example selection of list.
    MyModelObject my_selected_object = ...;
    // Publish any selectd model object or array of model objects "as is".
    // No need to wrap into StructuredSelection:
    selections.setSelection(my_selected_object);
});

To receive such a selection in another piece of E4 code:

// E4 will invoke this whenever the active selection changes.
// If the active selection is instanceof MyModelObject, it will be provided.
// Otherwise, null will be sent.
@Inject
public void handleChangedSelection(
    @Optional
    @Named(IServiceConstants.ACTIVE_SELECTION)
    final MyModelObject selected)
{
    // No need to unpack a StructuredSelection.
    // May only have to check for null
    System.out.println("Received changed selection " + selected);
}

If your selection listener happens to only receive null arguments, the reason is likely that the selected object does not match the type that you want to receive. To debug this, change the argument of your selection receiver from MyModelObject to just Object and check the type of the received object.
When porting existing code, you may by accident still publish a StructuredSelection that wraps your MyModelObject class. The E4 framework will not unwrap a StructuredSelection. Just publish and receive the exact class that you want to transfer!

Context Variables

There is only one global selection mechanism. If you want to pass updates on various model items between E4 components, you can use context variables.
Publishing anything into a context variable is as easy as publishing a selection, but note that you can now name the item that you put into the context. This allows you to publish different types of information under different names, effectively having access to multiple selection services.
You can either use a string or a class reference:

@Inject
public void methodThatPublishesContextVariable(final MWindow window)
{
    MyModelObject my_selected_object = ...;

    final IEclipseContext context = window.getContext();
    context.declareModifiable("my_item");
    context.set("my_item", my_selected_object);
}

Receiving the item updates is very similar to receiving the selection:

// E4 will invoke this whenever the named context variable changes.
@Inject
public void handleChangedItem(
    @Optional
    @Named("my_item")
    final MyModelObject item)
{
    // May have to check for null
    System.out.println("Received changed item " + item);
}

It is important to remember that the context is hierarchical!
In the publishing code it is tempting to directly ask E4 to inject the context like this instead of going via the MWindow as shown before:

@Inject
public void methodThatPublishesContextVariable(final IEclipseContext context)
{
    ...
    context.set("my_item", ...);
}

This will not work as expected! The context that your part gets injected will be the context of your part. You will then publish the context variable for "my_item" within that local context, and updates are not received by other parts. By instead asking for the window to be injected and publishing to the window's context, all parts within that window will receive updates:

@Inject
public void methodThatPublishesContextVariable(final MWindow window)
{
    final IEclipseContext context = window.getContext();
    ...
    context.set("my_item", my_selected_object);
}

If you want all windows of your application to receive updates, you can even use the application's context:

@Inject
public void methodThatPublishesContextVariable(final MApplication app)
{
    final IEclipseContext context = app.getContext();
    ...
    context.set("my_item", my_selected_object);
}

The scoped, hierarchical nature of the context variable mechanism therefore allows you to control who receives the updates: The same part, all parts in the window, or everything in the application.

Event Broker

Finally, the IEventBroker provides a lower level mechanism to post and receive events. When posting events, you can decide to simply post them in a fire-and-forget mode, or send them and wait for the receivers to handle them:

class MyClass
{
    // Receive event broker.
    // Could also fetch in code via
    // IEclipseContext.get(IEventBroker.class)
    @Inject
    private IEventBroker event_broker;
    
    void codeThatSendsEvents()
    {
        MyModelObject my_object = ...;
        event_broker.post("my_object", my_object);
    }

The 'post()' shown here is the fire-and-forget way. Use 'send()' to wait for receivers to handle the event.
To receive events for the topic "my_object" that carry data of type MyModelObject, use the following:

@Inject
public void receiveEvent(
    @Optional
    @UIEventTopic("my_object") final MyModelObject item)
{
    System.out.println("Received " + item);
}

When replacing @UIEventTopic with @EventTopic, events will be received on some event handling thread. The annotation @UIEventTopic asserts that events are received on the user interface thread, which is useful if your event receiver intends to make UI modifications, for example display the event in SWT widgets.




No comments:

Post a Comment