Which NetBeans Platform Action Class Should I Use?
Join the DZone community and get the full member experience.Join For Free
The Action system in the NetBeans Platform has become more robust with each release, however a lot of changes have taken place in this area over the past years. Though everything has consistently remained backward compatible, you probably want to use the most up to date approach when starting a new project on the NetBeans Platform or when refactoring existing code.
The hierarchy of the Action classes in the NetBeans Platform is described as follows in Heiko Boeck's "The Definitive Guide to the NetBeans Platform":
An important change since NetBeans IDE 6.7 is that it is not recommended to subclass any of the SystemAction-based classes anymore. I.e., in your code, you should not be extending or implementing any of the classes inheriting from SystemAction, that is, BooleanStateAction, CallableSystemAction, CallbackSystemAction, NodeAction, and CookieAction.
However, if you have existing actions using the above classes, and they work, there is not much reason to rewrite them using the new approach described below. Even old and no longer recommended actions based on CookieAction and NodeAction continue to work, exactly as before. In fact, there are a bunch of these actions in NetBeans IDE itself. Within the NetBeans IDE team, there are enough other things to do rather than work on replacing actions that work fine already, which is also the reason why these classes have not been deprecated. But, when new actions are created in NetBeans IDE, the new approach outlined below is used consistently, i.e., the above action classes are not introduced into the NetBeans IDE source code anymore.
Actions.* Factory Methods
Rather than directly using the abovementioned SystemAction classes in your code, you should be able to accomplish the same tasks about as easily, and at lower startup cost, using the recently introduced Actions.* factory methods. For example, for an action which should always be enabled (ignores context), use Actions.alwaysEnabled from your layer (line 2 in the snippet below):
<file name="your-pkg-action-id.instance"> <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.alwaysEnabled"/> <attr name="delegate" methodvalue="your.pkg.YourAction.factoryMethod"/> <attr name="displayName" bundlevalue="your.pkg.Bundle#key"/> <attr name="iconBase" stringvalue="your/pkg/YourImage.png"/> <!-- if desired: <attr name="noIconInMenu" boolvalue="true"/> --> <!-- if desired: <attr name="asynchronous" boolvalue="true"/> --> </file>
For an action that should callback to a key in an actionmap, use Actions.callback from your layer (line 2 below):
<file name="action-pkg-ClassName.instance"> <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.callback"/> <attr name="key" stringvalue="KeyInActionMap"/> <attr name="surviveFocusChange" boolvalue="false"/> <!-- defaults to false --> <attr name="fallback" newvalue="action.pkg.DefaultAction"/> <!-- may be missing --> <attr name="displayName" bundlevalue="your.pkg.Bundle#key"/> <attr name="iconBase" stringvalue="your/pkg/YourImage.png"/> <!-- if desired: <attr name="noIconInMenu" boolvalue="true"/> --> <!-- if desired: <attr name="asynchronous" boolvalue="true"/> --> </file>
(For more info on the above snippet, read this blog entry.)
For an action that should be contextually enabled, use Actions.context from your layer (line 2 below):
<file name="action-pkg-ClassName.instance"> <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.context"/> <attr name="type" stringvalue="org.netbeans.api.actions.Openable"/> <attr name="selectionType" stringvalue="ANY"/> <-- or EXACTLY_ONE --> <attr name="delegate" newvalue="action.pkg.YourAction"/> <!-- Similar registration like in case of "callback" action. May be missing completely: --> <attr name="key" stringvalue="KeyInActionMap"/> <attr name="surviveFocusChange" boolvalue="false"/> <attr name="displayName" bundlevalue="your.pkg.Bundle#key"/> <attr name="iconBase" stringvalue="your/pkg/YourImage.png"/> <!-- if desired: <attr name="noIconInMenu" boolvalue="true"/> --> <!-- if desired: <attr name="asynchronous" boolvalue="true"/> --> </file>
The "delegate" registered for Actions.alwaysEnabled and Actions.context can be any ActionListener (via the "newvalue" attribute) or a method within an ActionListener (via the "methodvalue" attribute). When you use the New Action wizard in the IDE, an ActionListener is created, together with the relevant layer entries (as above) for the Actions.* factory method relevant to the type of Action you have chosen to create.
Usually all metadata, such as name and icon, can be expressed in the layer ("displayName" and "iconBase"), but if you need to, e.g., dynamically change the display name, make the "delegate" be a full Action, instead of an ActionListener. Then, after the menu item has been selected for the first time, which loads the delegate, any subsequent changes to the Action's metadata, even enablement status, will be reflected normally.
Another advantage is that the above Actions.* factory methods can be declared to be asynchronous, as described here and demonstrated here.
You can, as always, register any Action directly (not using the Actions.* factory methods), but then it will be eagerly loaded. This is occasionally necessary, e.g., if your action must do some custom changes to enablement status, name, etc., even if it has not yet been invoked. But such registrations will slow down startup a little bit so should be avoided if at all possible.
Deprecated? Not Quite
The SystemAction-based classes, as started above, are not recommended to be used anymore. However, they have not been deprecated. Why is that the case? Because, in addition to their still being used in NetBeans IDE as pointed out above, in accordance with the NetBeans compatibility policy it is not possible to deprecate something that does not have a 100% replacement. The Actions.* factory methods can be considered to be a 95% replacement. Here are two things you cannot do with these factory methods, that are possible with the SystemAction-based classes:
- Fine Grained Context Sensitivity. Right now, with the Actions.* factory methods, an Action can be enabled context sensitively, based on the presence of a particular object, , for example. But what if you want enablement to be more finegrained than that, e.g., you want a property in the object to determine whether an action is enabled. That is not supported by the Actions.* factory methods. If you need this behavior, you can work directly with a CookieAction class and use its "enable(Node)" method to provide the more finegrained approach you need in this case.
- Multiple Objects in Context. If you use a CookieAction, you can specify multiple objects that need to be available for an Action to be enabled. For example, you could specify that only if both "SaveCapability" and "SelectCapability" are present will the Action be enabled. However, with the Actions.* factory methods, this is not possible. You can only require a single object (as can be seen in the layer entries above) to be available for an Action to be enabled.
Beyond the CookieAction
Be aware, though, that using CookieActions are not the only way to implement the above two scenarios. You can also implement them using a plain Action directly registered in your layer, i.e., not using any of the Actions.* factory methods. See the "Roll Your Own" scenario, as an example. As another example, the NetBeans "projectui" module registers actions with quite complex semantics: "Run Project" is enabled if there is a main project, or exactly one project in the current selection, or exactly one project open, and (in each case) if that project has an ActionProvider which supports COMMAND_RUN. It also adjusts its display name when presented as a menu item according to the selection.
What is the downside of this approach? Well, you do not get lazy loading of the Action, but this is also true of using CookieAction. The main reason to use CookieAction for this kind of thing would be that it would be easier to implement using such a subclass rather than from scratch, at least for the rare second scenario above, i.e., that of "multiple objects". Implementing the first scenario, i.e., "fine-grained" is more plausibly needed, but also more difficult to do right even with the CookieAction since, while you can override "enable(Node)" to perform additional checks, you then also need to attach and detach listeners at the right times.
There is actually a proposed replacement for these more exotic scenarios that would make them much easier to write, but as it has not yet passed review, it is currently exported only to a few friend modules. In the meantime you can implement any kind of behavior you believe you need in a global menu item or toolbar button by having the action use Utilities.actionsGlobalContext() and being careful to update your state using the correct chain of listeners.
Implementing context menu items with complex enablement semantics is much easier: if you are a ContextAwareAction, the context-aware delegate does not need to listen to anything, since it is created immediately before use and then discarded. In 6.9 it is also easy to use DynamicMenuModel.HIDE_WHEN_DISABLED to make the context menu item disappear rather than appear grayed out.
It is generally wiser to not use complex enablement logic in global presenters. Use some simple logic supported by Actions.*, err on the side of making the action be enabled rather than not, and if "actionPerformed" is called on an inappropriate context, just beep or display an informative error dialog. Performance will be better and users will not be left guessing why a menu item is sometimes disabled for no apparent reason.
Thanks to Jesse Glick and Jaroslav Tulach, who provided most of the text above.
Opinions expressed by DZone contributors are their own.