Dr. Stefan Winkler
freier Softwareentwickler und IT-Berater

Following a reader's comment on my earlier post, I found the new API (Extension point description of org.eclipse.ui.menus) which unifies all command contributions to Eclipse menus, views, objects, toolbars etc.

With this example it requires a lot less code to add an action (well, the new API uses commands) which is visible or enabled dynamically based on an object property. However, at the same time, we have to do quite a bit more extension configurations: Instead of a single declaration of an org.eclipse.ui.popupMenus extension, we need all this:

  • A command (which only declares the abstract concept of the semantic behavior):
  • <extension point="org.eclipse.ui.commands">
      <command
            description="Do something with a model element"
            id="my.command.id"
            name="Do Something">
      </command>
    </extension>
  • A handler (which implements the behavior - the equivalent to what used to be the ActionDelegate):
  • <extension point="org.eclipse.ui.handlers">
      <handler
            class="my.contribution.MyCommandHandler"
            commandId="my.command.id">
      </handler>
    </extension>
  • A contribution (which connects the command to a menu or action bar):
  • <extension point="org.eclipse.ui.menus">
      <menuContribution locationURI="popup:org.eclipse.ui.popup.any?after=additions">
         <command
               commandId="my.command.id"
               style="push">
            <visibleWhen>
                <with variable="activeMenuSelection">
                   <iterate>
                      <instanceof
                         value="my.model.MyObject">
                      </instanceof>
                   </iterate>
               </with>
            </visibleWhen>
         </command>
      </menuContribution>
    </extension>

Note that the locationURI offers a lot of options - the case above simulates the objectContribution behavior, as the command is added to any popup menu and the visibility is determined by the selected objects' types. Instead of behaving like an objectContribution, we can simply change the locationURI to menu:<view-or-menu-id> or to toolbar:<view-or-toolbar-id> in order to contribute the command to a view's menu or toolbar, respectively. Naturally, in these cases, we don't need the visibleWhen constraint. The command handler implemtation is a lot simpler than the action delegate handler, as the abstract class org.eclipse.core.commands.AbstractHandler does all the extra work (listener implementation etc.) for us. The only method, we have to implement ourselves is execute():

package my.contribution;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.handlers.HandlerUtil;

public class MyCommandHandler extends AbstractHandler {

    @Override
    public Object execute(ExecutionEvent event) throws ExecutionException {

        IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelectionChecked(event);
        MyObject object = (MyObject) selection.getFirstElement();

        // do something with MyObject
    }

}

Again, until here, we only implemented a command which is enabled always. Now, if we want to enable the command only, if the selected model element fulfills a certain constraint, in the old API we needed an adapter. In the new API, the adapter is made implicit using the concept of a PropertyTester. A PropertyTester can be used to add custom testable properties to arbitrary classes. For our example, the declaration and looks like this:

<extension point="org.eclipse.core.expressions.propertyTesters">
      <propertyTester
            class="my.package.MyObjectPropertyTester"
            id="my.proptester.id"
            namespace="my.property.namespace"
            properties="type"
            type="my.model.MyObject">
      </propertyTester>
</extension>

and the implementation is as follows:

package de.feuh.st.requipse.project;

import org.eclipse.core.expressions.PropertyTester;

public class ArtifactPropertyTester extends PropertyTester {

    @Override
    public boolean test(Object receiver, String property, Object[] args,
            Object expectedValue) {

        MyObject obj = (MyObject)receiver;
        if(property.equals("type")) {
            return expectedValue.equals(obj.getType());
        }
        else {
            return false;
        }
    }
}

This way, we have defined a property called type in the namespace my.property.namespace which applies to objects which are instance of my.model.MyObject. The implementation then compares the value of the object's getType() method to a given expected value and returns the result. As a final step, we have to use the PropertyTester in our handler declaration. We enhance the handler as follows:

<extension point="org.eclipse.ui.handlers">
  <handler
        class="my.contribution.MyCommandHandler"
        commandId="my.command.id">
     <enabledWhen>
        <with variable="selection">
           <iterate
                 ifEmpty="false"
                 operator="and">
              <test
                    property="my.property.namespace.type"
                    value="FOO">
              </test>
           </iterate>
        </with>
     </enabledWhen>
  </handler>
</extension>

This enables the command only if all of the selected objects return "FOO" for getType().

Further Reading: