You are on page 1of 22

01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE

Menu Account Country Call

Oracle Technology Network / Articles / Java

Application Development
Framework
Build a Rich Client Platform To-Do Application in NetBeans IDE
by John N. Kostaras
Application Express
Practice using NetBeans IDE features that improve code quality and increase developer productivity.
Big Data
Published May 2014
Business Intelligence
Cloud Computing
This article shows how to use NetBeans IDE 7.4 to develop a Swing-based "to-do" application, and it demonstrates the use of a rich client platform
(RCP). It is an update of "A Complete App Using NetBeans 5" by Fernando Lozano, which was originally published in NetBeans Magazine.
Communications
Note: NetBeans IDE 7.4 requires JDK 7, while earlier versions up through NetBeans IDE 7.3 can run with JDK 6.
Database Performance &
Availability You can download and then unzip the original application to compare it with the RCP to-do application that you'll develop in this article. To compare
Data Warehousing the applications, open the original application in NetBeans IDE and create a new project group by right-clicking somewhere inside the Project tab
and selecting Project Group -> New Group. Give the project group a name and click Create Group. Later, we'll create another group for the new
Database RCP to-do application. Then you can compare the two applications by switching between the two project groups by right-clicking and selecting
.NET Project Group and the appropriate group.

Dynamic Scripting Languages Note: The source code for the RCP to-do application developed in this article can be downloaded here.
Embedded Developing the RCP To-Do Application
Digital Experience The example application builds a to-do list, which is commonly found as part of privileged identity management (PIM) suites. It won't just demo the
NetBeans IDE's RCP features; it also sticks to object-oriented best practices, showing that you can develop GUI applications quickly and interactively
Enterprise Architecture without compromising long-term maintenance and a sound architecture.
Enterprise Management
You'll develop the to-do application in three steps:
Identity & Security
Build a "static" visual prototype of the GUI, using the NetBeans IDE's visual GUI builder to build a task list window.
Java Build a "dynamic" prototype of the application, coding user interface events and associated business logic and creating customized GUI components
Linux as needed.
Code the persistence logic by modeling the classes and the database.
Mobile These steps form a process that starts with the View, then builds the Controller, and finally builds the Model (the familiar MVC architecture).
Service-Oriented Architecture Your application requirements include the following:
Solaris
Tasks should have a priority, so users can focus first on higher-priority tasks.
SQL & PL/SQL Tasks should have a due date, so users can focus on tasks that are closer to their deadline.
Tasks that are either late or near their deadlines should have visual cues.
Systems - All Articles
Tasks can be marked as completed, but this doesn't mean they have to be deleted or hidden.
Virtualization You will have two main windows for the to-do application: a task list window and a task editing form. Figure 1 shows a rough sketch of both.

Figure 1

Step 1: Build a Static Prototype of the GUI


To get started, click the New Project toolbar button and select NetBeans Platform Application in the NetBeans Modules category (see Figure 2).
Enter TodoRCP as the project name and choose a suitable project location (anywhere on your hard disk), as shown in Figure 3. Then click Finish.

Figure 2

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 1/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE

Figure 3

NetBeans creates the TodoRCP project containing an empty Modules folder and an Important Files folder, which act as a container for the modules
that will be created in the rest of this article.

Right-click the Modules folder icon and choose Add New. Type View as the module name and click Next. Type todo.view in the Code Name
Base field and then click Finish (see Figure 4).

Figure 4

In NetBeans IDE 7.4, the Generate XML Layer checkbox has been removed from the second step of the wizard. To create the XML layer, right-click
the module and select New -> Other -> Module Development -> XML Layer.

The XML layer is a file named layer.xml, and each module can have one. NetBeans IDE's RCP functionality combines all layer.xml files during
runtime and creates the central registry of the application. Right-click the View module and select Open Project (see Figure 5).

Figure 5

The new module contains a package called todo.view. You now need to create your view. But instead of creating a JFrame form, as you do in the
Swing application, youll create its RCP equivalent, a TopComponent. If the View module isn't already open, right-click it and select New -> Window.

The dialog box asks you for the window position. The NetBeans IDE has various windows that can be positioned; the Editor window is the main area,
Output is the lower area where messages are displayed, and so on. Make the selections shown in Figure 6 and click Next.

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 2/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE

Figure 6

The Class Name Prefix field specifies the name of the frame or panel that will be created along with some other help files. Enter the name Tasks
(instead of TasksWindow, as in the original article) and click Finish. (See Figure 7.)

Figure 7

Two files are created, TasksTopComponent.java and TasksTopComponent.form, and the form is opened in Design view in the Editor window
along with the Palette window. Figure 8 shows the NetBeans IDE GUI and its components.

Note: If you can see the TasksTopComponent.form file in the Projects window but NetBeans IDE complains that it can't recognize the file when
you open it, you need to activate the Java SE plugin by selecting Tools -> Plugins -> Installed.

Figure 8

Notice the location of the Projects and Navigator windows on the left, and the Editor window in the center. A red frame highlights the selected
component (TopComponent). The Navigator displays all visual and nonvisual components of TopComponent, which is handy when you need to
change the properties of a component that's hidden by another or that's too small to be selected in the drawing area.

To the right is the Palette window, which by default shows the standard Swing components (you can also add third-party JavaBeans). Also to the right
is the Properties window. Properties are categorized to ease access to the ones most commonly used, and changed properties have their names
highlighted in bold.

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 3/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
To change the visual layout of the IDE, you can drag each window to another corner of the main window or even leave some windows floating around
by right-clicking their tab and selecting Float.

In previous versions of NetBeans IDE, layer.xml contained a TasksTopComponent displayed in Editor mode. In NetBeans IDE 7.4, this
information exists in annotations. Click Source to view the annotations shown in Listing 1 and compare them with the dialog box shown in Figure 6.

@ConvertAsProperties(
dtd = "-//todo.view//Tasks//EN",
autostore = false
)
@TopComponent.Description(
preferredID = "TasksTopComponent",
//iconBase="SET/PATH/TO/ICON/HERE",
persistenceType = TopComponent.PERSISTENCE_ALWAYS
)
@TopComponent.Registration(mode = "editor", openAtStartup = true)
@ActionID(category = "Window", id = "todo.view.TasksTopComponent")
@ActionReference(path = "Menu/Window" /* , position = 333 */)
@TopComponent.OpenActionRegistration(
displayName = "#CTL_TasksAction",
preferredID = "TasksTopComponent"
)
@Messages({
"CTL_TasksAction=Tasks",
"CTL_TasksTopComponent=Tasks Window",
"HINT_TasksTopComponent=This is a Tasks window"
})

Listing 1

Notice that mode = "editor" (if not, here's your chance to change it) and openAtStartup = true (again, if not, here's your chance to change
it).

The NetBeans IDE visual editor is unlike other visual Java editors you might have seen. Just right-click inside TopComponent and select the Set
Layout menu item. You see that the default choice isn't a traditional Swing/AWT layout manager; its something named Free Design. This means
you're using the Matisse Visual GUI builder. Matisse configures TopComponent to use the GroupLayout layout manager developed in the
SwingLabs java.net project, which is included as a standard layout manager in Java SE 6.

As shown in Figure 1, the task list consists of a menu bar, a toolbar, a table, and a status bar. Apart from the table, which in RCP is called
OutlineView, the platform handles all the rest. To add OutlineView, you need to add a dependency to the Explorer & Property Sheet API. Right-
click the Libraries folder of the View module and select Add Module Dependency, select Explorer & Property Sheet API, and click OK. Right-click
TopComponent in the Design view and change its layout to BorderLayout.

Now we have two options:

Option 1: The first option is rather a hack, but it's faster. Drag a ScrollPane from the Palette window and drop it in the center of
TasksTopComponent. Right-click JScrollPane in the Navigator window (look at the left bottom) and change its variable name to outlineView. In
the Properties window, click Code, and then click the small button [...] of the Custom Creation Code property, and add new OutlineView().
Switch to the Source view in the Editor window, right-click, and select Fix Imports to fix the errors.
Option 2: The second option is to add the visual components of the Explorer & Property Sheet API to the Palette window. If the Palette window isn't
visible, display it by selecting Window -> Palette. Right-click inside the Palette window and select Palette Manager. Create a new category by
clicking New Category and name the category something like NetBeans RCP or NetBeans platform components. Then click the Add from
JAR button, navigate to <Netbeans installation> -> platform -> modules, select org-openide-explorer.jar, and click Next. Select all available
components (as shown in Figure 9) and click Next. Select the category you created previously and click Finish. Click Close to close the Palette
Manager. Now you see the new category in the Palette window. Locate the OutlineView component and drag it inside the form.

Figure 9

When you run the application, you will see what's shown in Figure 10.

Figure 10

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 4/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
Look at how many things you get out of the box with NetBeans IDE, which in a normal Swing application you would have to develop yourself: a menu
bar and a toolbar (which need customization), a status bar, and a strange tree-table componentand all of these have just one line of code.

Let's start by customizing the menu. A central registry holds information about every module of the RCP to-do application. You can find the menu bar
in this central registry if you click Important Files -> XML Layer -> <this layer in context> -> Menu Bar (see Figure 11).

Figure 11

In Figure 1, you saw that we need only File, Edit, and Options menus.
So, keep File and Edit, rename Tools to Options (by selecting it, right-clicking, and selecting Rename), and remove the rest (by selecting them,
right-clicking, and selecting Delete). The result should look something like Figure 12.

Figure 12

Now you can start building your prototype. If you followed Option 1 earlier, open TasksTopComponent and at the end of the constructor, add the
lines of code shown in Listing 2:

OutlineView ov = (OutlineView)outlineView;

//Set the columns of the outline view,


//using the name of the property
//followed by the text to be displayed in the column header:
ov.setPropertyColumns(
"priority", "Priority",
"description", "Task",
"alert", "Alert",
"dueDate", "Due Date");

//Hide the root node, since we only care about the children:
ov.getOutline().setRootVisible(false);
TableColumnModel columnModel = ov.getOutline().getColumnModel();
ETableColumn column = (ETableColumn) columnModel.getColumn(0);
((ETableColumnModel) columnModel).setColumnHidden(column, true);
Listing 2

Or, if you chose Option 2 earlier, add the lines of code shown in Listing 3:

//Set the columns of the outline view,


//using the name of the property
//followed by the text to be displayed in the column header:
outlineView.setPropertyColumns(
"priority", "Priority",
"task", "Task",
"alert", "Alert",
"dueDate", "Due Date");

//Hide the root node, since we only care about the children:
outlineView.getOutline().setRootVisible(false);
TableColumnModel columnModel = ov.getOutline().getColumnModel();
ETableColumn column = (ETableColumn) columnModel.getColumn(0);
((ETableColumnModel) columnModel).setColumnHidden(column, true);
Listing 3

The only difference between Listing 2 and Listing 3 is the following line of code, which isnt needed in the second case because you don't need to
cast JScrollPane to an OutlineView:

OutlineView ov = (OutlineView)outlineView;

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 5/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
To be able to compile the code, you need to add one more dependency to the ETable and Outline module. You remember how to do this, right?
Heres a hint: right-click the Libraries folder. If you are successful, your application should look like Figure 13:

Figure 13

Now create the status bar. To display a StatusBar in NetBeans, a class must implement the StatusLineElementProvider interface and
declare it as a service. Right-click the todo.view package and select New -> Java Class. Name it StatusBar and click Finish. Then copy the code
shown in Listing 4 and paste it in:

package todo.view;

import java.awt.Component;
import javax.swing.JLabel;
import org.openide.awt.StatusLineElementProvider;
import org.openide.util.lookup.ServiceProvider;

/**
* The application's status bar.
* @author jnk
*/
@ServiceProvider(service=StatusLineElementProvider.class, position=1)
public class StatusBar implements StatusLineElementProvider {

@Override
public Component getStatusLineElement() {
return new Jlabel("There are no task alerts for today.");
}

}
Listing 4

Don't forget to right-click and select Fix imports. Then right-click and select Format to format the code.
So, what's going on here? As you can see, the StatusLineElementProvider interface is flexible; you can return any component you wanta
JLabel, JButton, JPanel, and so on. But of course, the magic thing is the first line, which declares the StatusBar class as a service provider of
StatusLineElementProvider.class.

What does this mean? Well, this is where lookups come in. In short, this line adds your class to the application's default lookup, which NetBeans IDE
then searches for StatusLineElementProvider.class, and then it adds all the providers it finds to the status bar.

Now it's time to create the toolbars. A toolbar contains actions, so you'll create the actions and insert them into the appropriate toolbars in the central
registry. You'll need the icons from the original to-do application, so if you haven't done so, download the zip file and unzip it on your disk.

The actions are controllers, so create a new module called Controller as you learned to do at the beginning of this article, and use
todo.controller as the code name base. Since you're here, also create the Model module using todo.model as the code name base.

Open the Controller module and create the packages todo.controller.file, todo.controller.edit, and
todo.controller.options. Right-click the todo.controller.edit package and select New Action. Create the Add Task action, which is always
enabled, and click Next.

Then select a category for the action. The categories represent semantic groupings of the actions. You can select a pre-existing category or create a
new one. In our case, select the pre-existing Edit category. In addition, assign your action to the Edit menu bar and the Edit toolbar, and set the
position where the action will be displayed. Drop-down menus show you possible locations for display. "HERE" identifies the location where the
display of your action will be inserted. Don't forget to add a keyboard shortcut (Insert, as you'd use in the old to-do application). See Figure 14.

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 6/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
Figure 14

Name the class as shown in Figure 15, and don't forget to add an icon.

Figure 15

The AddTaskAction class is created (see Listing 5).

package todo.controller.edit;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import org.openide.awt.ActionRegistration;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionID;
import org.openide.util.NbBundle.Messages;

@ActionID(category = "Edit",
id = "todo.controller.edit.AddTaskAction")
@ActionRegistration(iconBase = "todo/controller/edit/add_obj.gif",
displayName = "#CTL_AddTaskAction")
@ActionReferences({
@ActionReference(path = "Menu/Edit", position = 10),
@ActionReference(path = "Toolbars/Edit", position = 10),
@ActionReference(path = "Shortcuts", name = "Insert")
})
@Messages("CTL_AddTaskAction=Add Task...")
public final class AddTaskAction implements ActionListener {

public void actionPerformed(ActionEvent e) {


// TODO implement action body
}
}
Listing 5

Look how the input from the wizard is translated into Java annotations in NetBeans IDE 7.4. In versions prior to NetBeans IDE 7.0, this information
was added in layer.xml. Modify the position to be 10 in both cases to avoid mix-ups with the next actions you create. Add 10 to the position of
every new actionthat is, for EditTaskAction set the position to 20, for DeleteTaskAction set it to 30, and so on. Leave the action body empty
for the moment.

By executing the old to-do application, you might have noticed that while the Add Task action is always enabled, the other task actions are enabled
only when you select one or more tasks from the tablein other words, they are context actions. You can accomplish the same functionality by using
a conditionally enabled action. These actions operate on nodes. A node is the visual representation of a particular piece of dataa task, in our
example. When you select a row from the table, you are actually selecting a task node. Context sensitivity is constructed from interfaces, which are
called cookies. The node on which the action is to operate implements an interface specifying the method that the action should invoke. The action
can specify a set of cookies, the presence of which in the active node (if the active node implements one of these interfaces) determines whether the
action is enabled or not.

To create EditTaskAction, right-click the todo.controller.edit package and select New Action. Select Conditionally Enabled and User Selects
One Node (see Figure 16). The cookie class is a Task. This means that whenever a task (row) is selected in the OutlineView, this action is enabled.

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 7/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE

Figure 16

Click Next and complete the rest of steps as you did for AddTaskAction. If you completed the wizard correctly, you should see the output shown in
Listing 6.

package todo.controller.edit;

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import org.openide.awt.ActionRegistration;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionID;
import org.openide.util.NbBundle.Messages;
import todo.model.Task;

@ActionID(category = "Edit",
id = "todo.controller.edit.EditTaskAction")
@ActionRegistration(iconBase = "todo/controller/edit/configs.gif",
displayName = "#CTL_EditTaskAction")
@ActionReferences({
@ActionReference(path = "Menu/Edit", position = 20),
@ActionReference(path = "Toolbars/Edit", position = 20),
@ActionReference(path = "Shortcuts", name = "O-ENTER")
})
@Messages("CTL_EditTaskAction=Edit Task...")
public final class EditTaskAction implements ActionListener {

private final Task context;

public EditTaskAction(Task context) {


this.context = context;
}

public void actionPerformed(ActionEvent ev) {

}
}
Listing 6

Shortcuts are defined as follows (for platform compatibility):

Alt: O-
Ctrl: D-
Shift: S-
Your compiler might complain, though, because it can't find a Task class. If this is true, you need to copy the Task class to your Model module from
the old to-do application. Once you've done so, you need to expose this class to the other modules, as follows.

Right-click the Model module, select Properties -> API Versioning, and select the todo.model package from the Public packages to make it public.
Clean and build the Model module. Then, add a dependency from Controller to Model. You do this by right-clicking Libraries under the Controller
module and selecting the Add Module Dependency action. Search for Model and click OK. Now you may fix imports and have todo.model.Task
added as an import to EditTaskAction.

Repeat these steps to create the rest of the actions, trying to make the RCP to-do application as similar as possible to the old to-do application. That
means that EditTaskAction, DeleteTaskAction, and MarkAsCompletedTaskAction are conditionally enabled while the rest are always
enabled. DeleteTaskAction and MarkAsCompletedTaskAction should be conditionally enabled, and you may select multiple nodes since they
can be applied to many tasks at once. See Listing 7.

package todo.controller.edit;

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.util.List;

import org.openide.awt.ActionRegistration;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionID;
import org.openide.util.NbBundle.Messages;
import todo.model.Task;

@ActionID(category = "Edit",
id = "todo.controller.edit.MarkAsCompletedTaskAction")
@ActionRegistration(iconBase = "todo/controller/edit/complete_tsk.gif",
displayName = "#CTL_MarkAsCompletedTaskAction")
@ActionReferences({

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 8/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
@ActionReference(path = "Menu/Edit", position = 40, separatorBefore = 35),
@ActionReference(path = "Toolbars/Edit", position = 40),
@ActionReference(path = "Shortcuts", name = "D-SPACE")
})
@Messages("CTL_MarkAsCompletedTaskAction=Mark as completed")
public final class MarkAsCompletedTaskAction implements ActionListener {

private final List<Task> context;

public MarkAsCompletedTaskAction(List<Task> context) {


this.context = context;
}

public void actionPerformed(ActionEvent ev) {


for (Task task : context) {

}
}
}
Listing 7

Also, make sure that you use the Options toolbar and menu (see Figure 17) for the actions that belong to them, that is, Show completed tasks,
Sort by priority, Sort by due date, and Show Alerts.

The static visual prototype should look similar to Figure 18. If the order of the toolbars isn't correct, you can modify it either by right-clicking XML
Layer and selecting Open or by right-clicking the Toolbars node and selecting Go to Declaration. The layer.xml file opens and you can locate
the toolbars and change their Position attribute (a smaller value means that the toolbar is displayed first).

Figure 17

Figure 18

The task editing form shown in Figure 1 needs to be created, too. To save time and effort, simply copy the TaskDetailsDialog class from the old
to-to application (if you haven't opened it in NetBeans IDE yet, now's your chance) and paste it inside todo.view in the View module. You'll notice
some errors. Add a dependency from the View to the Model module for View to be able to access the Task class.

The other error can be resolved if you also copy the ActionSupport class from the old to-do application. You might encounter the error shown in
Listing 8 during the build:

...\netbeans\harness\build.xml:174: Module org.jdesktop.layout excluded from


the target platform
BUILD FAILED (total time: 5 seconds)
Listing 8

First, add a dependency to the deprecated org.jdesktop.layout module. Then, open the NetBeans Platform Config property file inside
Important Files of the module suite TodoRCP and delete the line org.jdesktop.layout,\. Do another clean and build. The code should
compile fine now.

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 9/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
To complete Step 1, you need to complete AddTaskAction to display TaskDetailsDialog. Simply copy the code of Listing 1 inside the
actionPerformed() method of AddTaskAction, as shown in Listing 9.

TaskDetailsDialog taskDetailsDialog = new TaskDetailsDialog(null, true);


taskDetailsDialog.setNewTask(true);
taskDetailsDialog.setTask(new Task());
// taskDetailsDialog.addActionListener(this);
taskDetailsDialog.setVisible(true);
Listing 9

EditTaskAction is also easy to write (see Listing 10).

public final class EditTaskAction implements ActionListener {

private final Task context;

public EditTaskAction(Task context) {


this.context = context;
}

public void actionPerformed(ActionEvent ev) {


TaskDetailsDialog taskDetailsDialog = new TaskDetailsDialog(null, true);
taskDetailsDialog.setNewTask(false);
taskDetailsDialog.setTask(context);
// taskDetailsDialog.addActionListener(this);
taskDetailsDialog.setVisible(true);
}
}
Listing 10

You need to make the todo.view package of the View module public and add a dependency from Controller to View. Clean and build the
application and execute it to make sure that the dialog box appears when you click the Add Task... action. What remains to be done is to replace the
splash screen and the About box, and you're almost ready to show your prototype to your client. Right-click TodoRCP and select Branding. Here
you can select a splash screen and the icon to display when the application is minimized.

Right-click the TodoRCP module suite, select Properties, and select the Installer category. Check the platforms that you wish to create installers for
(such as Windows, Mac OSX, and Linux) and click OK. Right-click the TodoRCP module suite again and select Package as and select one of the
available options, such as Installers or Zip distribution.

If everything ran smoothly, you created a new dist folder, which contains the deployed application. Run it by going inside dist/todorcp/bin and
executing the executable file (depending on your platform, that could be todorcp.exe or something similar).

The prototype is almost the finished application from the UI design perspective, but in a real project, don't spend too much time perfecting its looks.
Remember, the prototype is a tool for gathering and validating user requirements and for reducing the risk of missing important application
functionality.

Step 2: Build a Dynamic Prototype of the Application


The second stepbuilding the dynamic prototypeaims to implement as much user interaction as possible without using persistent storage or
implementing complex business logic. Following the original article, you'll use two well-known design patterns in the to-do application: Data Access
Object (DAO) and MVC. You'll also define a Value Object (VO) named Task for moving information between application tiers. Therefore, the view
classes (such as TasksTopComponent and TaskDetailsDialog) will receive and return either Task objects or collections of Task objects. The
controller classes will transfer those VOs from view classes to model classes and back.

The original article showed a Unified Modeling Language (UML) class diagram; you can compare it to the transformed UML diagram for the RCP to-
do application, which is shown in Figure 19. Notice that the packages todo.model, todo.controller, and todo.view of the original to-do
application have been transformed to modules for the RCP to-do application.

Figure 19

Here's the plan for building the dynamic prototype:

Display the visual cues for late and completed tasks.


Handle events:

Handle action events to sort and filter the tasks list.


Handle action events to create, edit, and remove tasks.
Add an in-place property editor for dates.
Items 1 and 2a can be implemented and tested with a mock model object (TaskManager) that always returns the same task collection. Item 2b can
be tested with a mock object that simply adds or removes objects from that collection.

Displaying Visual Cues


First, you'll want to display some data. First, wrap your model (Task) to a Node. Swing components follow the MVC design pattern, but you need a
different model for each visual componentfor example, a TableModel for a JTable, a ListModel for a JList, and so on. NetBeans attempts to
create a true MVC design by creating a single model that can be used by all view components (such as OutlineView or BeanTreeView). This is
done by wrapping the model to a node. The Node API provides several nodes that are shown in Figure 20, each one with a different purpose.

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 10/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE

Figure 20

Next, you'll use BeanNode, which uses reflection to retrieve the attributes of the VOs. Create the following class inside the View module (and add a
dependency on the Node API), as shown in Listing 11.

package todo.view;

import java.beans.IntrospectionException;
import org.openide.nodes.BeanNode;
import todo.model.Task;

class TaskNode extends BeanNode<Task> {

public TaskNode(Task bean) throws IntrospectionException {


super(bean);
}
}
Listing 11

To display tasks in OutlineView, you need a flat list of nodes. A flat list of nodes is a root node that provides leaf nodes only; that is, one-level-deep
children only. Factories are used to create children (see Listing 12).

package todo.view;

import java.beans.IntrospectionException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import todo.model.Task;

class TaskChildFactory extends ChildFactory<Task>{

@Override
protected boolean createKeys(final List<Task> toPopulate) {
final GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("Europe/Belgium"));
cal.set(2012, Calendar.JULY, 2, 10, 00, 00);
toPopulate.add(new Task(1, "Hotel Reservation", 1, cal.getTime(), true, 2));
cal.set(2012, Calendar.JULY, 3, 16, 30, 00);
toPopulate.add(new Task(2, "Review BOF-1", 1, cal.getTime(), true, 1));
cal.set(2012, Calendar.JULY, 6, 12, 45, 00);
toPopulate.add(new Task(3, "Reserve time for visit", 2, cal.getTime()));
return true;
}

@Override
protected Node createNodeForKey(final Task key) {
TaskNode taskNode = null;
try {
taskNode = new TaskNode(key);
} catch (IntrospectionException ex) {
Exceptions.printStackTrace(ex);
}
return taskNode;
}
}
Listing 12

OutlineView is populated with mock data, as shown in the method createKeys() in Listing 12. NetBeans IDE prompts you to create two new
constructors in Task. Finally, TasksTopComponent needs to be modified, as shown in Listing 13.

public final class TasksTopComponent extends TopComponent implements


ExplorerManager.Provider {
private final ExplorerManager em = new ExplorerManager();

public TasksTopComponent() {
...
em.setRootContext(new AbstractNode(Children.create(new TaskChildFactory(), true)));
// asynchronously
}
...
@Override
public ExplorerManager getExplorerManager() {
return em;
}
Listing 13

ExplorerManager, which is the controller of the explorer views, needs a root node element. Pass it an AbstractNode; its Children are derived
from the TaskChildFactory.

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 11/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
With many fewer lines of code than in the original Swing to-do application, you now have a running prototype with populated data. Build and run it to
see a view similar to that shown in Figure 21:

Figure 21

However, when you select one or more rows, the conditionally enabled actions aren't enabled accordingly. To fix this, you need to add the Task
object to the TaskNode's lookup, and then you need to set the TopComponent's lookup to be that of its nodes. To do that, modify TaskNode as
shown in Listing 14, which adds the task to the node's lookup.

class TaskNode extends BeanNode<Task> {

public TaskNode(Task bean) throws IntrospectionException {


super(bean, Children.LEAF, Lookups.singleton(bean));
}
}
Listing 14

The singleton lookup contains only one object, which in this case is your task. Set the TopComponent's lookup to be that of the node's by adding the
following after the line where you set the root context for the ExplorerManager:

associateLookup (ExplorerUtils.createLookup (em, getActionMap()));

To customize the outline view so that it displays a collection of Task objects, change the background color of each row according to the task status:
red for late tasks, yellow for tasks with an alert set, blue for completed tasks, and white otherwise. To do this, use the
CustomOutlineCellRenderer from NetBeans RCP Recipes.

TasksTopComponent needs to be modified as shown in Listing 15:

public final class TasksTopComponent extends TopComponent implements


ExplorerManager.Provider {
...

public TasksTopComponent() {
...
outlineView.getOutline().setDefaultRenderer(Node.Property.class, new
CustomOutlineCellRenderer() {

@Override
public Component getTableCellRendererComponent(final JTable table,
final Object value,
final boolean isSelected,
final boolean hasFocus,
final int row,
final int column) {
Component cell = super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
int modelRow = table.convertRowIndexToModel(row);
Node node = EM.getRootContext().getChildren().getNodeAt(modelRow);
if (node != null) {
Task task = node.getLookup().lookup(Task.class);
Decorator.decorate(task, cell);
}
return cell;
}
});
...
}
Listing 15

The new utility class Decorator does the decoration (see Listing 16).

package todo.view;

import java.awt.Color;
import java.awt.Component;
import todo.model.Task;

class Decorator {

static void decorate(final Task task, final Component cell) {


if (task != null) {
if (task.hasAlert()) {
cell.setBackground(Color.yellow);
} else if (task.isCompleted()) {
cell.setBackground(Color.blue);
} else if (task.isLate()) {
cell.setBackground(Color.red);
}
}
}
}
Listing 16

Handling Events
Now that you have a display of tasks ready, it's time to add some event handling. In the original to-do application, it was useful to separate GUI
events into two mutually exclusive categories:

Internal events, which affect just the view itself

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 12/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
External events, which cause model methods to execute
Internal events include selection changes and clicks on Cancel buttons. In the original to-do application, these were handled by the view classes
themselves and were not exposed as part of the view classes' public interfaces. For example, the selection of a task should enable the Edit task
menu item and the Remove task menu item, and the corresponding toolbar buttons. Such events are now handled by conditionally enabled actions
with cookies inside the Controller module, not in the View package anymore.

View classes should not handle the category of events that the author of the article of the original to-do application calls "external." These events
should instead be forwarded to controller classes, which usually implement the workflow logic for a specific use case or a related set of use cases.

The original to-do application includes the todo.view.ActionSupport class, which simply keeps a list of ActionListeners and forwards
ActionEvents to them. But ActionSupport is itself an ActionListener. This is done to avoid having lots of event-related methods, such as
addNewTaskListener(), removeNewTaskListener(), addEditTaskListener(), removeEditTaskListener(), and so on. Instead, view
classes generate only an ActionEvent. The ActionSupport classes capture ActionEvents from the view components and forward them to the
controller, which registers itself as a view ActionListener.

All these classes aren't needed in the RCP to-do application because the RCP framework takes care of all this. What you need to do is simply
complete the actionPerformed() methods of your actions. So your job is to transfer the logic from QueryEditTasks to your actions. Listing 17
shows how they map.

public final class MarkAsCompletedTaskAction implements ActionListener {

private final List<Task> context;

public MarkAsCompletedTaskAction(List<Task> context) {


this.context = context;
}

@Override
public void actionPerformed(ActionEvent ev) {
for (Task task : context) {
task.setCompleted(true);
}
}
}
public final class DeleteTaskAction implements ActionListener {

private final List<Task> context;

public DeleteTaskAction(List<Task> context) {


this.context = context;
}

public void actionPerformed(ActionEvent ev) {


for (Task task : context) {
int response = JOptionPane.showConfirmDialog(null,
"Are you sure you want to remove task\n["
+ task.getDescription() + "] ?",
"Remove Task",
JOptionPane.YES_NO_OPTION);
if (response == JOptionPane.YES_OPTION) {
// model.removeTask(task.getId());
}
}
}
}
Listing 17

To make DeleteTaskAction functional, you need to copy todo.model package classes from the original to-do application to your Model
todo.model package. However, the TaskManager contained in the original to-do application contains all the logic to save tasks in persistent
storage, such as a relational database. Since this is left for later in Step 3 of this article, you won't use it now. The implementation of
NewTaskListAction and OpenTaskListAction also comes in Step 3.

However, you will create a TaskManager that stores Tasks in memory. The TaskManager class is a DAO. Being the only DAO in the application, it
contains many methods that would otherwise be in an abstract superclass. Its implementation is very simple, so there's lots of room for improvement.
Start by creating the interface in the Model module that's based on the persistent TaskManager of the original article (see Listing 18).

package todo.model;

import java.util.List;

public interface TaskManagerInterface {


void addTask(Task task) throws ValidationException;
void updateTask(final Task task) throws ValidationException;
void removeTask(final int id);
List<Task> listAllTasks(boolean priorityOrDate);
List<Task> listTasksWithAlert() throws ModelException;
void markAsCompleted(final int id, final boolean completed);
}
Listing 18

Then TaskManager is implemented as a service, as shown in Listing 19.

package todo.model;

import java.util.*;
import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service = TaskManagerInterface.class)
public class TaskManager implements TaskManagerInterface {

private final List<Task> tasks = new ArrayList<Task>();

public TaskManager() {
final GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("Europe/Belgium"));
cal.set(2012, Calendar.JULY, 2, 10, 00, 00);
tasks.add(new Task(1, "Hotel Reservation", 1, cal.getTime(), true));
cal.set(2012, Calendar.JULY, 6, 16, 30, 00);
tasks.add(new Task(2, "Review BOF-1", 1, cal.getTime(), true));
cal.set(2012, Calendar.JULY, 5, 12, 45, 00);
tasks.add(new Task(3, "Reserve time for visit", 2, cal.getTime(), false));
}

@Override
public List<Task> listAllTasks(final boolean priorityOrDate) {
Collections.sort(tasks, priorityOrDate ? new PriorityComparator() : new DueDateComparator());

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 13/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
return Collections.unmodifiableList(tasks);
}

@Override
public List<Task> listTasksWithAlert() throws ModelException {
final List<Task> tasksWithAlert = new ArrayList<Task>(tasks.size());
for (Task task : tasks) {
if (task.hasAlert()) {
tasksWithAlert.add(task);
}
}
return Collections.unmodifiableList(tasksWithAlert);
}

@Override
public void addTask(final Task task)
throws ValidationException {
validate(task);
tasks.add(task);
}

@Override
public void updateTask(final Task task) throws ValidationException {
validate(task);
Task oldTask = findTask(task.getId());
tasks.set(tasks.indexOf(oldTask), task);
}

@Override
public void markAsCompleted(final int id, final boolean completed) {
Task task = findTask(id);
task.setCompleted(completed);
}

@Override
public void removeTask(final int id) {
tasks.remove(findTask(id));
}

private void validate(final Task task) throws ValidationException {


if (task.getDescription().isEmpty()) {
throw new ValidationException("Must provide a task description");
}
}

private Task findTask(final int id) {


for (Task task : tasks) {
if (id == task.getId()) {
return task;
}
}
return null;
}

private static class PriorityComparator implements Comparator<Task> {

@Override
public int compare(final Task t1, final Task t2) {
if (t1.getPriority() == t2.getPriority()) {
return 0;
} else if (t1.getPriority() > t2.getPriority()) {
return 1;
} else {
return -1;
}
}
}

private static class DueDateComparator implements Comparator<Task> {

@Override
public int compare(final Task t1, final Task t2) {
return t1.getDueDate().compareTo(t2.getDueDate());
}
}
}
Listing 19

Note that you moved the mock data from the TaskChildFactory.createKeys() method to the constructor of TaskManager. You added the
class to the default lookup by annotating it like this:

@ServiceProvider(service = TaskManagerInterface.class)

You can also retrieve an implementation class from the default lookup.

TaskChildFactory.createKeys() now becomes what is shown in Listing 20:

@Override
protected boolean createKeys(final List<Task> toPopulate) {
final TaskManagerInterface taskManager =
Lookup.getDefault().lookup(TaskManagerInterface.class);
toPopulate.addAll(taskManager.listAllTasks(true));
return true;
}
Listing 20

DeleteTaskAction becomes what is shown in Listing 21:

public final class DeleteTaskAction implements ActionListener {

private final List<Task> context;


private final TaskManagerInterface taskManager;

public DeleteTaskAction(List<Task> context) {


this.context = context;
this.taskManager = Lookup.getDefault().lookup(TaskManagerInterface.class);

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 14/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
}

@Override
public void actionPerformed(ActionEvent ev) {
for (Task task : context) {
int response = JOptionPane.showConfirmDialog(null,
"Are you sure you want to remove task\n["
+ task.getDescription() + "] ?",
"Remove Task",
JOptionPane.YES_NO_OPTION);
if (response == JOptionPane.YES_OPTION) {
taskManager.removeTask(task.getId());
}
}
}
}
Listing 21

Finally, MarkAsCompletedTaskAction becomes what is shown in Listing 22:

public final class MarkAsCompletedTaskAction implements ActionListener {

private final List<Task> context;


private final TaskManagerInterface taskManager;

public MarkAsCompletedTaskAction(List<Task> context) {


this.context = context;
taskManager = Lookup.getDefault().lookup(TaskManagerInterface.class);
}

public void actionPerformed(ActionEvent ev) {


for (Task task : context) {
taskManager.markAsCompleted(task.getId(), true);
}
}
}
Listing 22

ActionSupport, which you might have copied from the original to-do application to eliminate the errors of TaskDetailsDialog, isn't needed
anymore, so remove it from the View module and tackle the errors. Initially, remove all statements from TaskDetailsDialog that refer to
ActionSupport. Add a reference to TaskManager, as shown in Listing 23.

private final TaskManagerInterface taskManager;

public TaskDetailsDialog(java.awt.Frame parent, boolean modal) {


super(parent, modal);
initComponents();
setLocationRelativeTo(parent);
taskManager = Lookup.getDefault().lookup(TaskManagerInterface.class);
}
Listing 23

Then add actions to the Remove and Save buttons, as shown in Listing 24:

private void removeActionPerformed(java.awt.event.ActionEvent evt) {


taskManager.removeTask(getTask().getId());
}

private void saveActionPerformed(java.awt.event.ActionEvent evt) {


try {
if (isNewTask()) {
taskManager.addTask(getTask());
} else {
taskManager.updateTask(getTask());
}
} catch (ValidationException ex) {
Exceptions.printStackTrace(ex);
}
cancel.doClick();
}
Listing 24

Sorting behavior is delivered out of the box in OutlineViews just by clicking the specific header/field. Clicking once sorts the column in ascending
order, clicking once more sorts in descending order, and clicking once more leaves the order as it was originally. However, if you want to implement
SortByDateAction and SortByPriorityAction to see how sorting can be done programmatically, create a new Utilities class inside
todo.view, and then copy the code from Listing 25 and paste into to the Utilities class.

/**
* Sort the outline view {@code ov} on the given {@code field}.
* @param ov outline view to sort
* @param field to sort upon
* @param ascending if {@code true} then the list is sorted in ascending order,
* if {@code false} in descending order.
*/
public static void sortBy(final OutlineView ov, final String field, final boolean ascending) {
ETableColumnModel columnModel = (ETableColumnModel) ov.getOutline().getColumnModel();
int columnCount = columnModel.getColumnCount();
columnModel.clearSortedColumns();
for (int i = 0; i < columnCount; i++) {
ETableColumn column = (ETableColumn)columnModel.getColumn(i);
if (column.getHeaderValue().equals(field)) {
columnModel.setColumnSorted(column, ascending, 1);
}
}
TableModel model = ov.getOutline().getModel();
ov.getOutline().tableChanged(new TableModelEvent(model, 0, model.getRowCount()));
}
Listing 25

Since the Utilities class contains only static utility methods, add an empty private constructor to it to avoid initialization. Add the missing
dependency to the ETable and Outline module. Add the following method to TasksTopComponent:

public void sortBy(final String field, final boolean ascending) {


Utilities.sortBy((OutlineView)outlineView, field, ascending);
}

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 15/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
This step is to avoid adding a dependency to the Explorer & Property Sheet API in the Controller module. Because to call the original method in
the Utilities class, you need to add a reference to OutlineView, which is contained in the Explorer & Property Sheet API in your Controller
module. This workaround saves us this dependency, as shown in Listing 26.

public final class SortByPriorityAction implements ActionListener {

public void actionPerformed(ActionEvent e) {


TasksTopComponent tasksTopComponent = (TasksTopComponent)WindowManager.getDefault().findTopComponent(
"TasksTopComponent");
tasksTopComponent.sortBy("Priority", true);
}
}
public final class SortByDateAction implements ActionListener {

public void actionPerformed(ActionEvent e) {


TasksTopComponent tasksTopComponent = (TasksTopComponent)WindowManager.getDefault().findTopComponent(
"TasksTopComponent");
tasksTopComponent.sortBy("Due Date", true);
}
}
Listing 26

Regarding filtering, you can right-click a row and select Show only rows where and then select a criterion to filter. Remove the filter by following the
same procedure and selecting No filter. However, your two actions, ShowAlertsAction and ShowCompletedTasksAction, can't be displayed
by these out-of-the-box filters.

ShowAlertsAction displays only the tasks that have alertsfor example, alert==true. Start from the code generated by the wizard (see Listing
27).

package todo.controller.options;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JToggleButton;
import org.openide.awt.ActionRegistration;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionID;
import org.openide.util.NbBundle.Messages;

@ActionID(category = "Options",
id = "todo.controller.options.ShowAlertsAction")
@ActionRegistration(iconBase = "todo/controller/options/showwarn_tsk.gif",
displayName = "#CTL_ShowAlertsAction")
@ActionReferences({
@ActionReference(path = "Menu/Options", position = 40, separatorBefore = 35),
@ActionReference(path = "Toolbars/Options", position = 40),
@ActionReference(path = "Shortcuts",
name = "F9")
})
@Messages("CTL_ShowAlertsAction=Show alerts...")
public final class ShowAlertsAction implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
// TODO implement action body
}
}
Listing 27

First, you need to convert the button to a toggle button. However, JToggleButton doesn't really work with the NetBeans IDE toolbar, so you need to
extend org.openide.util.actions.BooleanStateAction. Because BooleanStateAction implements
java.awt.event.ActionListener, ActionListener can be deleted from your action (see Listing 28).

public final class ShowAlertsAction extends BooleanStateAction {

@Override
protected void initialize() {
super.initialize();
setBooleanState(false);
}

@Override
public void actionPerformed(ActionEvent e) {
}

@Override
public String getName() {
return "Show alerts...";
}

@Override
public HelpCtx getHelpCtx() {
return HelpCtx.DEFAULT_HELP;
}

@Override
protected String iconResource() {
return "todo/controller/options/showwarn_tsk.gif";
}
}
Listing 28

Because, due to lazy initialization, BooleanStateAction doesn't recognize the values passed in the annotations, you need to override the
iconResource() method. It's selected by default, so you need to set it to be unselected in the initialize() method.

The Outline class provides a method setQuickFilter(int col, Object filterObject), and filterObject can either be a value that
matches one of the values of the column or a QuickFilter object (see Listing 29):

public interface QuickFilter {

/** If the object is accepted its row is displayed by the table. */


public boolean accept(Object aValue);
}
Listing 29

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 16/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
In this case, the accepted value is simply true, which means that you should just accept those rows that contain the value true. Listing 30 shows
how the actionPerformed() method can be implemented:

super.actionPerformed(e);
TasksTopComponent tasksTopComponent = (TasksTopComponent)
WindowManager.getDefault().findTopComponent("TasksTopComponent");
if (getBooleanState()) {
tasksTopComponent.setQuickFilter(3, Boolean.TRUE); // or a QuickFilter
} else {
tasksTopComponent.unsetQuickFilter();
}
Listing 30

The setQuickFilter() wrapper method in TasksTopComponent wraps the same method of Outline (see Listing 31):

public void setQuickFilter(int column, Object filter) {


outlineView.getOutline().setQuickFilter(column, filter);
}

public void unsetQuickFilter() {


outlineView.getOutline().unsetQuickFilter();
}
Listing 31

Whether or not the toggle button is clicked is defined by getBooleanState(). Finally, super.actionPerformed(e); toggles selection and
deselection of the toggle button, so you don't need to do it yourself.

ShowCompletedTasksAction is a more complex case because completed is not shown in the outline view as a column (only as a color). To
implement it, you need a mechanism where you pass each one of the Tasks and you check whether completed == true.

However, creating this mechanism isn't as difficult as it might look. Start by transforming ShowCompletedTasksAction to a
BooleanStateAction. If you noticed in the beginning of the article, you hid the first column of your OutlineView. This column (column 0)
contains the node. So applying a filter to column 0 is like applying a filter to the node itself (see Listing 32):

super.actionPerformed(e);
TasksTopComponent tasksTopComponent = (TasksTopComponent)
WindowManager.getDefault().findTopComponent("TasksTopComponent");
if (getBooleanState()) {
tasksTopComponent.setQuickFilter(0, filter);
} else {
tasksTopComponent.unsetQuickFilter();
}
Listing 32

We now need to create the filter, as shown in Listing 33:

/** Show only tasks that have been completed. */


private final QuickFilter filter = new QuickFilter() {

@Override
public boolean accept(Object aValue) {
if (aValue instanceof TaskNode) {
TaskNode taskNode = (TaskNode) aValue;
Task task = taskNode.getLookup().lookup(Task.class);
return task.isCompleted();
}
return true;
}
};
Listing 33

When you add this definition to the ShowCompletedTasksAction class, you need to add the Nodes and ETable and Outline dependencies to
the Controller module. You could add this definition to a class in the View module (for example, in TasksTopComponent) and access it from
there; however, you would still need a dependency to ETable and Outline since QuickFilter is defined there.

Adding an In-Place Property Editor for Dates


Note: The following functionality runs best with NetBeans IDE 7.4 or later, although with more effort, it can run on earlier versions.

Adding new tasks or editing tasks goes smoothly, except when you have to add or edit the due date. To provide a more user-friendly date input, follow
the "NetBeans Property Editor Tutorial" and, most specifically, the section "Creating a Custom Inplace Editor." Use the date picker component of the
SwingX library, which you can find inside the ide/modules/ext/ folder of your NetBeans IDE installation. However, don't add swingx.jar as a
new library, as was done in the original article. You want to follow a different approach, because later on, you'll need hsqldb.jar for persisting the
tasks to the database and maybe other to libraries.

Create a new module, Libraries, and wrap your various external libraries inside. The benefit is that you need to add only one new module to your
suite and a single reference to it from the modules that need it. The drawback is that if you need only one JAR file, you need to have visibility to all
other files that are wrapped inside the Libraries module.

Right-click the Modules folder icon of the TodoRCP module suite and choose Add New. Type Libraries as the module name. Click Next. Type
lib as the Code Base Name and then click Finish.
Right-click the new module and select Properties -> Libraries -> Wrapped JARs. Click Add JAR and add ide/modules/ext/swingx.jar.
Repeat the procedure to add hsqldb.jar, which you can download from the HyperSQL site.
Select the API Versioning category from the left panel of the opened Project Properties -> Libraries dialog box and make the org.hsqldb and
org.jdesktop.swingx packages public by selecting them.
After clicking OK, clean and build the Libraries module. Add a dependency to the Libraries module in the View module. Now you're ready to
make use of the date picker, which involves implementing a couple of NetBeans IDEspecific interfaces:

ExPropertyEditor: A property editor interface through which the property sheet can pass an "environment" object (PropertyEnv) that gives the
editor access to the Property object it is editing and more.
InplaceEditor.Factory: An interface for objects that own an InplaceEditor.
InplaceEditor: An interface that allows a custom component to be provided for display in the property sheet.
Create a new class DatePropertyEditor inside todo.view (see Listing 34):

public class DatePropertyEditor extends PropertyEditorSupport implements ExPropertyEditor,


InplaceEditor.Factory {
private InplaceEditor ed;

@Override
public String getAsText() {
Date d = (Date) getValue();
if (d == null) {
return "No Date Set";
}
return new SimpleDateFormat("dd/MM/yy HH:mm:ss").format(d);
}

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 17/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE

@Override
public void setAsText(String s) {
try {
setValue(new SimpleDateFormat("dd/MM/yy HH:mm:ss").parse(s));
} catch (ParseException pe) {
IllegalArgumentException iae = new IllegalArgumentException("Could not parse
date");
throw iae;
}
}

@Override
public void attachEnv(PropertyEnv env) {
env.registerInplaceEditorFactory(this);
}

@Override
public InplaceEditor getInplaceEditor() {
if (ed == null) {
ed = new Inplace();
}
return ed;
}

private static class Inplace implements InplaceEditor {

private final JXDatePicker picker = new JXDatePicker();


private PropertyEditor editor = null;

@Override
public void connect(PropertyEditor propertyEditor, PropertyEnv env) {
editor = propertyEditor;
reset();
}

@Override
public JComponent getComponent() {
return picker;

@Override
public void clear() {
//avoid memory leaks:
editor = null;
model = null;
}

@Override
public Object getValue() {
return picker.getDate();
}

@Override
public void setValue(Object object) {
picker.setDate((Date) object);
}

@Override
public boolean supportsTextEntry() {
return true;
}

@Override
public void reset() {
Date d = (Date) editor.getValue();
if (d != null) {
picker.setDate(d);
}
}

@Override
public KeyStroke[] getKeyStrokes() {
return new KeyStroke[0];
}

@Override
public PropertyEditor getPropertyEditor() {
return editor;
}

@Override
public PropertyModel getPropertyModel() {
return model;
}
private PropertyModel model;

@Override
public void setPropertyModel(PropertyModel propertyModel) {
this.model = propertyModel;
}

@Override
public boolean isKnownComponent(Component component) {
return component == picker || picker.isAncestorOf(component);
}

@Override
public void addActionListener(ActionListener actionListener) {
//do nothing - not needed for this component
}

@Override
public void removeActionListener(ActionListener actionListener) {

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 18/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
//do nothing - not needed for this component
}
}
}
Listing 34

The date format (dd/MM/yy HH:mm:ss) depends on your location, so adapt it accordingly.

The final thing that you need to do, which works only with NetBeans IDE 7.4 or later, is to add the following annotation to the definition of the class to
register DatePropertyEditor globallythat is, as the default editor for all properties of the type java.util.Date throughout the system:

@PropertyEditorRegistration(targetType = Date.class)
public class DatePropertyEditor extends PropertyEditorSupport implements ExPropertyEditor,
InplaceEditor.Factory {

Clean and build the View module and run the RCP to-do application again. A date picker now appears when you try to edit the due date field (see
Figure 22).

Figure 22

Note: If you use NetBeans IDE 7.2 or earlier, you need to do more work to achieve this functionality. You'll have to override the createSheet()
method of TaskNode and edit the properties, setting the following to the date property, too:

dateProp.setPropertyEditorClass(DatePropertyEditor.class);

We need to add a date picker to the TaskDetailsDialog, too. To do this, add the visual components of swingx.jar to the Palette, as you did for
the Explorer & Property Sheet API. Open the TaskDetailsDialog in Design view, if you haven't already done so. Right-click inside the Palette
window and select Palette Manager. Create a new category by clicking New Category, and name the new category SwingX. Then, click Add from
JAR, navigate to TodoRCP -> Libraries -> lib, select swingx.jar, and click Next. Select all available components and click Next. Select the category
SwingX and click Finish. Click Close to close the Palette Manager. The new category is now shown in the Palette window.

Delete the Due date text field from the form, and then locate the JxDatePicker component in the Palette window. Drag it inside the form in the same
place where the Due date text field was. Change to Source view, fix imports, and correct the errors.

In setTask():

dueDate.setDate(task.getDueDate());

In getTask():

task.setDueDate(dueDate.getDate());

Build the View module and run the application again. When you click the Add Task or Edit Task actions to display the Task Details dialog box, you
can modify the due date by selecting it from the date picker. Cool! However, if you run the application and try to add a new task, your new task never
appears in the OutlineView because OutlineView is never notified of changes to the model. You need to fix this.

First, modify TaskManagerInterface by adding two more methods to it:

void addPropertyChangeListener(PropertyChangeListener listener);


void removePropertyChangeListener(PropertyChangeListener listener);

Implement these methods in TaskManager, as shown in Listing 35:

@ServiceProvider(service = TaskManagerInterface.class)
public class TaskManager implements TaskManagerInterface {
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
...
@Override
public void addTask(final Task task) throws ValidationException {
validate(task);
tasks.add(task);
pcs.firePropertyChange("ADDED", null, task);
}

@Override
public void updateTask(final Task task) throws ValidationException {
validate(task);
Task oldTask = findTask(task.getId());
tasks.set(tasks.indexOf(oldTask), task);
pcs.firePropertyChange("UPDATED", oldTask, task);
}

@Override
public void markAsCompleted(final int id, final boolean completed) {
Task task = findTask(id);
boolean oldValue = task.isCompleted();
task.setCompleted(completed);
pcs.firePropertyChange("COMPLETED", oldValue, completed);
}

@Override
public void removeTask(final int id) {
Task task = findTask(id);
tasks.remove(task);
pcs.firePropertyChange("DELETED", task, null);
}
...
@Override

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 19/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}

@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
}
Listing 35

Finally, TaskChildFactory needs to be notified (see Listing 36):

public class TaskChildFactory extends ChildFactory<Task> {

private final TaskManagerInterface taskManager;


private final transient PropertyChangeListener pcl = new PropertyChangeListener() {

@Override
public void propertyChange(final PropertyChangeEvent evt) {
refresh(true);
}
};

public TaskChildFactory() {
taskManager = Lookup.getDefault().lookup(TaskManagerInterface.class);
taskManager.addPropertyChangeListener(pcl);
}
...
}
Listing 36

These changes should allow the OutlineView to be updated accordingly. However, you'll notice one more problem. When you edit a task, it isn't
updated until you click another cell. To cope with this problem, you need to make some more changes. The first change is to your Task class (make
it an observable to notify listeners of changes to its fields); second, add a firePropertyChange() to all setter methods that affect the outline view.
An example is shown in Listing 37, but you need to do the same thing for the other setters, too.

public class Task implements Serializable {


...
private final PropertyChangeSupport propertyChangeSupport = new
PropertyChangeSupport(this);

public void addPropertyChangeListener(final PropertyChangeListener listener) {


propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
}

public void removePropertyChangeListener(final PropertyChangeListener listener) {


propertyChangeSupport.removePropertyChangeListener(listener);
}

...
public void setDueDate(Date dueDate) {
Date oldValue = this.dueDate;
this.dueDate = dueDate;
propertyChangeSupport.firePropertyChange("DUE DATE CHANGED", oldValue, dueDate);
}
...
}
Listing 37

Then, TaskNode must be notified of any changes (see Listing 38):

public class TaskNode extends BeanNode<Task> {

private final transient PropertyChangeListener pcl = new PropertyChangeListener() {

@Override
public void propertyChange(final PropertyChangeEvent evt) {
firePropertySetsChange(null, getPropertySets());
}
};

public TaskNode(Task bean) throws IntrospectionException {


super(bean, Children.LEAF, Lookups.singleton(bean));
bean.addPropertyChangeListener(pcl);
}
}
Listing 38

Now that you have fully functional view and model classes, it's time to replace the mock implementations of the model classes by real logic using
persistent storage. In large application projects, you could have a team working on the UIbuilding the two prototypes in sequence as you didand
another team working on business and persistence logic, preferably using test-driven development (TDD). They can work in parallel and join up at
the end, putting together functional view and controller implementations with functional model implementations.

Most of the work in this Step 2 was just coding. NetBeans IDE provides nice code editors and a good debugger that provide the usual benefits: code
completion, Javadoc integration, and refactoring support. But NetBeans IDE can go beyond: it's easy to build in new plugin modules to package your
project coding standards, such as project templates, controller class templates, and so on.

Step 3: Code the Persistence Logic


The RCP to-do application uses HyperSQL (hsqldb), an embedded Java database, which simplifies the deployment requirements for a typical
desktop application. Because you're working with modules in RCP applications, adding the hsqldb.jar archive to a modules libraries isn't
straightforward. To allow for future libraries, you created a new module earlier called Libraries, which contains the libraries necessary for the
application.

Inspecting the Database


When developing and debugging persistence code, developers usually need a way to tap into the database. Maybe they need to check the effect of
an update or change some table definition. NetBeans IDE provides direct support for browsing any JDBC-compliant database and submitting SQL
commands.

To connect to the database, switch to the Services tab or open it from the Window menu. Expand the Databases category and then expand the
Drivers category. Open the Drivers folder. If the HSQLDB driver isn't there, right-click Drivers, select New Driver, and add the location of the
hsqldb.jar archive. NetBeans IDE often sets the database driver class name by itself.

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 20/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
Now right-click the HSQLDB driver icon, and choose the Connect using menu item. Provide the parameters to connect to your local RCP to-do
application's database, using the Figure 23 as a template.

Figure 23

The default database location is db/todo under the {user.home} folder, which is usually /home/<user> under Linux or C:\Users\<UserName>
under Windows. Then you can open the connection and browse the database catalog for tables, indexes, and other database objects. Each item has
a context menu for operations such as creating new tables, altering columns, and viewing data. Most operations have easy-to-use wizards.

The RCP to-do application uses HSQLDB in the standalone mode, which locks the database files for exclusive access so you can't use the NetBeans
IDE's database console while the application is running. However, you can run HSQLDB in server mode accepting concurrent connections from
multiple clients, allowing the inspection of a live task list database. Check the HSQLDB manual for instructions on how to start and connect to the
database server.

To finish the application, you need to copy three more classes from the original to-do application: TaskManager, Parameters, and
DatabaseException. TaskManager replaces your current class with some modificationsit must be a ServiceProvider and, as such, it must
have a parameterless constructor (see Listing 39):

@ServiceProvider(service = TaskManagerInterface.class)
public class TaskManager implements TaskManagerInterface {

public TaskManager() throws DatabaseException {


this(new Parameters());
}
...
}
Listing 39

You need to remove DatabaseException from the signature of the complaining methods (for an example, see Listing 40):

Override
public List<Task> listAllTasks(boolean priorityOrDate) {
List<Task> allTasks = new ArrayList<Task>();
try {
allTasks = query(null, priorityOrDate
? "priority, dueDate, description" : "dueDate, priority, description");
} catch (DatabaseException e) {
e.printStackTrace();
}
return allTasks;
}
Listing 40

You must also make TaskManager an observable by providing a PropertyChangeSupport, adding the appropriate firePropertyChange()
commands to the end of the appropriate methods: addTask(), removeTask(), markAsCompleted(), and updateTask(). For an example, see
Listing 41.

@Override
public void removeTask(int id) {
try {
update("DELETE FROM todo WHERE id = " + id);
} catch (DatabaseException e) {
e.printStackTrace();
}
pcs.firePropertyChange("DELETED", id, null);
}
Listing 41

Now, just add a dependency from the Model module to the Libraries module, and you're almost done! Run the application and verify that it works
like it did before.

The final step is to add the two actions New Task List and Open Task List to the File menu, but this is left as an exercise for the reader.

A last thing to mention: you might notice that the Tasks window tab contains an x button, which you can click to close this window without any way to
bring the window back. To disable this x button, add the following lines in the TasksTopCompnent constructor:

putClientProperty(TopComponent.PROP_CLOSING_DISABLED, Boolean.TRUE);
putClientProperty(TopComponent.PROP_UNDOCKING_DISABLED, Boolean.TRUE);

Conclusion
You've migrated the original to-do application successfully without much effort. Although simple in scope and involving only a few classes, the
process explored here demonstrates many practices that could improve the quality your RCP desktop Java applications and your development
speed.

You also practiced many features that NetBeans IDE provides to increase developer productivity. NetBeans IDE goes beyond visual development by
supporting coding activities with specialized editors for Java, Ant, XML, and other languages, in addition to CVS, JUnit, and refactoring support and a
database console.

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 21/22
01/11/2017 Build a Rich Client Platform To-Do Application in NetBeans IDE
See Also
"NetBeans Property Editor Tutorial"
"NetBeans Nodes API Tutorial"
About the Author
Ioannis (John) Kostaras is a software architect and has been a Java developer since JDK 1.0 was released. He has developed a number of
standalone and web applications focusing on flexible object-oriented design and security. One such RCP application, written in NetBeans, was
awarded the 2012 Duke's Choice Community Choice Award. He is also co-organizing the hottest Java conference on earth, JCrete.

Join the Conversation


Join the Java community conversation on Facebook, Twitter, and the Oracle Java Blog!

E-mail this page Printer View

Contact Us Cloud Top Actions Key Topics


US Sales: +1.800.633.0738 Overview of Cloud Solutions Download Java ERP, EPM (Finance)
Have Oracle Call You Software (SaaS) Download Java for Developers HCM (HR, Talent)
Global Contacts Platform (PaaS) Try Oracle Cloud Marketing Cloud
Support Directory Infrastructure (IaaS) Subscribe to Emails CX (Sales, Service,
Commerce)
Data (DaaS)
Supply Chain
About Oracle Free Cloud Trial News
Industry Solutions
Company Information Newsroom
Database
Communities Events Magazines
MySQL
Careers Oracle OpenWorld Blogs
Middleware
Customer Successes Oracle Code
Java
JavaOne
Engineered Systems
All Oracle Events

Oracle Site Map Terms of Use and Privacy Prfrences de cookies Ad Choices

http://www.oracle.com/technetwork/articles/java/rcp-todo-2194057.html 22/22

You might also like