NetBeans Platform 6.9: Working with Actions

Exclusive offer: get 50% off this eBook here
NetBeans Platform 6.9 Developer's Guide

NetBeans Platform 6.9 Developer's Guide — Save 50%

Create professional desktop rich-client Swing applications using the world's only modular Swing application framework

$23.99    $12.00
by Jürgen Petri | August 2010 | Java Open Source Web Development

In this article we focus on "Global" Actions, that is, those that should always be enabled. These types of Actions are not very different from standard Swing Actions. For example, you might want to create "New Task" and "Edit Task" Actions that can be invoked from the menubar and toolbar of the TaskManager.

The topics covered in this article by Jürgen Petri, author of NetBeans Platform 6.9 Developer's Guide, will teach you the following:

  • How to create global Actions
  • How to add menu items
  • How to add toolbar buttons
  • How to add keyboard shortcuts

(For more resources on NetBeans, see here.)

In Swing, an Action object provides an ActionListener for Action event handling, together with additional features, such as tool tips, icons, and the Action's activated state. One aim of Swing Actions is that they should be reusable, that is, can be invoked from a menu item as well as a related toolbar button and keyboard shortcut.

The NetBeans Platform provides an Action framework enabling you to organize Actions declaratively. In many cases, you can simply reuse your existing Actions exactly as they were before you used the NetBeans Platform, once you have declared them. For more complex scenarios, you can make use of specific NetBeans Platform Action classes that offer the advantages of additional features, such as more complex displays in toolbars and support for context-sensitive help.

Preparing to work with global actions

Before you begin working with global Actions, let's make some changes to our application. It should be possible for the TaskEditorTopComponent to open for a specific task. You should therefore be able to pass a task into the TaskEditorTopComponent. Rather than the TaskEditorPanel creating a new task in its constructor, the task needs to be passed into it and made available to the TaskEditorTopComponent.

On the other hand, it may make sense for a TaskEditorTopComponent to create a new task, rather than providing an existing task, which can then be made available for editing. Therefore, the TaskEditorTopComponent should provide two constructors. If a task is passed into the TaskEditorTopComponent, the TaskEditorTopComponent and the TaskEditorPanel are initialized. If no task is passed in, a new task is created and is made available for editing.

Furthermore, it is currently only possible to edit a single task at a time. It would make sense to be able to work on several tasks at the same time in different editors. At the same time, you should make sure that the task is only opened once by the same editor. The TaskEditorTopComponent should therefore provide a method for creating new or finding existing editors. In addition, it would be useful if TaskEditorPanels were automatically closed for deleted tasks.

  1. Remove the logic for creating new tasks from the constructor of the TaskEditorPanel, along with the instance variable for storing the TaskManager, which is now redundant:

    public TaskEditorPanel() {
    initComponents();
    this.pcs = new PropertyChangeSupport(this);
    }

  2. Introduce a new method to update a task:

    public void updateTask(Task task) {
    Task oldTask = this.task;
    this.task = task;
    this.pcs.firePropertyChange(PROP_TASK, oldTask, this.task);
    this.updateForm();
    }

  3. Let us now turn to the TaskEditorTopComponent, which currently cannot be instantiated either with or without a task being provided. You now need to be able to pass a task for initializing the TaskEditorPanel. The new default constructor creates a new task with the support of a chained constructor, and passes this to the former constructor for the remaining initialization of the editor.
  4. In addition, it should now be able to return several instances of the TaskEditorTopComponent that are each responsible for a specific task. Hence, the class should be extended by a static method for creating new or finding existing instances. These instances are stored in a Map<Task, TaskEditorTopComponent> which is populated by the former constructor with newly created instances. The method checks whether the map for the given task already stores a responsible instance, and creates a new one if necessary. Additionally, this method registers a Listener on the TaskManager to close the relevant editor for deleting a task. As an instance is now responsible for a particular task this should be able to be queried, so we introduce another appropriate method. Consequently, the changes to the TaskEditorTopComponent looks as follows:

private static Map<Task, TaskEditorTopComponent> tcByTask =
new HashMap<Task, TaskEditorTopComponent>();

public static TaskEditorTopComponent findInstance(Task task) {
TaskEditorTopComponent tc = tcByTask.get(task);
if (null == tc) {
tc = new TaskEditorTopComponent(task);
}
if (null == taskMgr) {
taskMgr = Lookup.getDefault().lookup(TaskManager.class);
taskMgr.addPropertyChangeListener(newListenForRemovedNodes());
}
return tc;
}

private class ListenForRemovedNodes implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent arg0) {
if
(TaskManager.PROP_TASKLIST_REMOVE.equals
(arg0.getPropertyName())) {
Task task = (Task) arg0.getNewValue();
TaskEditorTopComponent tc = tcByTask.get(task);
if (null != tc) {
tc.close();
tcByTask.remove(task);
}
}
}
}

private TaskEditorTopComponent() {
this(Lookup.getDefault().lookup(TaskManager.class));
}

private TaskEditorTopComponent(TaskManager taskMgr) {
this((taskMgr != null) ? taskMgr.createTask() : null);
}

private TaskEditorTopComponent(Task task) {
initComponents();

// ...

((TaskEditorPanel) this.jPanel1).updateTask(task);
this.ic.add(((TaskEditorPanel) this.jPanel1).task);
this.associateLookup(new AbstractLookup(this.ic));
tcByTask.put(task, this);
}

public String getTaskId() {
Task task = ((TaskEditorPanel) this.jPanel1).task;
return (null != task) ? task.getId() : "";
}

With that our preparations are complete and you can turn to the following discussion on Actions.

NetBeans Platform 6.9 Developer's Guide Create professional desktop rich-client Swing applications using the world's only modular Swing application framework
Published: August 2010
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

(For more resources on NetBeans, see here.)

Creating global actions

Let’s start by creating an Action to add new TopLevelTasks. It will create a new Task and display it in the TaskEditorPanel. The wizard that you will use in this section will simply create a new ActionListener and register it in the layer.xml file.

  1. Right-click the TaskEditor module project node and choose New | Action. Choose Always Enabled as the Action type as shown in the following screenshot. Click Next>.

    NetBeans Platform 6.9: Working with Actions

  2. Choose Edit as the category of the Action, which determines where in the Options window's Keymap section it will be displayed.
  3. Check the Global Menu Item checkbox. Now you can specify where the Action should appear in the menu bar. Choose Edit and then choose any position you want within the Edit menu. Click Next>.

    You do not need to insert the Action in the toolbar at this time, nor assign a keyboard shortcut.

    NetBeans Platform 6.9: Working with Actions

  4. Name the Action NewTaskAction, with New as the Display Name. Optionally, specify an icon.

    NetBeans Platform 6.9: Working with Actions

  5. The actionPerformed method of the generated class NewTaskAction should create a new task and display it in a TaskEditor. Therefore, implement the method as follows:

    public void actionPerformed(ActionEvent e) {
    TaskManager taskMgr =
    Lookup.getDefault().lookup(TaskManager.class);
    if (null != taskMgr) {
    Task task = taskMgr.createTask();
    TaskEditorTopComponent win =
    TaskEditorTopComponent.findInstance(task);
    win.open();
    win.requestActive();
    }
    }

  6. Run the application again. In the Edit menu there is now a New menu item. When you select the menu item, a new task is created in the TaskEditor.

To summarize, you have used the New Action wizard to create and register a new global action.

Examining the created files

The wizard created a simple ActionListener and registered it declaratively in the layer.xml file. As a result, the Actions folder now has an Edit subfolder, with this content in the layer.xml file:

<folder name="Actions">
<folder name="Edit">
<file name="com-netbeansrcp-taskeditor-NewTaskAction.instance">
<attr name="delegate"
newvalue="com.netbeansrcp.taskeditor.NewTaskAction"/>
<attr name="displayName" bundlevalue="com.netbeansrcp.
taskeditor.Bundle#CTL_NewTaskAction"/>
<attr name="iconBase"
stringvalue="com/netbeansrcp/taskeditor/icon.png"/>
<attr name="instanceCreate"
methodvalue="org.openide.awt.Actions.alwaysEnabled"/>
<attr name="noIconInMenu" boolvalue="false"/>
</file>
</folder>
</folder>

Below the Edit folder, you see that an .instance file has been registered. That file represents an instance of the class for which it is registered. A set of attributes have been defined for the file:

Attributes Description
delegate

The task to be performed when the Action is invoked.

displayName The name of the Action, optionally pointing to a key in a bundle file.
iconBase The location of the icon displayed for the Action.
instanceCreate The instance created by the Action registration.
noIconInMenu True if the Action does not display an icon.

In addition to the Action registration, the wizard has also created a Menu registration in the layer.xml file:

<folder name="Menu">
<folder name="Edit">
<file name="com-netbeansrcp-taskeditor-NewTaskAction.shadow">
<attr name="originalFile" stringvalue="Actions/Edit/
com-netbeansrcp-taskeditor-
NewTaskAction.instance"/>
<attr name="position" intvalue="100"/>
</file>
</folder>
</folder>

Within the Edit menu, a shadow file has been registered. Shadow files do not represent an instance, but serve as a symbolic reference, which is a familiar concept from the Unix world. If the Action instance had been directly attached to the menu, deleting the menu registration would cause the removal of the Action also. Thanks to the shadow files, removing the menu registration does not mean that the Action registration itself is removed, as the Action is registered separately. Also, when you decide to let the user invoke the Action from a toolbar or keyboard shortcut, you will again use shadow files. The Action will then be instantiated once, not each time it is invoked from the various places where the user invokes it from.

Enabling Users to Invoke actions

Let us now allow the user to invoke the action from both a toolbar button and a keyboard shortcut. After all, you can connect Actions to a variety of places. Actions are often attached, for example, to context menus within Explorer views, files of certain MIME types, or the editor. Often these Actions only make sense if they react in a context-sensitive way to the element from whose context menu they are called. This too is supported by the NetBeans Platform.

In this case, though, you simply want to have the Action always enabled and present in the toolbar, as well as from the menu bar where it is already found.

Toolbar

You have already seen how Actions are attached to menus. All that is involved is registering the corresponding Action class in the appropriate position in the layer.xml file. The same approach applies to toolbar buttons, where a new toolbar button is registered within a subfolder of the Toolbars folder, as follows in the layer.xml file:

<folder name="Toolbars">
<folder name="Edit">
<file name="com-netbeansrcp-taskeditor-NewTaskAction.shadow">
<attr name="originalFile" stringvalue="Actions/Edit/com
netbeansrcp-taskeditor-NewTaskAction.instance"/>
</file>
</folder>
</folder>

Toolbar buttons must have an icon
For toolbar buttons, it is important that you register an Action for which an icon has been registered in the layer.xml file. Otherwise no icon will be shown for the Action in the toolbar.

Run the application. The Action is now visible in the Edit toolbar.

In summary, you have attached an Action declaratively to a toolbar. You simply had to add a shadow file entry to the relevant location in the layer.xml file.

Keyboard

Actions can be triggered by pressing key combinations on a keyboard. As with menus and toolbars, a keybinding is implemented within the NetBeans Platform declaratively. The relevant folder in this case is Shortcuts, while the file that you need to register there is again a shadow file.

Consequently, the name of the shadow file does not relate to the name of the Action that is to be triggered, but instead indicates the key combination in an Emacs-like notation, consisting of a modifier followed by a key designator. A hyphen is required between the modifier and designator.

The valid modifiers are listed as follows:

C—Ctrl key
A—Alt key
S—Shift key
M—Meta key, for example, Command button

As the Ctrl key on the Mac is different to what it is on a conventional PC, the NetBeans Platform further provides us a wildcard, with a corresponding key that varies depending on the operating system:

D—Standard Acceleration Key(for example, PC: Ctrl Key, Mac: Command Key)
O—Alternative Acceleration Key(for example, PC: Alt Key, Mac: Ctrl Key)

As a key designator, the name of each key constant is derived from java.awt.event.KeyEvent, with the leading VK_ of the constant name removed.

You now want to bind your action to the key combination standard accelerator key-A, which on the Mac is Command-A, while on the PC it is Ctrl-A.

Following the preceding description, register the Action to be invoked from a shortcut as follows in the layer.xml file:

<folder name="Shortcuts">
<file name="D-A.shadow">
<attr name="originalFile" stringvalue="Actions/Edit/
comnetbeansrcp-taskeditor-NewTaskAction.instance" />
</file>
</folder>

Run the application again. Press Ctrl-A on a PC or Command-A on a Mac. When you do so, you should see that the TaskLog registers the creation of a new task.

In summary, you have now declaratively bound an Action to a keyboard shortcut.

Summary

In this article, you continued working on the TaskManager, while learning about global Actions. First you created the Actions, and then you hooked them up to menu items, toolbar buttons, and keyboard shortcuts.


Further resources on this subject:


NetBeans Platform 6.9 Developer's Guide Create professional desktop rich-client Swing applications using the world's only modular Swing application framework
Published: August 2010
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

About the Author :


Jürgen Petri

Jürgen Petri (http://www.juergen-petri.de) is an independent consultant and instructor of primarily enterprise Java courses. As a Sun certified enterprise architect and Java developer, his interests relate primarily to Java EE projects, software architectures, user interface technologies, and tooling. His practical experience is based on over 10 years of work with each of the Java platforms.

Books From Packt


Swing Extreme Testing
Swing Extreme Testing

Java EE 6 with GlassFish 3 Application Server
Java EE 6 with GlassFish 3 Application Server

JavaFX 1.2 Application Development Cookbook
JavaFX 1.2 Application Development Cookbook

JSF 2.0 Cookbook
JSF 2.0 Cookbook

GlassFish Security
GlassFish Security

Alfresco Enterprise Content Management Implementation
Alfresco Enterprise Content Management Implementation

Oracle JRockit: The Definitive Guide
Oracle JRockit: The Definitive Guide

YUI 2.8: Learning the Library
YUI 2.8: Learning the Library


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
9
y
4
T
u
E
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software