Geertjan is a DZone Zone Leader and has posted 468 posts at DZone. You can read more from them at their website. View Full User Profile

Which NetBeans Platform Action Class Should I Use?

06.01.2010
| 10955 views |
  • submit to reddit

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"/> &lt-- 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, as described here, 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. 

Upcoming Enhancements

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.

Conclusion

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.

Published at DZone with permission of its author, Geertjan Wielenga.

Comments

Nicholas Dunn replied on Thu, 2010/06/10 - 11:37am

" 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. "

 

I find this worrisome from a UI perspective.  If an action will not work, and you can determine this ahead of time, why would you keep it enabled? If you're concerned about people being confused by disabled options, couldn't you associate an explanation in the tool tip text?

Bryan Low replied on Fri, 2013/01/18 - 1:24am

Sant Ritz Launch will be accessible via Potong Pasir MRT station on the North East Line. Commuting to bugis area as well as the city area is therefore very convenient. It is also near to many eateries along the upper serangoon area as well as NEX shopping mall.

Bryan Low replied on Fri, 2013/02/01 - 10:05pm

Twin Fountains EC is also near elite schools such as Singapore American School, Greenwood Primary School, Si Ling Primary School and Qihua Primary School.

Woodlands EC

Bryan Low replied on Sun, 2013/02/24 - 4:47am

For Bartley Ridge vehicle owners, it takes less than 10 minutes to drive to the business hub and vibrant Orchard Road shopping district, via Pan Island Expressway (PIE) and Central Expressway (CTE).

http://bartleyridge.org

Venkata Aditya ... replied on Tue, 2013/03/19 - 8:10am

 Hi

        Is there any online course for NetBeans Platform?

At Ppty replied on Thu, 2013/04/18 - 7:39am

Awesome blog. I enjoyed reading your articles. This is truly a great read for me. I have bookmarked it and I am looking forward to reading new articles. Keep up the good work <a href="http://www.coralsatkeppel.net/">Corals at Keppel Bay</a>

At Ppty replied on Wed, 2013/04/24 - 11:58pm

Corals at Keppel Bay , the development is located off Telok Blangah Road, and move inwards along Keppel Bay Drive towards the sea. The development will comprise 11 blocks of residential units, with varying heights from 10-storey (nearer Telok Blangah Road) sloping to 4-storey as the development approaches sea-ward. Similar height-distribution concept as Caribbean at Keppel Bay, to ensure maximum view corridors towards sea, dock, Mount Faber. 

Desmond Tam replied on Sun, 2013/11/24 - 9:22pm

A wonderful and unique lifestyle awaits you. Please see Sengkang West Way Condo project details and floor plans for more information. Sengkang West Way Condo

Desmond Tam replied on Mon, 2013/12/16 - 11:07pm

Several buses are available near Boardwalk Residences along with shopping centers and restaurants. Boardwalk Residences is also near Waterway Point, the shopping, dining and entertainment hub which is scheduled to open in 2 years time. Also, it is right beside Punggol Waterfront. Entertainment for your loved ones and friends are therefore at your fingertips with the full condo facilities as well as the amenities near Boardwalk Residences. Boardwalk Residences

Desmond Riverba... replied on Sat, 2014/01/18 - 5:56am

The condo’s facilities provide full family entertainment needs for your family and loved ones. Indulge in a serene and tranquil lifestyle right in the heart of Sengkang. Rivertrees Residences

Lakeville Pet G... replied on Mon, 2014/02/24 - 10:56pm

Highline Residences will be accessible via Tiong Bahru MRT. Commuting to the city area is therefore very convenient and the amenities are at your finger tips. It is also right beside Tiong Bahru Plaza and Central Plaza. IMM Jurong as well as Jurong Country Club are also within a short walk. condo in

Lakeville Pet G... replied on Mon, 2014/02/24 - 11:05pm

A wonderful and unique lifestyle awaits you. Please see Highline Residences project details and floor plans for more infomation. launch new condo

Lakeville Pet G... replied on Wed, 2014/02/26 - 1:58am

The Santorini will be accessible via the upcoming Tampines West MRT Station, which is just next to it. Commuting to the city area as well as the Serangoon area is therefore very convenient. condo floor plan

Lakeville Pet G... replied on Wed, 2014/02/26 - 9:35pm

Lakeville is also near elite schools such as Fuhua Primary School and St Anthony Primary School. Yuhua Primary School and Shuqun Secondary School are also around in the area. new condo near lakeside mrt

Commonwealth Towers replied on Mon, 2014/04/07 - 9:07pm

The majority of global real estate and construction companies are not properly protected against fraud and corruption, according to the latest research from the Grant Thornton International Business Report (IBR). terraced houses

Jason Tan replied on Fri, 2014/05/02 - 7:39am

And now HDB has launched a new Cool Ideas Fund to help winning participants transform their prototype creations into workable ones which can be tested in HDB blocks. There is a possibility the prototypes could be commercialised. cdl south beach condo

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.