NetBeans Platform Application Developer and Architect living in Gauteng, South Africa. Member of the NetBeans Dream Team, and community elected member of the 20th NetBeans Governance Board. Hermien has posted 10 posts at DZone. You can read more from them at their website. View Full User Profile

Nodes with Dynamic Value Icons

12.01.2010
| 6556 views |
  • submit to reddit

The Nodes API provides the functionality to present data objects. It includes the functionality to customise the way in which nodes are displayed in explorer views. The focus of this short tutorial is changing the value icon for a node property.

The NetBeans Nodes API Tutorial explains in a lot of detail how to use the Nodes API. It is assumed that the reader is familiar with the basics of that tutorial, and as such some parts of this tutorial are not addressed in great detail.

Goal of the Tutorial

The goal of the tutorial is to create a view displaying the number of users connected to a system. When the number of users drops to 0, the connection status should include a warning icon. Figure 1 shows the desired end result. The view is a TopComponent containing an OutlineView.

 Completed Status Window

Figure 1: Completed Status Window

Step 1: Creating the Projects

Start off by creating a new NetBeans Platform Application Project, and adding a new empty module to it. All of the classes will be created in that module. The code name base I chose for my module is za.co.kitt.demo.nodesdemo – remember to modify the image locations in the code snippets to reflect your code name base.

Add dependencies on the following libraries to your module:

  • Explorer & Property Sheet API
  • Nodes API
  • Utilities API

Add these two icons to your module: Node icon Error Icon

Step 2: Creating the API Object

The next step is to create an API object that will contain the data to be presented. The Nodes API is intended as a presentation layer only, and as such the nodes should not contain the actual data directly. The very simple StatusObject class is shown below.

/**
 * Status data object, containing a single property for the
 * number of clients online.
 */
public class StatusObject
{
    int mNumberOfClientsOnline = 0;

    public int getNumberOfClientsOnline()
    {
        return mNumberOfClientsOnline;
    }

    public void setNumberOfClientsOnline(int NumberOfClientsOnline)
    {
        mNumberOfClientsOnline = NumberOfClientsOnline;
    }
}

Step 3: Creating the Node

Create the StatusNode class as shown below.

import java.awt.Image;
import java.lang.reflect.InvocationTargetException;
import org.openide.nodes.Children;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Sheet;
import org.openide.nodes.PropertySupport;
import org.openide.util.ImageUtilities;

/**
 * Node object demonstrating value icons.
 */
public class StatusNode extends AbstractNode
{

    private StatusObject mStatus = null;

    /**
     * Constructor.
     * @param Status The object containing the status to be displayed.
     */
    public StatusNode(StatusObject Status)
    {
        super(Children.LEAF);
        mStatus = Status;
        setDisplayName("Online");
    }

    @Override
    public Image getIcon(int type)
    {
        return ImageUtilities.loadImage(
                "za/co/kitt/demo/nodesdemo/nodeicon.png", true);
    }

    @Override
    public Image getOpenedIcon(int type)
    {
        return getIcon(type);
    }

    /**
     * Create a property sheet containing only the single status property.
     */
    @Override
    protected Sheet createSheet()
    {
        Sheet sheet = Sheet.createDefault();
        Sheet.Set set = Sheet.createPropertiesSet();
        set.put(new StatusProperty());
        sheet.put(set);
        return sheet;
    }

    /**
     * Property class displaying the number of online users.
     */
    public class StatusProperty extends PropertySupport.ReadWrite
    {

        /**
         * Constructor.
         */
        public StatusProperty()
        {
            super("Status", int.class, "Number of Users", "Number of Users");
        }

        /**
         * Get the value of the number of clients online property.
         * @return The number of clients online.
         * @throws IllegalAccessException
         * @throws InvocationTargetException
         */
        @Override
        public Object getValue() throws IllegalAccessException,
                InvocationTargetException
        {
            return mStatus.getNumberOfClientsOnline();
        }

        /**
         * Set the value of the number of clients online property.
         * @param NewValue The new value of the property.
         * @throws IllegalAccessException
         * @throws IllegalArgumentException
         * @throws InvocationTargetException
         */
        @Override
        public void setValue(Object NewValue) throws IllegalAccessException,
                IllegalArgumentException, InvocationTargetException
        {
            mStatus.setNumberOfClientsOnline((Integer) NewValue);
        }
    }
}

This class can be mostly recreated by following the NetBeans Nodes API Tutorial, perhaps with the exception of the StatusProperty inner class. It is a very simple extension of the ReadWrite property, that for now only contains the getValue and setValue methods.

The node itself has an icon as returned by the getIcon() method (the # icon shown in Figure 1).

Step 4: Creating the Top Component

Create a new TopComponent in the module. When prompted for the window position, choose Explorer. For convenience, check the Open on Application Start check box.

Create a private field in the TopComponent to contain the status object:

private StatusObject mStatus = new StatusObject();

Change the TopComponent class to implement the interface ExplorerManager.Provider, and add the following to the class:

private final ExplorerManager mgr = new ExplorerManager();

public ExplorerManager getExplorerManager()
{
    return mgr;
}

In design view, drag a Scroll Pane from the Swing Containers category onto the TopComponent, and call it mScrollPane. Change the custom creation code for mScrollPane to:

new OutlineView()

and fix imports.

Finally, in the constructor of the TopComponent after the initComponents() call, add:

StatusNode node = new StatusNode(mStatus);
((OutlineView) mScrollPane).setPropertyColumns(
        "Status", "Number of Users");
mgr.setRootContext(node);
associateLookup(ExplorerUtils.createLookup(mgr, getActionMap()));

Up to this point, nothing new or noteworthy has been implemented, and the window should now appear as in Figure 2.

 Status window with no value icon

Figure 2: Status window with no value icon

Step 5: Adding an Unchanging Icon

The Customizing how Properties are displayed javadoc page lists all the custom parameters that may be set to instruct editors how to display the property. (I discovered this page only after debugging through the org.openide.explorer source code while in search of something else.)

One of these parameters is valueIcon. This means that we can add

setValue("valueIcon", ImageUtilities.loadImage(
                "za/co/kitt/demo/nodesdemo/error_1.png", true));

to the constructor of the StatusProperty class to permanently set a value icon for the property. However, should we wish to remove the icon later,

setValue("valueIcon", null);

would cause a null pointer exception to be thrown, since the underlying collection does not allow null values. If we try

setValue("valueIcon", new ImageIcon());

an unwanted empty space is shown when editing the value, as shown in figure 3.

 Unwanted Empty Space

Figure 3: Unwanted Empty Space

Step 6: Changing the Value Icon Dynamically

To allow the icon to be changed according to the data of the StatusObject, simply add the following method override to the StatusProperty class:

/**
 * Gets the value of the named attribute.
 * @param AttributeName The name of the attribute.
 * @return If the attribute is "valueIcon", the return value is either
 * null if there are clients online, or an icon if no clients are
 * online.
 */
@Override
public Object getValue(String AttributeName)
{
    if ("valueIcon".equals(AttributeName))
    {
        if (mStatus.getNumberOfClientsOnline() == 0)
        {
            return ImageUtilities.loadImage(
                    "za/co/kitt/demo/nodesdemo/error_1.png", true);
        }
        else
        {
            return null;
        }
    }
    return super.getValue(AttributeName);
}

Note that the setValue() call added in step 5 no longer has any effect and may be safely removed.

Conclusion

Dynamically changing value icons are a powerful way of giving the user feedback on the status of data objects that are displayed by nodes. By simply overriding the getValue(String) method of a property, icons may be changed with minimal effort.

From http://www.kitt.co.za/index.php/articles/nodes-with-dynamic-value-icons/

AttachmentSize
error.png279 bytes
nodeicon.png571 bytes
nodes-dynamic-value-icons-nodesdemo01.png6.67 KB
nodes-dynamic-value-icons-nodesdemo02.png6.45 KB
nodes-dynamic-value-icons-nodesdemo03.png8.21 KB
Published at DZone with permission of its author, Hermien Pellissier.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Nicholas Dunn replied on Mon, 2010/12/13 - 10:06pm

I tried what you suggested and the node's icon never changed.  Don't you need to fire a property change so that the valueIcon property is queried?  Perhaps with fireIconChange()?

Hermien Pellissier replied on Wed, 2010/12/15 - 8:16am in response to: Nicholas Dunn

Make sure that you are overriding the getValue() method of the StatusProperty and not the node. The node has a similar method, but that is not the one that is called. The getValue() method of the property is called when the COMMAND_SUCCESS action is fired from the InplaceEditor used for the particular property. This editor could be either a custom one that you create, or a built in one as for the example.

Hermien Pellissier replied on Wed, 2010/12/15 - 8:21am in response to: Nicholas Dunn

The fireIconChange() method is useful only when the node icon changes, not the value icon. It fires a property change for the "icon" property, not the "valueIcon" property. If you want to manually fire the event (which should not be necessary), try to call the following on the node: firePropertyChange("valueIcon", oldIcon, newIcon);

Comment viewing options

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