Skip to content

Creating a Dynamically Populated Toolbar Menu

AaronGreenhouse edited this page Dec 7, 2020 · 7 revisions

This note explains how to create a toolbar component that is a dynamically populated menu. Specially, I am trying to duplicate the behavior of menus like the "Run" and "Debug" launch configuration menus. Below I described how to produce the toolbar and menu displayed here (with the "gears" icon):

Dynamic toolbar image

I found the following the resources that attempt to explain how to do this:

Ultimately, I found the article from Vogella to be the most helpful, but none of them really explain what is going on very well. I found some strange behavior in how the API of one the classes is called (see below) that could use clarification, but I none seems to available.

Create the toolbar and command

Create the toolbar and the menu command item via a menuContribution in the org.eclipse.ui.menus extension point:

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>

    <!-- ... -->

    <extension
          point="org.eclipse.ui.menus">

       <!-- ... -->

       <menuContribution
             locationURI="toolbar:org.eclipse.ui.main.toolbar?after=additions">

          <!-- ... -->

          <toolbar
                id="org.osate.ui.rerunAnalysisToolbar"
                label="Rerun analysis">
             <command
                   commandId="org.osate.ui.rerunLastAnalysis"
                   icon="icons/rerun_analysis.gif"
                   label="Rerun analysis"
                   style="pulldown"
                   tooltip="Rerun the last analysis">
             </command>
          </toolbar>

          <!-- ... -->

       </menuContribution>
    </extension>
</plugin>

Of note:

  • We create a new toolbar named org.osate.ui.rerunAnalysisToolbar.
    • The toolbar is located (by default) after the toolbar org.eclipse.ui.main.toolbar.
  • Inside the toolbar we create a command named org.osate.ui.rerunLastAnalysis.
    • The command style must be pulldown.

Populate the menu

The menu is marked as having dynamic content by adding a dynamic member to the just-defined pulldown command:

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>

    <!-- ... -->

    <extension
          point="org.eclipse.ui.menus">

       <!-- ... -->

       <menuContribution
             allPopups="false"
             locationURI="menu:org.osate.ui.rerunLastAnalysis">
          <dynamic
                class="org.osate.ui.handlers.DynamicMenuTest"
                id="org.osage.ui.rerunDynamic">
          </dynamic>
       </menuContribution>
    </extension>
</plugin>

Of note:

  • The locationURI of the menu contribution names the command declared above.
  • The dynamic element references a class that is used to provide the menu contents. This class must extend org.eclipse.jface.action.ContributionItem.

Add command handler

Add a command and handler to the toolbar item created earlier. This creates a handler that is invoked when the button in the toolbar is pressed, which is not the same thing as when the menu attache to button is displayed.

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>

    <!-- ... -->

    <extension
          point="org.eclipse.ui.commands">

       <!-- ... -->

       <command
             id="org.osate.ui.rerunLastAnalysis"
             name="Rerun Last Analysis">
       </command>
    </extension>
    <extension
          point="org.eclipse.ui.handlers">
       <handler
             class="org.osate.ui.handlers.TestHandler"
             commandId="org.osate.ui.rerunLastAnalysis">
       </handler>   
    </extension>
</plugin>

Of note:

  • The command id must match the one used in the earlier declaration.

Add the command handler implementation

Here is a our simple handler implementation for the button:

public final class TestHandler extends AbstractHandler {

	@Override
	public Object execute(ExecutionEvent event) throws ExecutionException {
		try {
			System.out.println(event.getCommand().getName());
		} catch (NotDefinedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

}

Add the dynamic menu contributor

We provide a class that adds between 1 and 10 items to the menu. Each time the menu is opened a new item is added, rolling over to 1 after ten items.

public final class DynamicMenuTest extends ContributionItem {
	private static final String ID = "org.osage.ui.rerunDynamic";

	private final IMenuListener menuListener = manager -> fillMenu(manager);

	private final class TestAction extends Action {
		public TestAction(final int id) {
			super("Action " + id);
		}

		@Override
		public void run() {
			System.out.println(getText());
		}
	}

	private final TestAction[] actions = new TestAction[10];

	public DynamicMenuTest() {
		super(ID);
		for (int i = 0; i < 10; i++) {
			actions[i] = new TestAction(i + 1);
		}
	}

	@Override
	public void fill(Menu menu, int index) {
		if (getParent() != null) {
			((MenuManager) getParent()).setRemoveAllWhenShown(true);
			((MenuManager) getParent()).addMenuListener(menuListener);
		}
	}

	private volatile int count = 0;

	private void fillMenu(final IMenuManager menuManager) {
		System.out.println("Fill menu");

		count += 1;
		if (count == 11) {
			count = 1;
		}
		for (int i = 0; i < count; i++) {
			menuManager.add(actions[i]);
		}
	}
}

Of note:

  • The id of the dynamic contribution ("org.osage.ui.rerunDynamic" in this case) must be provided to the super constructor.
  • The method fill() should do two things:
    1. Call setRemoveAllWhenShown(true) on the parent of the contribution (which in this case is a MenuManager object). This makes so that the menu is emptied whenever it is about to be shown.
    2. Register a menu listener with the parent of the contribution. This listener is invoked whenever the menu is about to show. The listener adds the items to the menu.
  • Our menu listener just calls the method fillMenu(), which adds actions to the menu.
  • It is best to avoid creating new action objects when filling the menu. Here we have an array of actions that we use over and over again. I think the main concern here is that it can be slow to create and action.

What is baffling to me is that you would think that the fill() method should do what the fillMenu() method does. Why is the menu listener necessary at all? I have no idea. In my experiments, the fill() method is called only one time: the first time the menu is opened. Doesn't make a lot of sense to me, but that is the way it is.