Geertjan's Blog

  • June 29, 2010

How to Centralize the Management of NetBeans Platform Actions (Part 1)

Geertjan Wielenga
Product Manager
When you use the Window wizard in the IDE to create a new TopComponent, you'll find an Action that looks something like this, registered in your layer.xml file, without any Java source file for the Action class being created in your module:
<folder name="Actions">
<folder name="Window">
<file name="org-netbeans-feedreader-FeedAction.instance">
<attr name="instanceCreate" methodvalue="org.openide.windows.TopComponent.openAction"/>
<attr name="component" methodvalue="org.netbeans.feedreader.FeedTopComponent.getDefault"/>
<attr name="displayName" bundlevalue="org.netbeans.feedreader.Bundle#CTL_FeedAction"/>
<attr name="iconBase" stringvalue="org/netbeans/feedreader/rss16.gif"/>

What's the above all about?

The starting point for understanding the above is, as always, the NetBeans API Javadoc. In the snippet above, you can see that the above Action will create an instance of "openAction", which is defined in the TopComponent class. Here's the Javadoc for the "openAction":


So, the TopComponent class has, within its own definition, an openAction method that requires certain values. Those values are set in the layer file. Via an internal class (XMLMapAttr.java) the connection between the layer file and the Action is established.

So, let's now make our own Action and use it from the layer, in the same way as is done by default for the "openAction" in the TopComponent class.

  1. We'll start with the Feed Reader sample that is part of your NetBeans IDE distribution. Go to the Samples category in the New Project wizard and create the Feed Reader sample that you'll find there.

  2. Now we'll add a new module, which will provide API classes for the application. Name the module FeedReaderAPI, with code name base "org.feedreader.api". Set dependencies on Lookup, UI Utilities API, Utilities API, and Window System API.

  3. Use the "New Java Class" template to create a class named "Utils". In there, define a new "closeAction", which I simply copied and then modified from the "openAction" in the TopComponent definition:
    public class Utils {
    static Action closeAction(Map map) {
    return Actions.alwaysEnabled(
    new CloseComponentAction(map),
    (String) map.get("displayName"), // NOI18N
    (String) map.get("iconBase"), // NOI18N
    Boolean.TRUE.equals(map.get("noIconInMenu")) // NOI18N
    private static class CloseComponentAction implements ActionListener {
    private TopComponent component;
    private final Map map;
    CloseComponentAction(TopComponent component) {
    assert component != null; //to diagnose #185355
    this.component = component;
    map = null;
    CloseComponentAction(Map map) {
    this.map = map;
    private TopComponent getTopComponent() {
    assert EventQueue.isDispatchThread();
    if (component != null) {
    return component;
    component = (TopComponent) map.get("component"); // NOI18N
    assert component != null : "Component cannot be created for " + map;
    return component;
    public void actionPerformed(ActionEvent e) {
    TopComponent win = getTopComponent();

    See part 2 or read Jesse's critique in the comments to this blog entry: The code above is too complicated and could be simplified very easily.

    What is the "map" referred to above? You don't need to think about that, other than to know that those are the attribute values provided by the layer (in the next step), since the internal XMLMapAttr class handles the actual mapping for you. So that's why there's no place where I am (in my own code in this blog entry) using FileUtil.getConfigFile, or anything like that, since the XMLMapAttr does that under the hood, the connection being established via the "instanceCreate" attribute in the layer (as shown in the next step).

    Instead of a Utils class, you could create a FeedReaderBaseTopComponent, with a number of Actions. I.e., this FeedReaderBaseTopComponent would extend TopComponent and provide a set of Actions (and other generic features) that other TopComponents within the FeedReader application could reuse.

  4. Now open the "FeedReader" module. In the layer.xml file, register a new Action, pointing to the above "closeAction":
    <folder name="Actions">
    <folder name="Window">
    <file name="org-netbeans-feedreader-FeedAction2.instance">
    <attr name="instanceCreate" methodvalue="org.feedreader.api.Utils.closeAction"/>
    <attr name="component" methodvalue="org.netbeans.feedreader.FeedTopComponent.getDefault"/>
    <attr name="displayName" bundlevalue="org.netbeans.feedreader.Bundle#CTL_CloseFeedAction"/>
    <attr name="iconBase" stringvalue="org/netbeans/feedreader/rss16.gif"/>

What's the benefit of all this? Well, you can, optionally, define Actions generically (e.g., "openAction" and "closeAction"), after which any module can pass any values they want to those generic Actions, from any module in the application. This approach lets you centrally manage the Actions of an application, providing consistency to the application and simplicity to its contributors.

Join the discussion

Comments ( 6 )
  • Nick Dunn Tuesday, June 29, 2010

    Thanks for this, I had pieced together that this was how the xml mechanism worked, but it's nice to read it explicitly.

    Slightly orthogonal to this article, but would you consider an article showing in detail how to create context sensitive actions that use a specific lookup (not the Utilities.actionsGlobalContext()) but are still statically registered in xml? e.g. I want an action that's disabled/enabled based on the lookup of one specific TopComponent, not the currently active window.

  • Geertjan Wielenga Tuesday, June 29, 2010

    Hi Nick, thanks for the support. About your further question, which part of it is not already discussed here?:


  • Jesse Glick Tuesday, June 29, 2010

    Your example is too complicated. There is no reason to ever call Actions.alwaysEnabled from Java code; it exists solely as a placeholder for Javadoc (since the variant which is actually used, taking Map, is not public). Just make Utils.closeAction return an Action impl with the right options. There is no need for a component field, nor for a constructor taking one. In short, all you really need is:

    public static Action closeAction(final Map<String,?> params) {

    return new AbstractAction() {


    putValue(NAME, params.get("displayName"));

    // optional (handled by Actions.connect):

    putValue("iconBase", params.get("iconBase"));

    putValue("noIconInMenu", params.get("noIconInMenu"));


    public @Override void actionPerformed(ActionEvent e) {

    ((TopComponent) map.get("component")).close();




  • Eduardo Costa Tuesday, June 29, 2010

    Is this related to actions only or can be used by any \*.instance file? If so, it is an even more powerful tool!

  • Andrew Perry Tuesday, June 29, 2010

    Thanks for these helpful postings.

    I would like to echo Nick Dunn's question. I have probably misunderstood but in the dzone article and other examples the context seems to come by default from the Utilities.actionsGlobalContext(). It is not clear what the best practice method is for using a different Lookup to provide the context.

  • Geertjan Wielenga Wednesday, June 30, 2010

    Thanks Jesse for the corrections. Others, hope to get to your questions, but would recommend you ask them on the dev mailing list instead.

Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.