TodoRCP

This article has been published in Java Magazine.


By John Kostaras


Back in May 2006, the first issue of NetBeans Magazine was released. A high quality publication with excellent articles, a very good effort indeed. Unfortunately, only 4 issues were published in total.
This article is a re-write of an article back then, in the first issue, "A complete App using NetBeans 5" by Fernando Lozano. The differences are that in this article I 'll use Netbeans 7.0 and we shall see how we can develop the same Swing application using the Rich Client Platform. You may download the original application to compare it with the RCP application that we are going to develop in this article. If you did so, then open it in NetBeans and create a new Project Group for it (once you opened the project in NetBeans, right click somewhere inside the "Project" tab and select Project Group -> New Group... give it a name and click on Create Group button. Later, create another group for the new RCP application so that you can switch among the two project groups by right clicking and selecting Project Group and the appropriate group.

Contents

The Todo Application

The example will build a to-do list commonly found as part of PIM suites. It won’t just demo the IDE's RCP features but it will also stick to Object-Oriented best practices, showing that you can develop GUI applications quickly and interactively, without compromising long-term maintenance and a sound architecture.
The to-do application will be developed using a three-step process. The first step prototypes the UI design, where NetBeans really shines. The second step focuses on user interaction and event handling; it’s actually a second prototype for the application. The third and last step builds the persistence and validation logic. Readers familiar with the MVC architecture will note these steps form a process that starts with the View, then builds the Controller, and finally builds the Model.

Developing the sample app

Here is a short list of requirements for it:

  • Tasks should have a priority, so users can focus first on higher-priority tasks;
  • Tasks should have a due date, so users can instead focus on tasks which are closer to their deadline;
  • There should be visual cues for tasks that are either late or near their deadlines;
  • Tasks can be marked as completed, but this doesn’t mean they have to be deleted or hidden.

There will be two main windows for the Todo application: a tasks list and a task-editing form. A rough sketch for both is shown in Figure 1.

"Figure 1 Todo Application prototype"

The Todo application is going to be developed in three steps:

  1. Build a “static” visual prototype of the user interface, using a visual GUI builder.
  2. Build a “dynamic” prototype of the application, coding user interface events and associated business logic, and creating customized UI components as needed.
  3. Code the persistence logic.

Step 1 - Designing the tasks list window

Let's get started. Click on the New Project toolbar button and select the NetBeans Platform Application in the NetBeans Modules category. Use "TodoRCP" as the project name and choose a suitable project location (anywhere in your hard disk). Then click Finish.

"Figure 2 - Create a New RCP Project"

NetBeans creates the RCP project containing an empty Modules folder and an Important Files folder. This is the 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" as the Code Base Name and then click on Finish.

"Figure 3 - Create a New Module"

In NetBeans 7.2, the Generate XML Layer check box has been removed. To create layer.xml, right click on 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 RCP combines all layer.xmls during runtime and creates what is called the central registry of the application.
Right-click on the View module and select Open Project.

"Figure 4 - New Module layout"

The new module contains a package called todo.view. We now need to create our view. But instead of creating a JFrame Form as is done in the Swing application, here we will create its equivalent, a TopComponent.
Right click on the View module that we just opened and select New --> Window if the module is not already open.

"Figure 5 - New Window"

The dialog box asks you for the window position. NetBeans IDE has various positions; the editor is the main area, output is the lower area where messages are displayed etc. Make the selections shown in the above figure and click on Next.

"Figure 6 - New Window name"

The Class Name Prefix is the name of the Frame or Panel that will be created along with some other help files. Provide the name "Tasks" (instead of "TasksWindow" in the original article) and click Finish. Two files have been created, TasksTopComponent.java and TasksTopComponent.form and the form has been opened in Design mode in the editor along with the Palette.
However, if you can see the TasksTopComponent.form file in the Projects tab and NetBeans complains that it cannot recognize the file when you open it, you need to activate Java SE plugin from Tools -> Plugins --> Installed.

"Figure 7 - The Palette"

Notice also the location of the Projects, Navigator and Properties windows, the Palette and the editor area. A red frame highlights the selected component (the TopComponent in the figure). The navigator displays all visual and non-visual components on the TopComponent, which is handy when you need to change the properties of a component hidden by another or too small to be selected in the drawing area. To the right there’s a component palette, which shows by default the standard Swing components (you can also add third-party JavaBeans), as well as the properties windows. Properties are categorized to ease access to the ones most commonly used, and changed properties have their names highlighted in bold. To change the visual editor IDE layout, you can drag each window to another corner of the main window or even leave some windows floating around.
In previous versions of NetBeans, the layer.xml contained a TasksTopComponent displayed in editor mode. In version 7, this information exists in annotations. Click on the Source button to view the following annotations and compare it with the dialog box of figure 6 above:

@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"
})

Notice that mode = "editor" (if not, here is your chance to change it) and the openAtStartup = true (the same applies as above).
The NetBeans' visual editor is unlike other visual Java editors you may have seen. Just right-click inside the TopComponent and select the Set Layout menu item. You’ll see that the default choice is not a traditional Swing/AWT layout manager; it’s something named “Free Design”. This means you are using the Matisse Visual GUI builder. Matisse configures the TopComponent to use the GroupLayout layout manager developed in the SwingLabs java.net project, which is included as a standard layout manager in Java 6.

As shown in Figure 1, the task list consists of a menu, a toolbar, a table and a status bar. Apart from the table, which in RCP is called OutlineView, all the rest are handled by the platform as we shall see shortly. In order to add the OutlineView we need to add a dependency to the Explorer & Property Sheet API. Right-click on the Libraries folder of the View module and select Add Module Dependency, select the aforementioned API and click OK. Right-click on the TopComponent on the Design view and change its layout to BorderLayout.
From now on we have two options.

Option 1

The first option is rather a hack, but it is faster. Drag a ScrollPane from the palette and drop it in the center of the TasksTopComponent. Right-click the JScrollPane in the Inspector (look at left bottom) and change its variable name to outlineView. Then, in the Properties box, click on Code and then on the small button [...] of Custom Creation Code property, and add the following:

new OutlineView()

Switch to Source in the editor, 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.

If the palette is not visible, display it from Window -> Palette. Right click inside the palette window and select the Palette Manager. Create a new category by clicking on the New Category button and name it something like NetBeans RCP or NetBeans platform components. Then, click on Add from JAR button, navigate to <Netbeans installation> -> platform -> modules, select org-openide-explorer.jar and click Next. Select all available components and click Next. Select the category you created previously and click Finish. Click on Close to close the palette manager. The new category is now shown in the palette. Locate the OutlineView component and drag it inside the form.

"Figure 8 - The new palette category"

NetBeans visual editor should automatically add them to the palette once the developer has chosen to create an RCP application with nice icons next to them. A note to the NetBeans team to be included to future releases? :)

Let's see what have we done so far. Run the application and this is what you will see:

"Figure 9 - An empty RCP Application"

Look at how many things you get out of the box, which in a normal Swing application you should develop yourself: a menu bar and a toolbar (which need customization), a status bar and a strange tree-table component which we 'll further develop shortly, and all these with just one line of code.
Let's start by customizing the menu. As already mentioned, there is a central registry which holds information about every module of the RCP application. You can find this central registry if you click on Important Files -> XML Layer -> <this layer in context> -> Menu Bar:

"Figure 10 - The central registry"

From Figure 1, we see that we only need File, Edit and Options menus. The rest can be deleted by selecting them, choosing right-click and Delete or renamed to meet our needs. Do the same for the Toolbars by keeping/renaming only those we need, i.e. File and Edit, by renaming Tools to Options and by removing the rest. The result should be something like in the following Figure.

"Figure 11 - The central registry customized"

Let's start building our prototype. Open TasksTopComponent and add the following lines of code at the end of the constructor if you followed Option 1 above:

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);

or the following if you chose Option 2:

//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 = outlineView.getOutline().getColumnModel();
ETableColumn column = (ETableColumn) columnModel.getColumn(0);
((ETableColumnModel) columnModel).setColumnHidden(column, true);

See http://jnkjava.wordpress.com/2013/04/14/recipe-8-hide-node-column-in-outlineview/.
The only difference is this line of code which is not needed in the second case as we don't need to cast the JScrollPane to an OutlineView.

OutlineView ov = (OutlineView)outlineView; 

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? Here is a hint: right-click on the Libraries folder).

"Figure 12 - An empty RCP Application with table headers"

Let's create the status bar. To display a StatusBar in Netbeans RCP, a class must implement the StatusLineElementProvider interface and declare it as a service. Right click on todo.view package and select New-> Java Class. Name it StatusBar and click Finish. Copy paste the following code:

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("Todo - Task List");
    }
    
}

Don't forget to right click and select Fix imports as well as right click and Format to format the code.
So, what is going on here? As you can see, the StatusLineElementProvider interface is very flexible as you can return any component you want, a JLabel, a JButton, a JPanel etc. 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 place. Have a quick look to this blog if you wish to learn why. In short, this line adds your class to the application's default lookup, which is then searched by NetBeans RCP itself for StatusLineElementProvider.class and adds all the providers it finds in the status bar.

Let's create the toolbars. A toolbar contains actions, so we shall create the actions and insert them to the appropriate toolbars in the central registry. We shall need the icons from the original todo application, so if you haven't done so, download the zip file that contains them and unzip it in your disk.
The actions are controllers, so create a new module called Controller as we learned in the beginning of this article, with todo.controller as the code name base. Since we are here, create also the Model module with 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 on the todo.controller.edit package and select New Action. We shall create the Add Task action which is always enabled and click Next. Next, 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, we selected 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. Dropdown menus show you possible locations for display. HERE identifies the location where display of your action will be inserted. Don't forget to add a keyboard shortcut (Insert as in the old todo application).

"Figure 13 - A New Action"

Name the class as shown in the following figure and don't forget to add an icon.

"Figure 14 - Add Task Action"

The AddTaskAction is created:

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
    }
}

Look how the input from the wizard is translated into Java annotations in NetBeans 7. In previous versions this information was added in layer.xml. Modify the position to be 10 in both cases to avoid mix-ups with the next actions that you will create. Add 10 to the position of every new action, i.e. for EditTaskAction set position to 20, for DeleteTaskAction to 30 etc. We shall leave the action body empty for the moment.

By executing the old todo application, you may 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 table, in other words, they are context actions. We can accomplish the same functionality by using a Conditionally Enabled action, via cookies. These actions operate on nodes. A node is the visual representation of a particular piece of data, a 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 should be invoked by the action. 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.

So, in order to create the EditTaskAction, right-click on the todo.controller.edit package and select New Action. Select a Conditionally Enabled action where user selects one node. The cookie class is a Task. This means, that whenever a task (row) is selected in the OutlineView, this action will be enabled. Click Next and complete the rest of steps as mentioned previously for the AddTaskAction. If you completed the wizard correctly, you should see:

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) {
        
    }
}

Shortcuts are defined as follows (for platform compatibility):

  • Alt  : O-
  • Ctrl  : D-
  • Shift : S-

Your compiler will complain though because it cannot find a Task class. This is true; you need to copy the Task class to your Model module from the old todo application. Once you 've done so, you need to expose this class to the other modules. Right-click on the Model module and select Properties | API Versioning, and check the todo.model package from the Public packages to make it public. Clean and build the Model module. Then, you need to add a dependency from Controller to Model. You do this by right-clicking on Libraries of Controller module and selecting Add Module Dependency action. Search for Model and press OK. Now you may fix imports and have todo.model.Task added as import to EditTaskAction.

Repeat the above steps to create the rest of the actions, trying to make the RCP application as similar as possible to the old todo 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 user may select multiple nodes since they can be applied to many tasks at once.

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({
    @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) {
            
        }
    }
}

The static visual prototype should be like in the following figure. If the order of the toolbars is not correct, you can modify it by right-clicking on the XML Layer and selecting Open or more specifically right-clicking on the Toolbars node and selecting Go to Declaration. The layer.xml opens and you can locate the toolbars and change their position attribute (smaller value means the toolbar is displayed first).

"Figure 17 - Static visual prototype"

The details task dialog box needs to be created, too. To save us some time and effort, simply copy the TaskDetailsDialog class from the todo application (if you haven't opened it in NetBeans, then now it 's your chance), and paste it inside todo.view in View module. You will notice some errors. Add a dependency from View to Model module, in order for the former to be able to access the Task class.
The other error can be resolved if you also copy the ActionSupport class from todo application. You might encounter the following error during build:

...\netbeans\harness\build.xml:174: Module org.jdesktop.layout excluded from the target platform
BUILD FAILED (total time: 5 seconds)

First, add a dependency to the deprecated module org.jdesktop.layout module. Then, open 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. It should compile fine now.
To be in accordance with the article's end of step 1, we need to complete the AddTaskAction to display the TaskDetailsDialog. Simply copy the code of Listing 1 of the article inside the actionPerformed() method of AddTaskAction as shown below:

 TaskDetailsDialog taskDetailsDialog = new TaskDetailsDialog(null, true);
 taskDetailsDialog.setNewTask(true);
 taskDetailsDialog.setTask(new Task());
//        taskDetailsDialog.addActionListener(this);
 taskDetailsDialog.setVisible(true);

EditTaskAction is also easy to write:

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);
    }
}

You need to make the todo.view package of View module public and add a dependency from Controller to View. 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 we are almost ready to show our prototype to our client. Right-click TodoRCP and select Branding. Here you can select a splash screen as well as the icon to display when the application is minimized.

Right click on TodoRCP module suite and select Properties and select the Installer category. Check the platforms that you wish to create installers for (e.g. Windows, MacOSX, Linux) and press OK. Right click on TodoRCP module suite again and select Package as and select one of the available options e.g. Installers or Zip distribution etc. If everything ran smoothly, a new dist folder should be created which contains the deployed application. Copy it to your customer's machine and run it by going inside dist/todorcp/bin and executing the executable file (e.g. depending on your platform could be todorcp.exe or something).

This prototype is almost the finished application from the UI design perspective, but in real projects you shouldn’t spend too much time perfecting its looks. Remember, the prototype is a tool to gather and validate user requirements and lessen the risk of missing important application functionality.

Step 2 - Todo Application Architecture

The second step – building the "dynamic prototype" – aims to implement as much user interaction as possible without using a persistent storage or implementing complex business logic. Following the original article [1], we’ll use two well-known design patterns in the Todo application: DAO (Data Access Object) and the MVC (Model-View Controller). We’ll also define a VO (Value Object) named Task for moving information between application tiers. Therefore the view classes (such as the 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. See Figure 20 on page 14 of the original article [1] for the UML class diagram. Notice that the packages todo.model, todo.controller, todo.view of the original todo application have been transformed to modules in TodoRCP.

"Figure 18 - The TodoRCP UML class diagram"

Here’s the plan for building the second prototype:

  1. Display the visual cues for late and completed tasks;
  2. Handle action events to sort and filter the tasks list;
  3. Handle action events to create, edit and remove tasks.

Items 1 to 2 can be implemented and tested with a mock model object (TaskManager) that always returns the same task collection. Item 3 can be tested with a mock object that simply adds or removes objects from that collection.

But before we do this, let's display some data. First, we need to wrap our model (Task) to a Node. Swing components follow the MVC design pattern, however a different model is needed for each visual component, e.g. a TableModel for a JTable, a ListModel for a JList etc. NetBeans RCP attempts to create a true MVC design by creating a single model that can be used by all view components (OutlineView, BeanTreeView etc.). This is done by wrapping the model to a Node. There are a number of Nodes provided by the Nodes API that are shown in the following diagram, each one with a different purpose (see [2]).

"Figure 19 - The available Node types"

In the following we shall use the BeanNode which uses reflection to retrieve the VOs' attributes. Create the following class inside View module (and add a dependency on Node API):

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);
    }   
}

To display them in the OutlineView, we 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:

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;
    }
}

We populate the OutlineView with mock data as shown in method createKeys() above. NetBeans will prompt you to create two new constructors in Task.

Finally, TaskTopComponent needs to be modified as follows:

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;
    }

The ExplorerManager, which is the controller of the explorer views, needs a root node element. We pass it an AbstractNode which needs the Children to use from the TaskChildFactory.

With much fewer lines of code we have a running prototype with populated data. Build and run it to see a view similar to the following figure:

"Figure 20 - The prototype with populated data"

However, when you select one or more rows, the conditionally enabled actions are not enabled accordingly. To do this we need to add the Task object to the TaskNode 's lookup and then we need to set the TopComponent 's lookup to be that of its nodes.
To do the above, modify TaskNode like so:

class TaskNode extends BeanNode<Task> {

    public TaskNode(Task bean) throws IntrospectionException {
        super(bean, Children.LEAF, Lookups.singleton(bean));
    }   
}

which adds the task to the node's lookup. The singleton lookup is a lookup that contains only one object, which in our case is our task [7]. Then, you set the TopComponent 's lookup to be that of the nodes' by adding after the line where you set the root context for the explorer manager the line:

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

In order to customize the outline view so that it displays a collection of Task objects, we change each row background colors 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 here.

TaskTopComponent needs to be modified as follows:

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;
            }
        });
      ...
    }

and the new utility class Decorator does the decoration:

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);
            }
        }
    }
}

Handling Internal Events

Having a display of tasks ready, it’s time to add some event-handling. In the original article it was useful to separate UI events into two mutually exclusive categories:

1. Internal events, that affect just the view itself.
2. External events, which cause model methods to execute.

Among internal events, are selection changes and clicks on Cancel buttons. In the original article, 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 and Remove task menu item, and the corresponding toolbar buttons.
As we already saw, such events are now handled by conditionally enabled actions via cookies inside the controller package and are not dealt by the view package anymore.

Handling External Events

The category of events the author of the original article calls "external" should not be handled by view classes. They 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 todo application includes the todo.view.ActionSupport class. This class 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, e.g. add/removeNewTaskListener(), add/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 are not needed in TodoRCP anymore. The RCP framework takes care of all this. What we need to do is simply complete the actionPerformed() methods of our actions. So our job is to transfer the logic from QueryEditTasks to our actions. Let's see how do 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());
                }
        }
    }
}

In order to make DeleteTaskAction functional we need to copy todo.model package classes from the original todo application to our Model todo.model package. However, the TaskManager contained in the original todo application contains all the logic to save tasks in a persistent storage such as a relational database. Since this is left for step 3, we won't use it for this step. We are going to leave for step 3 the implementation of NewTaskListAction and OpenTaskListAction, too.
However, we shall create a TaskManager that stores Tasks in memory. The TaskManager class is a DAO (Data Access Object). Being the only DAO on 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. We start by creating the following interface in Model module based on the persistent TaskManager of the original article:

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);
}

Then the TaskManager is implemented as a service:

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());
        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 boolean isEmpty(final String str) {
        return str == null || str.trim().length() == 0;
    }

    private void validate(final Task task) throws ValidationException {
        if (isEmpty(task.getDescription())) {
            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());
        }
    }
}

Note that we moved the mock data from TaskChildFactory.createKeys() method to the constructor of TaskManager. Note how we added the class to the default lookup by annotating it like so: @ServiceProvider(service = TaskManagerInterface.class) - see here on how to retrieve an implementation class from the default lookup.
TaskChildFactory.createKeys() now becomes:

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

DeleteTaskAction becomes:

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);
    }

    @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());
            }
        }
    }
}

and MarkAsCompletedTaskAction becomes:

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);
        }
    }
}

The ActionSupport which you might probably copied from the original todo application to eliminate the errors of TaskDetailsDialog is not needed anymore, so remove it from the View module and let's tackle the errors. Initially, remove all statements from TaskDetailsDialog that refer to ActionSupport. Add a reference to TaskManager:

    private final TaskManagerInterface taskManager;

    public TaskDetailsDialog(java.awt.Frame parent, boolean modal) {
        super(parent, modal);
        initComponents();
        setLocationRelativeTo(parent);
        taskManager = Lookup.getDefault().lookup(TaskManagerInterface.class);
    }

and add actions to Remove and Save buttons:

 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();
}         

Sorting comes out of the box in OutlineViews by clicking on the specific header/field. Clicking once sorts the column in ascending order, clicking once more in descending order and once more leaves it as it was originally. However, if you want to implement the SortByDateAction and SortByPriorityAction in order to see how can sorting be done programmatically, do the following:
Create a new Utilities class inside todo.view and copy the code from Recipe 6: Sort an OutlineView programmatically and paste it to the Utilities class. Since the Utilities class will contain only static utility methods, add an empty private constructor to it to avoid initialisation. Add the missing dependency to 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);
}

This is in order to avoid adding a dependency to Explorer & PropertySheet API to Controller module. Because, in order to call the original method in Utilities class we need to add a reference to OutlineView which is contained in Explorer & PropertySheet API in our Controller module. The above workaround saves us this dependency.

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);
    }
}

Regarding filtering, you may right click on 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, our two actions ShowAlertsAction and ShowCompletedTasksAction can not be displayed by these out-of-the-box filters.

The ShowAlertsAction displays only the tasks that have alerts, i.e. alert==true. Let's start from the code generated by the wizard.

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
    }
}

First, we need to convert the button to a toggle button. However, JToggleButton doesn't really work with the NetBeans toolbar, so you need to extend BooleanStateAction and since it implements ActionListener this can be deleted from our action.

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";
    }
}

Since, due to lazy initialization, it doesn't recognize the values passed in the annotations, we need to override the iconResource() method. Because, by default, it is selected, we need to set it to unselected in the initialize() method. The Outline class provides a method setQuickFilter(int col, Object filterObject). filterObject can either be a value that matches one of the values of the column or a QuickFilter object:

public interface QuickFilter {
    
    /** If the object is accepted its row is displayed by the table. */
    public boolean accept(Object aValue);
}

In our case, the accepted value is simply true which means just accept those rows that contain the value true. So the actionPerformed() method can be implemented as:

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

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

public void setQuickFilter(int column, Object filter) {
   outlineView.getOutline().setQuickFilter(column, filter);
}
    
public void unsetQuickFilter() {
   outlineView.getOutline().unsetQuickFilter();
}

Whether the toggle button is pressed or not is defined by getBooleanState(). Finally, super.actionPerformed(e); toggles selection/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). For this, we need a mechanism where we pass each one of the Tasks and we check whether completed == true.

TODO: Finish ShowCompletedTasksAction.

Add an inplace property editor for Dates

Note! You will need NetBeans 7.3 for this functionality. It can run with earlier versions with some more effort.

Adding new tasks or editing tasks goes smooth apart from when you have to add/edit the due date. To provide a more user-friendly date input, we follow the NetBeans Property Editor Tutorial and most specifically the section Creating a Custom Inplace Editor. We will use the date picker component of the SwingX library which you can find inside ide/modules/ext/ folder of your NetBeans installation.
However, instead of adding the swingx.jar as a new library, as in the original article, we follow a different approach. The reason is, that later on, we shall need hsqldb.jar for persisting the tasks to the database and maybe other libraries. In the following, we create a new module Libraries inside which we wrap our various external libraries. The benefit is that we need to add only one new module to our suite and a single reference to it from the modules that need it. The drawback is that if we need only one jar, we need to reference all other jars that are wrapped inside the Libraries module.

  1. Right-click the Modules folder icon of the TodoRCP module suite and choose Add New. Type "Libraries" as the module name and click Next. Type "lib" as the Code Base Name and then Finish.
  2. Right-click on the newly created module and select Properties --> Libraries --> Wrapped JARs. Click on Add JAR and add the ide/modules/ext/swingx.jar. Repeat the procedure to add hsqldb.jar which you can download from http://hsqldb.org/.
  3. Select the API Versioning category from the left panel of the opened Project Properties - Libraries dialog box and make the following packages public by checking them:
  • org.hsqldb
  • org.jdesktop.swingx

After clicking on OK, clean and build the Libraries module.
Add a dependency to the Libraries module in the View module. Now you are ready to make use of the date picker. This will involve implementing a couple of NetBeans-specific interfaces:

  1. ExPropertyEditor — a property editor interface through which the property sheet can pass an "environment" (PropertyEnv) object that gives the editor access to the Property object it is editing and more.
  2. InplaceEditor.Factory — an interface for objects that own an InplaceEditor.
  3. 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:

  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);
   }
   
   @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) {
           //do nothing - not needed for this component
       }
   }
  }

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 only works with 7.3 or later, is to add the following annotation to the definition of the class in order to register DatePropertyEditor globally, i.e. 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 TodoRCP application again. A date picker now appears when you try to edit the due date field.

"Figure 21 - A date picker for due date"

If you use 7.2 or earlier, then you need to do more work to achieve the above functionality. You will need to override the createSheet() method of TaskNode and edit the properties, setting the following to the date property, too:

  dateProp.setPropertyEditorClass(DatePropertyEditor.class); 

See here and here on how to do that. Also this blog is quite helpful.

We need to add a date picker to the TaskDetailsDialog too. We shall add the visual components of the swingx.jar to the Palette, like we did previously for the Explorer & Property Sheet API. Open TaskDetailsDialog in Design mode, if you haven't already done so. Right click inside the palette window and select the Palette Manager. Create a new category by clicking on the New Category button and name it SwingX. Then, click on Add from JAR button, navigate to TodoRCP/Libraries/lib, select swingx.jar and click Next. Select all available components and click Next. Select the category you created previously and click Finish. Click on Close to close the palette manager. The new category is now shown in the palette.

First, delete the Due date text field from the form, and then, locate the JxDatePicker component in the palette and drag it inside the form in the same place where the Due date text field was. Change to the Source mode, 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 on 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, you will realise that your new task never appears in the OutlineView. The reason is that the OutlineView is never notified of changes to the model. We need to fix this.
First, modify TaskManagerInterface by adding two more methods to it:

void addPropertyChangeListener(PropertyChangeListener listener);
    
void removePropertyChangeListener(PropertyChangeListener listener);

and in the TaskManager implement these methods like so:

@ServiceProvider(service = TaskManagerInterface.class)
public class TaskManager implements TaskManagerInterface {
    private final PropertyChangeSupport pcs = null;
    ...

    /**
     * @return a thread-safe PropertyChangeSupport
     */
    private PropertyChangeSupport getPropertyChangeSupport() {
        if (pcs == null) {
            pcs = new PropertyChangeSupport(this);
        }
        return pcs;
    }

    @Override
    public void addTask(final Task task) throws ValidationException {
        validate(task);
        tasks.add(task);
        getPropertyChangeSupport().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);
        getPropertyChangeSupport().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);
        getPropertyChangeSupport().firePropertyChange("DELETED", task, null);
    } 
    ...
    @Override
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        getPropertyChangeSupport().addPropertyChangeListener(listener);
    }

    @Override
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        getPropertyChangeSupport().removePropertyChangeListener(listener);
    }
}

Finally, TaskChildFactory needs to be notified:

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);
    }
    ...
}

These changes should allow the OutlineView to be updated accordingly.

End of Step 2

Now that we have fully functional view and model classes, it’s time to start replacing 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 UI, building the two prototypes in sequence as we did, and another team working on business and persistence logic, preferably using TDD. They can work in parallel and join at the end, putting together functional view and controller implementations with functional model implementations. Most of the work in this step was just coding. NetBeans provides nice code editors and a good debugger that eases the task providing the usual benefits: code-completion, JavaDoc integration and refactoring support. But it can go beyond: it’s very easy to build in NetBeans new plug-in modules to package your project coding standards, such as project templates, controller class templates and so on.

Step 3 - Model classes and database

The Todo application uses HSQLDB, an embedded Java database. This allows the application to meet the ease of deployment requirements for a typical desktop application. In RCP applications it is not as straightforward to add the archive hsqldb.jar to a modules' libraries since we deal with modules. To allow for future libraries, we created a new module, 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 provides direct support for browsing any JDBC-compliant database and submit SQL commands. Switch to the Services tab or open it from the Window menu. Expand the Databases and then the Drivers categories. Open the Drivers folder. If the HSQLDB driver is not there, right-click on Drivers, select New Driver and add the location of the hsqldb.jar archive. NetBeans will often set the database driver class name by itself. Now right-click the HSQLDB driver icon, and choose Connect using... menu item. Provide the parameters to connect to your local Todo database, using the following Figure as a template. 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 like creating new tables, altering columns and viewing data. Most operations have easy-to-use wizards.

The Todo application uses HSQLDB in the stand-alone mode, which locks the database files for exclusive access. So it won’t be possible to use the NetBeans database console while the application is running. However it’s possible to 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.


TODO: FINISH THIS STEP


This article has been published in Java Magazine.

References

  1. Lozano F. (2006), "A complete App using NetBeans 5", NetBeans Magazine, Issue 1, May, http://netbeans.org/download/magazine/01/nb01_completeapp.pdf
  2. Petri J. (2010),NetBeans Platform 6.9 Developer's Guide, Packt Publishing.
  3. Löf N., layerxml a netbeans rcp blog, http://layerxml.wordpress.com/
  4. Epple A. "Quick Tip 44: Custom Renderer for OutLineView", Tony's blog, http://eppleton.de/blog/?p=1064
  5. Kostaras J. "NetBeans RCP Recipes", http://jnkjava.wordpress.com/
  6. NetBeans Property Editor Tutorial, http://platform.netbeans.org/tutorials/nbm-property-editors.html#inplace-editor
  7. Epple, T. (2009), "NetBeans Lookups Explained!", http://netbeans.dzone.com/articles/netbeans-lookups-explained
Not logged in. Log in, Register

By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2012, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo