Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Eclipse4/RCP/FAQ

< Eclipse4‎ | RCP
Revision as of 11:55, 9 July 2013 by Olivier.prouvost.opcoach.com (Talk | contribs) (Why isn't my @Inject-able/@PostConstruct methods being injected?)

Contents

Adopting the Eclipse 4 Application Platform

How do the Eclipse 3.x and 4.x programming models differ?

Conceptually, the models aren't very different. The Eclipse 4 programming model is strongly influenced by the Eclipse 3.x model, but rectifies some of the mistakes that were only realized in hindsight. If you are a proficient Eclipse 3.x RCP developer, then most concepts and approaches will be fairly familiar.

Handlers

With E3.x, the handlers were in a flat global namespace. The handler service used activeWhen expressions to choose the most specific handler for the current situation (e.g., active when the activePartId = xxx).

With E4.x, handlers can be installed on parts, windows, as well as globally on the MApplication. Handler look up starts from the active part and proceeds upwards. So many of the uses for the activeWhen expressions disappeared. Enablement expressions are now handled by the handler class itself through @CanExecute methods.

Parts

Eclipse 4 has no formal notion of an 'editor', though one could be defined by virtue of the EMF-based model. Eclipse 4 instead distinguishes between a Part and an InputPart. An input part has a URI to provide the input and can also be marked as dirty.

Lazy Instantiantion

Most classes referenced by model objects are immediately instantiated on the rendering of the model. This means that bundles contributing to the UI will be invariably activated on startup.


How would I accomplish X in Eclipse 4?

The following snippets show how to access various services from pure E4 components (created using injection). These snippets cannot be used directly from Eclipse 3.x parts using the E4 Compatibility Layer as these parts are not injected.

Accessing the status line

Eclipse 3.x Eclipse 4.0
getViewSite()
  .getActionsBars()
      .getStatusLineManager()
          .setMessage(msg);
@Inject
IStatusLineManager statusLine;
...
statusLine.setMessage(msg);

Associating help context with a control

getSite()
  .getWorkbenchWindow()
    .getWorkbench()
       .getHelpSystem().setHelp(
               viewer.getControl(), some_id)
@Inject
IWorkbenchHelpSystem helpSystem;
...
helpSystem.setHelp(
        viewer.getControl(), some_id);

Handling errors and exceptions

try {
    ...
} catch (Exception ex) {
    IStatus status = new Status(
       IStatus.ERROR, "plugin-id",
       "Error while ...", ex);
    StatusManager.getManager()
        .handle(status, StatusManager.SHOW);
}
@Inject
StatusReporter statusReporter;
...
try{
    ...
} catch (Exception ex) {
    statusReporter.show("Error while ...", ex);
}

Accessing preference values

IPreferenceStore store =
    IDEWorkbenchPlugin.getDefault()
        .getPreferenceStore();
boolean saveBeforeBuild = store
    .getBoolean(SAVE_BEFORE_BUILD);
@Inject @Preference(SAVE_BEFORE_BUILD)
boolean saveBeforeBuild;
IPreferenceStore store =
    IDEWorkbenchPlugin.getDefault()
        .getPreferenceStore();
store.putBoolean(SAVE_BEFORE_BUILD, false);
@Inject @Preference
IEclipsePreferences prefs;
...
prefs.setBoolean(SAVE_BEFORE_BUILD, false);

The E4 Model

What is an xmi:id? How is it different from the elementId?

if you look at the contents of an .e4xmi file, you'll see that every object has a unique xmi:id attribute. The xmi:id are unique identifiers are internal to EMF and used for resolving model-level references between objects, similar to a memory reference. The elementIds are Eclipse 4 identifiers and serve to provide a well-known name to an object.

Unlike EMF's xmi:id identifiers, which are expected by EMF to be unique, Eclipse 4's elementIds do not necessarily have to be unique. For example, it may make sense for every part stack containing editors to have elementId editor-stack (though we might instead recommend using a tag). Some elementIds are expected to be unique for particular uses. For example, an MCommands elementId serves as the unique command identifier.

In the following example, notice the (anonymous) handler has a reference to the command instance. The command instance's elementId is used as the Eclipse command identifier.

  <handlers xmi:id="_385TQr5EEeGzleFI7lW1Fg"
      contributorURI="platform:/plugin/org.eclipse.platform"
      contributionURI="bundleclass://org.eclipse.e4.ui.workbench/org.eclipse.e4.ui.internal.workbench.ExitHandler"
      command="_385TTr5EEeGzleFI7lW1Fg"/>
  <commands xmi:id="_385TTr5EEeGzleFI7lW1Fg"
      elementId="e4.exit" 
      contributorURI="platform:/plugin/org.eclipse.platform"
      commandName="%command.name.exit"
      description=""/>

How do I reference an object defined in another .e4xmi?

Referencing an object defined elsewhere is often necessary, such as to provide an MHandler for a particular MCommand. To reference an object defined in a different .e4xmi you need an import, basically creating an alias that is replaced at load-time to the xmi:id of the imported object.

Ensure the object to be imported has a unique elementId, and then do the following steps in the Model Editor:

  1. Open the fragment.e4xmi
  2. Select the "Imports" section from the overview pane on the left.
  3. In the details pane, on the right, select the type of object to be imported and click the "Add" button. This adds and opens the new import in the details pane.
  4. Select the "Default" tab ({bug|384500})
  5. Provide the elementId of the desired object

Note: only model fragments can reference objects defined elsewhere. You cannot have an Application.e4xmi reference an object defined in a fragment.e4xmi.

Are identifiers (elementId) supposed to be unique?

It depends on the context in which the elementIds are being used. In practice, searches of the model are performed within some scope such as for a particular type of object (e.g., all MBindingContexts) or within some object graph (e.g., the children of an MPerspective).

The E4 model does not require that elementIds be unique. Otherwise every "File" menu in different windows would require a different identifier, which would be very annoying. But each MCommand defined on an MApplication is expected to have a unique identifier. (Note: command-identifier uniqueness is not actually enforced, but could lead to unexpected UI behaviours since there will be multiple possible command objects.) If you are attempting to import an object into a fragment, then it's important that the elementIds are unique for that type.


How do I use MPlaceholders?

How do I create an MPart from an MPartDescriptor?

Use the EPartService.

Problems on Configuration, Start-Up, and Shutdown

Why won't my application start?

E4AP products require having the following plugins:

  • org.eclipse.equinox.ds (must be started)
  • org.eclipse.equinox.event (must be started)

Note that org.eclipse.equinox.ds and org.eclipse.equinox.event must be explicitly started. In your product file, you should have a section like the following:

  <configurations>
     <plugin id="org.eclipse.core.runtime" autoStart="true" startLevel="2" />
     <plugin id="org.eclipse.equinox.ds" autoStart="true" startLevel="3" />
     <plugin id="org.eclipse.equinox.event" autoStart="true" startLevel="3" />
  </configurations>

Failure to set the auto-start levels usually manifest as runtime errors like

  Unable to acquire application service. Ensure that the org.eclipse.core.runtime bundle is resolved and started (see config.ini)

I modified my App.e4xmi/fragment.e4xmi but the changes aren't being loaded. Why?

The E4 workbench persists the current model on shutdown and, if found, restores that model on startup. Any changes in the Application.e4xmi and previously-loaded fragments will be ignored. If you're debugging, use the -persistState false option to prevent the model from being persisted on shutdown.

How can I prevent my workbench model from being saved on exit?

You can prevent the workbench from being persisted on shutdown by launching with "-persistState false" or setting the system property "persistState=false".

You can prevent the persisted workbench from being loaded on startup by launching with "-clearPersistedState" or setting the property "clearPersistedState=true"

Dependency Injection & Contexts

Why isn't my @Inject-able/@PostConstruct methods being injected?

There are typically two reasons why injection fails.

Mismatched Annotations: Ensure your bundles use Import-Package with a package version to pull in the standard annotations rather than a Require-Bundle on the javax.annotation bundle.

Import-Package: javax.inject;version="1.0.0",javax.annotation; version="1.1.0"

Basically the injector is resolving to a different PostConstruct class from your code. The reasons behind this are complex (see bug 348155 for a long discussion about the issue and the problems with various solutions). The real solution is for the OSGi Framework to annotate the VM definitions with their respective versions (bug 348630), but it's a hard problem.

Unresolvable Injections: The injector attempts to resolve objects in the context. If an object cannot be resolved in the context, and it's not marked as @Optional, then the method will not be injected. The injector does not normally log when such situations occur as it is an expected occurrence.

Two approaches to help diagnose such problems:

  • enable tracing on org.eclipse.e4.core.di for debug/injector;
  • put an exception breakpoint on InjectionException.

What services are available for injection?

See the list of provided services

How can I override a provided object?

FIXME For example, to provide an alternative StatusReporter or Logger

How do I provide singleton objects?

Typical E4AP applications have a single injector, accessible through org.eclipse.e4.core.di.InjectorFactory#getDefault(). Within this injector, any class or interface annotated with the javax.inject.Singleton will be treated as a singleton instance.

Another approach is to use a IContextFunction that checks and sets a value in the top context.

FIXME: Can the injector also be configured to bind @Singleton to a particular class?

Why am I getting a new instance of an object?

[NB: the injector's behaviour changed as of 4.2M7. After M7, objects are only created if annotated with @Creatable.]

The injector attempts to resolve objects in the context. If they are not found in the context, but the class exists, then the injector will instantiate and return a new instance providing that its injectable dependencies can be resolved.

This behaviour can be a bit confusing, so let's walk through a somewhat subtle example that frequently causes confusion to new developers with E4 and DI. Consider an E4 RCP app with two MParts, OverviewPart and DetailPart. Since the OverviewPart provides an overview of the contents shown by DetailPart, it needs to get ahold of the DetailPart. A first attempt at writing OverviewPart and DetailPart might be:

public class OverviewPart {
   @Inject private Composite detail;
   @Inject private DetailPart detail; 
 
   @PostConstruct private void init() { /* ... */ }
}
 
public class DetailPart {
   @Inject private Composite detail;
 
   @PostConstruct private void init() { /* ... */ }
}

If you try to run with this code, it seems to work — but somehow the OverviewPart and DetailPart receive the same Composite! What's wrong?

There are several problems in the code above:

  1. Objects are resolved through the context ancestry, and never through the context tree. Since MParts are siblings, an MPart will never be resolved through injection alone.
  2. Despite their names, neither OverviewPart nor DetailPart are actually MParts. They are the contributed objects of an MPart that implement the behaviour. These contributed object are not available through the injection context.
  3. Even if contributed objects could be injected, there could be several instances in the model. Consider the Eclipse IDE with multiple windows with the Outline view in each.

So how did the code above seem to work? It works because of a subtle feature of the Dependency Injector called instance autogeneration (also supported by Guice). When trying to inject a field or method argument of type T, our DI tries to resolve an object of type T using the provided context. If an object of type T cannot be found, it examines the type T: if it is a concrete class, and either has a 0-argument constructor or a constructor marked with @Inject, it will autogenerate an instance using the provided context.

So the flow looks something like this.

  1. MPart(OverviewPart) is to be rendered. A new IEclipseContext is created hanging off of the MPart(OverviewPart)'s parent.
    1. DI is requested to create OverviewPart, using the IEclipseContext for <tt>MPart(OverviewPart)
    2. OverviewPart's constructor was called.
    3. The OverviewPart instance's fields were examined for injection
      1. DI found the field for DetailPart. It tried to resolve that type in the MPart(OverviewPart)'s context, but nothing was found.
        1. DI then looked to see if DetailPart was concrete and either had a 0-argument constructor or an @Injectable constructor; it found a 0-argument constructor. DI then created an instance of DetailPart and began injecting this new DetailPart using MPart(OverviewPart)'s context. Note that DI did not create a new context for this object!
        2. DI looked to see if this new DetailPart object had any injectable fields.
          1. DI found a field of type Composite. DI checked in MPart(OverviewPart)'s context for Composite — and found an instance. But this instance was the Composite for OverviewPart. The field was injected.
        3. DI then looked for methods of the new object to be injected.
    4. DI looked for any methods in OverviewPart to be injected.
  2. The OverviewPart object is returned.

(The correct solution is to use the EModelService to find the detail part.)

Why is my widget/part not displaying? Why am I getting a new Shell?

This type of problem is another symptom of the DI autogeneration issue, and usually occurs with code like the following:

class ShowDialogHandler {
 
   @Execute
   private void showDialog(Shell shell) {
      dialog = new Dialog(shell, ...);
   }
}

As there is no Shell in the DI context, but Shell has a 0-argument constructor, the DI will create a new Shell for the purpose of injection.

The fix is to annotate the Shell request with "@Named(ACTIVE_SHELL)" to fetch the shell for the active window, as in:

   @Execute
   private void showDialog(@Named(ACTIVE_SHELL) Shell shell) { ... }

This value is set in the context for each window.

Why am I being injected with null?

Typically null values are only injected when an argument or field is marked as @Optional. But a null value will be injected if the value has previously been explicitly set to null in the context.

For example, the valueChanged() method will be injected with null:

@PostConstruct
public void init(IEclipseContext context) {
   context.set("value", null);
}
 
@Inject
public void valueChanged(@Named("value") String selection) {
   // ...
}

Why aren't my parts being injected with my value set from my bundle activator?

The context obtained using EclipseContextFactory.getServiceContext(bundleContext) is completely dissociated from the context created for an application in E4Application. If you want to populate a context such that a part can be injected, you either need to use an addon or an IContextFunction.

What is the difference between IEclipseContext#set and IEclipseContext#modify?

public class MyPart {
    @Inject IEclipseContext context;
 
    ...
 
    private void repositoryChanged(IRepository repository) {
        //  set the variable "repository" in this part's context: the value is only visible to
        // this part and any children
        context.set("repository", repository);
 
        // search up the context stack to see if the variable exists in one of the the context's 
        // ancestors; otherwise it does a set in the specified context
        context.modify("repository", repository);
    }
 
    ...
}

A placeholder can be made for #modify with IEclipseContext#declareModifiable() or a <variable> declaration in an Application.e4xmi.

Commands and Handlers

Why is my handler's @Execute not being triggered?

See "Why isn't my @Inject-able method being injected?" above.

Why is my parameterized handler not triggered?

When binding a command to a UI element (e.g., an MHandledToolItem or MHandledMenuItem), the binding must provide a parameter for each of the parameters defined by the command. The attribute names used (as of 4.2M3) for establishing a correspondance between command parameters and handler arguments can lead to some confusion.

The parameters to a command are specified as instances of MCommandParameter. The identifier for each parameter, used for matching, is taken from the elementId attribute. The name attribute is a descriptive label for the parameter.

The confusion arises as each binding parameter (an instance of MParameter) also have an elementId, but it is the name attribute that is used for matching against the command parameters. The binding parameter's elementId merely serves to identify that parameter instance within the model.

For example, consider defining a command to perform a CSS theme switch, where the theme identifier is provided as a command parameter themeId. We might configure the command (programmatically) as follows:

MCommand switchThemeCommand = MCommandsFactory.INSTANCE.createCommand();
// set the unique command id
switchThemeCommand.setElementId("command.switchTheme");   
MCommandParameter themeId = MCommandsFactory.INSTANCE.createCommandParameter();
themeId.setElementId("themeId");
themeId.setName("The Theme Identifier");
themeId.setOptional(false);
switchThemeCommand.getParameters().add(themeId);
// make the command known
app.getCommands().add(switchThemeCommand);

To configure a menu item to trigger a theme switch:

// somehow find the MCommand definition
MCommand switchThemeCommand = helper.findCommand("command.switchTheme");
MMenu themeMenu = ...;
for(ITheme theme : engine.getThemes()) {
   MHandledMenuItem menuItem = MMenuFactory.INSTANCE.createHandledMenuItem();
   menuItem.setLabel(theme.getLabel());
   menuItem.setCommand(switchThemeCommand);
   menuItem.setContributorURI("platform:/plugin/bundle-name/class-name");
   MParameter parameter = MCommandsFactory.INSTANCE.createParameter();
   // set the identifier for the corresponding command parameter
   parameter.setName("themeId");
   parameter.setValue(themeIdentifier);
   menuItem.getParameters().add(parameter);
   themeMenu.getChildren().add(menuItem);
}


Why does org.eclipse.core.commands.Command's isEnabled() and getHandler() not work?

Command's isEnabled() and getHandler() and are specific to Eclipse 3.x based API and are not supported in Eclipse 4. Hence they will always return false or null in Eclipse 4 applications. Applications should use the EHandlerService to query against a command.

UI

How do I enable Drag N Drop (DND) of parts?

The DND addon is found in the org.eclipse.e4.ui.workbench.addons.swt plugin. However it requires the compatibility layer and is not available for native E4AP applications.

Why are my CSS theming not taking effect?

Assuming that you are using the CSS theming through the 'org.eclipse.e4.ui.css.swt.theme extension point:

  • Ensure the org.eclipse.e4.ui.css.swt.theme bundle is included with its prereqs
  • Verify that a plugin.xml defines one or more themes on the org.eclipse.e4.ui.css.swt.theme extension point
  • Verify that your product specifies a "cssTheme" property, or is passed "-cssTheme" on the command-line, with a valid theme-id
  • Verify that your product specifies the applicationCSSResources property, a URI prefix to the location containing your CSS resources
    • A common symptom: your switch-theme handler cannot be injected as IThemeEngine could not be resolved
  • Verify that your CSS files have no errors: add a breakpoint to org.eclipse.e4.ui.css.core.exceptions.UnsupportedClassCSSPropertyException, org.eclipse.e4.ui.css.core.exceptions.DOMExceptionImpl, and org.w3c.css.sac.CSSException

Why is my part's selection never set as the active selection?

You must ensure that your part implements a method annotated with @Focus, and this method must set the SWT focus to one of your created widgets. Setting the focus to that widget is an essential part for triggering the change of the active selection.

We generally recommend that your part create a container composite in your constructor or @PostConstruct, and this container is the perfect widget to use for your @Focus as Composite.setFocus() traverses its children to restore focus to its child widget that last had focus. You should not use the parent composite provided to your part's constructor/@PostConstruct, though as this composite is (1) not under your control, and (2) may contain other UI elements that should not receive the focus.

Customizing and Controlling the Platform

How do I provide my own prompt-to-save when closing a part?

Put your own custom org.eclipse.e4.ui.workbench.modeling.ISaveHandler implementation somewhere in the part's context hierarchy. Note that this handler is only called if the part is already marked as dirty.

Copyright © Eclipse Foundation, Inc. All Rights Reserved.