-
Notifications
You must be signed in to change notification settings - Fork 8
Creating a Dynamically Populated Toolbar Menu
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):
I found the following the resources that attempt to explain how to do this:
- https://www.vogella.com/tutorials/EclipseCommands/article.html
- https://stackoverflow.com/questions/29391336/eclipse-how-to-get-the-menumanager-for-a-specific-menu-id-defined-in-plugin-xml
- https://wiki.eclipse.org/Menu_Contributions/Populating_a_dynamic_submenu
- https://wiki.eclipse.org/FAQ_How_do_I_make_menus_with_dynamic_contents%3F
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 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
.
- The toolbar is located (by default) after the toolbar
- Inside the
toolbar
we create acommand
namedorg.osate.ui.rerunLastAnalysis
.- The command
style
must bepulldown
.
- The command
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 thecommand
declared above. - The
dynamic
element references aclass
that is used to provide the menu contents. This class must extendorg.eclipse.jface.action.ContributionItem
.
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.
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;
}
}
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:- Call
setRemoveAllWhenShown(true)
on the parent of the contribution (which in this case is aMenuManager
object). This makes so that the menu is emptied whenever it is about to be shown. - 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.
- Call
- 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.