X
  • March 31, 2014

Dynamically Creating Menu Items (Part 1)

Geertjan Wielenga
Product Manager

Let's set up an infrastructure for dynamically creating menu items in a NetBeans Platform application. We start with this, i.e., a simple skeleton. We have an Action presented as a JMenu over which we have control via Presenter.Menu, together with a LookupListener that we're going to use for listening to the Lookup for objects of interest:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.actions.Presenter;
@ActionID(category = "Shortcuts", id = "org.bla.core.DynamicShortcutMenu")
@ActionReference(path = "Menu/Shortcuts")
public class DynamicShortcutMenu
implements ActionListener, Presenter.Menu, LookupListener {
JMenu menu = new JMenu();
@Override
public JMenuItem getMenuPresenter() {
return menu;
}
@Override
public void resultChanged(LookupEvent le) {
}
@Override
public void actionPerformed(ActionEvent e) {
// not used
}
}

Next, let's develop our LookupListener further so that we can listen for new objects of interest. What the "object of interest" is, is up to you. Let's create an interface named "Clickable" as the currency, i.e., the communication mechanism that will be used to communicate with the Action that we have created above, i.e., to tell it that new menu items are needed.

import javax.swing.AbstractAction;
public interface Clickable {
AbstractAction getAbstractAction();
}

OK. We have an interface that is a wrapper around an AbstractAction. Later on, we'll publish implementations of this interface.

For the moment, let's focus on changing the earlier code so that we can consume the implementations, once those implementations are published.

@ActionID(category = "Shortcuts", id = "org.bla.core.DynamicShortcutMenu")
@ActionRegistration(displayName = "not-used", lazy = false)
@ActionReference(path = "Menu/Shortcuts")
public class DynamicShortcutMenu extends AbstractAction
implements ContextAwareAction, Presenter.Menu, LookupListener {
private final Lookup.Result<Clickable> clickables;
private JMenu menu = new JMenu("Dynamic");
public DynamicShortcutMenu() {
this(Utilities.actionsGlobalContext());
}
public DynamicShortcutMenu(Lookup lkp) {
clickables = lkp.lookupResult(Clickable.class);
clickables.addLookupListener(
WeakListeners.create(LookupListener.class, this, clickables));
}
@Override
public JMenuItem getMenuPresenter() {
return menu;
}
@Override
public void resultChanged(LookupEvent le) {
for (Clickable c : clickables.allInstances()){
menu.add(c.getAbstractAction());
}
}
@Override
public void actionPerformed(ActionEvent e) {
// not used
}
@Override
public Action createContextAwareInstance(Lookup lkp) {
return new DynamicShortcutMenu(lkp);
}
}

Great. We're now listening to the global lookup for Clickables and then adding their AbstractActions to the menu we created above.

Now that we know how to consume Clickables, let's learn how to publish a Clickable. Below you see a TopComponent that contains a JButton. When clicked, the JButton publishes a Clickable into the Lookup of the TopComponent. At that stage, the Action code above causes a new JMenuItem to be added. Note that at no point is a JMenuItem removed, i.e., when selection changes to a different TopComponent, the JMenuItem added via the code below will still be in the dynamic menu, which is probably how you want things to be anyway.

@TopComponent.Description(
preferredID = "SomeTopComponent",
persistenceType = TopComponent.PERSISTENCE_ALWAYS
)
@TopComponent.Registration(
mode = "explorer",
openAtStartup = true)
public class SomeTopComponent extends TopComponent {
private DateFormat formatter = new SimpleDateFormat("HH:mm:ss");
private InstanceContent ic = new InstanceContent();
public SomeTopComponent() {
setDisplayName("Some");
setLayout(new BorderLayout());
add(new JButton(new AbstractAction("Add new menu item to shortcuts") {
@Override
public void actionPerformed(ActionEvent e) {
ic.set(Collections.singletonList(new SomeClickable()), null);
}
}), BorderLayout.NORTH);
associateLookup(new AbstractLookup(ic));
}
private class SomeClickable implements Clickable {
@Override
public AbstractAction getAbstractAction() {
final String formatted = formatter.format(System.currentTimeMillis());
return new AbstractAction("Added at: " + formatted) {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "hello from " + formatted);
}
};
}
}
}

Here's the result.

Of course, this throws up some new questions. Especially, how to achieve this result for a menu within the menubar, rather than a submenu within a menu within the menubar. I.e., as you can see above, this only solves the situation where a third-level menu needs to be dynamic. But what about if the menu "Dynamic" above needs to itself be dynamic? And, even more complexly, what if the "Shortcuts" menu should, itself, be dynamic? See part 2 for the solutions to these questions.

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha
Oracle

Integrated Cloud Applications & Platform Services