blob: 22fdc174b5d2829b547ed333d9a97f423768102e [file] [log] [blame]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- -*- xhtml -*- -->
<title>NetBeans Nodes API Tutorial for NetBeans Platform 7.0</title>
<link rel="stylesheet" type="text/css" href="https://netbeans.org/netbeans.css"/>
<meta name="AUDIENCE" content="NBUSER"/>
<meta name="TYPE" content="ARTICLE"/>
<meta name="EXPIRES" content="N"/>
<meta name="developer" content="tboudreau@netbeans.org"/>
<meta name="indexed" content="y"/>
<meta name="description"
content="A walk-through of enhancing Nodes with properties and other decorations."/>
<!-- Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. -->
<!-- Use is subject to license terms.-->
</head>
<body>
<h1>NetBeans Nodes API Tutorial</h1>
<p>This tutorial shows how to make use of some of the features of the Nodes API
in NetBeans. It shows how to do the following:</p>
<ul>
<li>Decorate Nodes with icons</li>
<li>Use HTML markup to enhance how Nodes are displayed</li>
<li>Create properties for display in the property sheet</li>
<li>Provide Actions from Nodes</li>
</ul>
<p>This tutorial is intended as a follow-on to the
<a href="nbm-selection-1.html">NetBeans Selection Management Tutorial</a>, which
covers how <code>Lookup</code> is used in managing selection in the NetBeans
windowing system, and its <a href="nbm-selection-2.html">follow-on tutorial</a>
which demonstrates how to use the Nodes API in managing selection.</p>
<p>As its basis, this tutorial uses the source code created in the first tutorial
and enhanced further in the second. If you have not yet done these tutorials,
it is recommended to do them first.</p>
<p><b class="notes">Note:</b> This document uses the NetBeans IDE 7.0 Release or above. If you
are using an earlier version, see <a href="691/nbm-nodesapi2.html">the previous version
of this document</a>.</p>
<p><b>Contents</b></p>
<p><img src="../../images/articles/70/netbeans-stamp.gif" class="stamp" width="114" height="114" alt="Content on this page applies to NetBeans IDE 7.0" title="Content on this page applies to NetBeans IDE 7.0"/></p>
<ul class="toc">
<li><a href="#nodes-background">Creating a Node subclass</a></li>
<li><a href="#displayname-html">Enhancing Display Names with HTML</a></li>
<li><a href="#icons">Providing Icons</a></li>
<li><a href="#actions">Actions and Nodes</a></li>
<li><a href="#action-presenters">Presenters</a></li>
<li><a href="#propertysheet">Properties and the Property Sheet</a></li>
<li><a href="#read-write-properties">Read-Write Properties</a></li>
<li><a href="#separate-property-groups">Grouping Property Sets</a></li>
<li><a href="#caveats">General Property Sheet Caveats</a></li>
<li><a href="#review">Review of Concepts</a></li>
<li><a href="#next">Next Steps</a></li>
</ul>
<p><b>To follow this tutorial, you need the software and resources listed in the following
table.</b></p>
<table>
<tbody>
<tr>
<th class="tblheader" scope="col">Software or Resource</th>
<th class="tblheader" scope="col">Version Required</th>
</tr>
<tr>
<td class="tbltd1"><a href="https://netbeans.org/downloads/index.html">NetBeans IDE</a></td>
<td class="tbltd1">version 7.0 or above</td>
</tr>
<tr>
<td class="tbltd1"><a href="http://java.sun.com/javase/downloads/index.jsp">Java Developer Kit (JDK)</a></td>
<td class="tbltd1">Version 6</td>
</tr>
</tbody>
</table>
<p class="tips">Optionally, for troubleshooting purposes, you
can <a href="http://plugins.netbeans.org/PluginPortal/faces/PluginDetailPage.jsp?pluginid=3146">download the completed sample</a>.</p>
<h2><a name="nodes-background"></a>Creating a Node subclass</h2>
<p>As mentioned in the <a href="nbm-selection-2.html">previous tutorial</a>,
Nodes are <i>presentation objects</i>. That means that they are not a data
model themselves&#8212;rather, they are a presentation layer for an
<i>underlying data model</i>. In the Projects or Files windows in the NetBeans
IDE, you can see <code>Node</code>s used in a case where the underlying data model is
files on disk. In the Services window in the IDE, you can see them used in a case
where the underlying objects are configurable aspects of NetBeans runtime
environment, such as available application servers and databases.</p>
<p>
As a presentation layer, <code>Node</code>s add human-friendly attributes to the
objects they model. The essential ones are:</p>
<ul>
<li><b>Display Name</b>&#8212;a human readable, user-friendly display name</li>
<li><b>Description</b>&#8212;a human readable, user-friendly description, often shown as a tooltip</li>
<li><b>Icon</b>&#8212;some glyph that graphically indicates the type of object shown and possibly
its state</li>
<li><b>Actions</b>&#8212;actions that appear on the context menu when the node is
right-clicked, which can be invoked by the user</li>
</ul>
<p>In the preceding tutorial, you used your <code>MyChildren</code> class
to create <code>Node</code>s, by calling</p>
<pre class="examplecode">
new AbstractNode(Children.create(new MyChildren(), true), Lookups.singleton(key));</pre>
<p>and then calling <code>setDisplayName(key.toString())</code> to provide a basic
display name. There is much more that can be done to make your <code>Node</code>s more
user-friendly. First you will need to create a <code>Node</code> subclass to
work with:</p>
<ol>
<li>In the My Editor project, right click the package <code>org.myorg.myeditor</code>
and choose New &gt; Java Class.</li>
<li>When the wizard opens, name the class &quot;MyNode&quot; and press Enter
or click Finish.</li>
<li>Change the signature and constructors of the class as follows:
<pre class="examplecode">
public class MyNode extends AbstractNode {
public MyNode(APIObject obj) {
super (Children.create(new MyChildren(), true), Lookups.singleton(obj));
setDisplayName ( "APIObject " + obj.getIndex());
}
public MyNode() {
super (Children.create(new MyChildren(), true));
setDisplayName ("Root");
}
....
....
....
</pre>
</li>
<li>Open <code>MyEditor</code> from the same package, in the code editor.</li>
<li>Replace these lines in the constructor:
<pre class="examplecode">
mgr.setRootContext(new AbstractNode(new MyChildren()));
setDisplayName ("My Editor");</pre>
with this single line of code:
<pre class="examplecode">
mgr.setRootContext(new MyNode());</pre>
</li>
<li>Now you will make a similar change to the Children object. Open
the <code>MyChildren</code> class in the editor, and change its
<code>createNodes</code> method as follows:
<pre class="examplecode">
@Override
protected Node createNodeForKey(APIObject key) {
return new MyNode(key);
}</pre>
</li>
</ol>
<h2><a name="displayname-html"></a>Enhancing Display Names with HTML</h2>
<p>The code is now runnable, but so far all you've done is moved logic around. It
will do exactly what it did before. The only (non-user-visible) difference you
now are using a Node subclass instead of just using AbstractNode.</p>
<p>
The first thing you will do is provide an enhanced display name. The Nodes and
Explorer APIs support a limited subset of HTML which you can use to enhance how
the labels for <code>Node</code>s are shown in Explorer UI components. The following
tags are supported:</p>
<ul>
<li>font color&#8212;font size and face settings are not supported, but color is,
using standard html syntax</li>
<li>font style tags&#8212;b,i,u and s tags&#8212;bold, italic, underline, strikethrough</li>
<li>A limited subset of SGML entities: &amp;quot;, &amp;lt;, &amp;amp;, &amp;lsquo;,
&amp;rsquo;, &amp;ldquo;, &amp;rdquo;, &amp;ndash;,
&amp;mdash;, &amp;ne;, &amp;le;, &amp;ge;,
&amp;copy;, &amp;reg;, &amp;trade;, and &amp;nbsp;
</li>
</ul>
<p>Since
there is no terribly exciting data available from your <code>APIObject</code>, which
only has an integer and a creation date, you'll extend this artificial example,
and decide that odd numbered <code>APIObjects</code> should appear with blue text.</p>
<ol>
<li>Add the following method to <code>MyNode</code>:
<pre class="examplecode">
@Override
public String getHtmlDisplayName() {
APIObject obj = getLookup().lookup (APIObject.class);
if (obj!=null && obj.getIndex() % 2 != 0) {
return "&lt;font color='0000FF'&gt;APIObject " + obj.getIndex() + "&lt;/font&gt;";
} else {
return null;
}
}</pre></li>
<li><p>What the above code accomplishes is this: When painting, the Explorer component showing
the nodes calls <code>getHtmlDisplayName()</code> first. If it gets a non-null
value back, then it will use the HTML string it received and a fast, lightweight HTML
renderer to render it. If it is null, then it will fall back to whatever is
returned by <code>getDisplayName()</code>. So this way, any <code>MyNode</code>
whose <code>APIObject</code> has an index not divisible by 2 will have a non-null
HTML display name.</p>
<p>Run the suite again and you should see the following:</p>
<p>
<img alt="" src="../../images/tutorials/nodes-2/technicolor-nodes-60.png"/></p>
</li>
</ol>
<p>There are two reasons for <code>getDisplayName()</code> and
<code>getHtmlDisplayName()</code> being
separate methods: First, it is an optimization; second, as you will see later,
it makes it possible to compose HTML strings together, without needing to strip
&lt;html&gt; marker tags.</p>
<p>You can enhance this further&#8212;in the previous tutorial, the date was included
in the HTML string, and you have removed it here. So let's make your HTML string
a little more complex, and provide HTML display names for all of your nodes.</p>
<ol>
<li>Modify the <code>getHtmlDisplayName()</code> method as follows:
<pre class="examplecode">
@Override
public String getHtmlDisplayName() {
APIObject obj = getLookup().lookup (APIObject.class);
if (obj != null) {
return "&lt;font color='#0000FF'&gt;APIObject " + obj.getIndex() + "&lt;/font&gt;" +
"&lt;font color='AAAAAA'&gt;&lt;i&gt;" + obj.getDate() + "&lt;/i&gt;&lt;/font&gt;";
} else {
return null;
}
}</pre>
</li>
<li><p>Run the suite again and now you should see the following:</p>
<p><img alt="" src="../../images/tutorials/nodes-2/technicolor-nodes-2-60.png"/></p></li>
</ol>
<p>One minor thing you can do to improve appearance here: You are currently using
hard-coded colors in your HTML. Yet NetBeans can run under various look and
feels, and there's no guarantee that your hard-coded color will not be the same as
or very close to the background color of the tree or other UI component your Node appears in.</p>
<p>The NetBeans HTML renderer provides a minor extension to the HTML spec
which makes it possible to look up colors by passing UIManager keys.
The look and feel Swing is using provides a UIManager,
which manages a name-value map of the colors and fonts a given
look and feel uses. Most (but not all) look and feels find the colors to
use for different GUI elements by calling <code>UIManager.getColor(String)</code>,
where the string key is some agreed-upon value. So by using values from
UIManager, you can guarantee that
you will always be producing readable text. The two keys you will use are
&quot;textText&quot;, which returns the default color for text (usually black
unless using a look and feel with a dark-background theme), and
&quot;controlShadow&quot; which should give us a color that contrasts, but not
too much, with the default control background color.</p>
<ol>
<li>Modify the <code>getHtmlDisplayName()</code> method as follows:
<pre class="examplecode">
@Override
public String getHtmlDisplayName() {
APIObject obj = getLookup().lookup (APIObject.class);
if (obj != null) {
return "&lt;font color='!textText'&gt;APIObject " + obj.getIndex() + "&lt;/font&gt;" +
"&lt;font color='!controlShadow'&gt;&lt;i&gt;" + obj.getDate() + "&lt;/i&gt;&lt;/font&gt;";
} else {
return null;
}
}</pre>
</li>
<li><p>Run the suite again and now you should see the following:</p>
<p><img alt="" src="../../images/tutorials/nodes-2/technicolor-nodes-3-60.png"/></p></li>
</ol>
<p>You'll note above that you got rid of your blue color and switched to plain old
black. Using the value of <code>UIManager.getColor("textText")</code> guarantees us
text that will always be readable under any look and feel, which is valuable;
also, color should be used sparingly in user interfaces, to avoid the
<a href="http://www.catb.org/jargon/html/A/angry-fruit-salad.html">angry fruit salad</a>
effect. If you really want to use wilder colors in your UI, the best bet is to
either find a UIManager key/value pair that consistently gets what you want, or
create a <a href="http://wiki.netbeans.org/wiki/view/DevFaqModulesGeneral">ModuleInstall</a>
class and
<a target="other" href="http://core.netbeans.org/source/browse/*checkout*/core/swing/plaf/src/org/netbeans/swing/plaf/util/RelativeColor.java">
<i>derive the color</i></a> <i>from a color you can get from UIManager</i>, or if
you are sure you know the color theme of the look and feel, hard-code it on a
per-look and feel basis (<code>if ("aqua".equals(UIManager.getLookAndFeel().getID())...</code>).</p>
<h2><a name="icons"></a>Providing Icons</h2>
<p>Icons, used judiciously, also enhance user interfaces. So providing 16x16 pixel
icon is another way to improve the appearance of your UI. One caveat of using
icons is, do not attempt to convey too much information via an icon&#8212;there are
not a lot of pixels there to work with. A second caveat that applies to both
icons and display names is, <i>never use only color to distinguish a node</i>&#8212;
there are many people in the world who are colorblind.</p>
<p>Providing an icon is quite simple&#8212;you just load an image and set it. You
will need to have a GIF or PNG file to use. If you do not have one easily
available, here is one you can use:</p>
<p><img src="../../images/tutorials/nodes-2/icon.png" /></p>
<ol>
<li>Copy the image linked above, or another 16x16 PNG or GIF, into the same package as
the <code>MyEditor</code> class.
</li>
<li>
Add the following method to the <code>MyNode</code> class:
<pre class="examplecode">
@Override
public Image getIcon (int type) {
return ImageUtilities.loadImage ("org/myorg/myeditor/icon.png");
}</pre>
Note that it is possible to have different icon sizes and styles&#8212;the possible
int values passed to <code>getIcon()</code> are constants on <code>java.beans.BeanInfo</code>,
such as <code>BeanInfo.ICON_COLOR_16x16</code>. Also, while you can use the
standard JDK <code>ImageIO.read()</code> to load your images, <code>ImageUtilities.loadImage()</code>
is more optimized, has better caching behavior, and supports branding of images.
</li>
<li><p>If you run the code now, you will notice one thing&#8212;the icon is used for
some nodes but not others! The reason for this is that it is common to use a
different icon for an unexpanded versus an expanded <code>Node</code>. All you
need to do to fix this is to override another method.</p>
<p>Add the following additional method to the <code>MyNode</code>:</p>
<pre class="examplecode">
@Override
public Image getOpenedIcon(int i) {
return getIcon (i);
}</pre>
</li>
<li><p>Now if you run the suite, all of the Nodes will have the correct icon, as shown below:
</p>
<img alt="" src="../../images/tutorials/nodes-2/icon-nodes.png"/></li>
</ol>
<h2><a name="actions"></a>Actions and Nodes</h2>
<p>The next aspect of <code>Node</code>s you will treat is <i>Actions</i>. A
<code>Node</code> has a popup menu which can contain actions that the user
can invoke against that <code>Node</code>. Any subclass of <code>javax.swing.Action</code> can
be provided by a <code>Node</code>, and will show up in its popup menu. Additionally, there
is the concept of <i>presenters</i>, which you will cover later.</p>
<p>
First, let's create a simple action for your nodes to provide:</p>
<ol>
<li>Override the <code>getActions()</code> method of <code>MyNode</code> as
follows:
<pre class="examplecode">
@Override
public Action[] getActions (boolean popup) {
return new Action[] { new MyAction() };
}</pre>
</li>
<li>Now, create the <code>MyAction</code> class as an inner class of <code>MyNode</code>:
<pre class="examplecode">
private class MyAction extends AbstractAction
public MyAction () {
putValue (NAME, &quot;Do Something&quot;);
}
@Override
public void actionPerformed(ActionEvent e) {
APIObject obj = getLookup().lookup (APIObject.class);
JOptionPane.showMessageDialog(null, &quot;Hello from &quot; + obj);
}
} </pre> </li>
<li><p>Run the suite again and notice that when you right-click
on a node, a menu item is shown:</p>
<p><img alt="" src="../../images/tutorials/nodes-2/action-no-presenter-60.png"/></p>
<p>When you select the menu item, the action is invoked:</p>
<p><img alt="" src="../../images/tutorials/nodes-2/optionpane-60.png"/></p></li>
</ol>
<h2><a name="action-presenters"></a>Presenters</h2>
<p>Of course, sometimes you will want to provide a submenu or checkbox menu item or
some other component, other than a JMenuItem, to display in the popup menu. This
is quite easy:</p>
<ol>
<li>Add to the signature of <code>MyAction</code> that it implements <code>Presenter.Popup</code>:
<pre class="examplecode">private class MyAction extends AbstractAction implements Presenter.Popup {</pre>
</li>
<li>Press Ctrl-Shift-I to fix imports.</li>
<li>Position the caret in the class signature line of <code>MyAction</code> and
press Alt-Enter when the lightbulb glyph appears in the margin, and accept
the hint &quot;Implement All Abstract Methods&quot;.</li>
<li>Implement the newly created method <code>getPopupPresenter()</code> as follows:
<pre class="examplecode">
@Override
public JMenuItem getPopupPresenter() {
JMenu result = new JMenu("Submenu"); //remember JMenu is a subclass of JMenuItem
result.add (new JMenuItem(this));
result.add (new JMenuItem(this));
return result;
}</pre></li>
<li><p>Run the suite again and notice that you now have the following:</p>
<img alt="" src="../../images/tutorials/nodes-2/action-with-presenter-60.png"/>
<p>The result is not too exciting&#8212;you now have a submenu called "Submenu" with two
identical menu items. But again, you should get the idea of what is possible
here&#8212;if you want to return a <code>JCheckBoxMenuItem</code> or some other kind
of menu item, it is possible to do that.</p>
</li>
</ol>
<blockquote>
<p><font color="red"><b>Caveat:</b> You can also use Presenter.Menu to provide a
different component to display for any action in the main menu, <i>but</i> certain
versions of Mac OS-X for Macintosh do not play nicely at all with random Swing
components being embedded in menu items. To be safe, do not use anything but
JMenu, JMenuItem and subclasses thereof in the main menu.</font></p>
</blockquote>
<h2><a name="propertysheet"></a>Properties and the Property Sheet</h2>
<p>The last subject you'll cover in this tutorial is properties. You are probably aware that NetBeans
IDE contains a &quot;property sheet&quot; which can display the
&quot;properties&quot; of a <code>Node</code>. What exactly &quot;properties&quot; means
depends on how the <code>Node</code> is implemented. Properties are essentially
name-value pairs which have a Java type, which are grouped in sets and shown in
the property sheet&#8212;where writable properties can be edited via their <i>property editors</i>
(see <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/beans/PropertyEditor.html"><code>java.beans.PropertyEditor</code></a>
for general information about property editors).</p>
<p>So, built into <code>Node</code>s from the ground up is the idea that a Node may
have properties that can be viewed and, optionally, edited on a property sheet.
Adding support for this is quite easy. There is a convenience class in the
Nodes API, <code>Sheet</code>, which represents the entire set of properties for
a Node. To it you may add instances of <code>Sheet.Set</code>, which represent
&quot;property sets&quot;, which appear in the property sheet as groups of
properties.</p>
<ol>
<li>Override <code>MyNode.createSheet()</code> as follows:
<pre class="examplecode">@Override
protected Sheet createSheet() {
Sheet sheet = Sheet.createDefault();
Sheet.Set set = Sheet.createPropertiesSet();
APIObject obj = getLookup().lookup(APIObject.class);
try {
Property indexProp = new PropertySupport.Reflection(obj, Integer.class, "getIndex", null);
Property dateProp = new PropertySupport.Reflection(obj, Date.class, "getDate", null);
indexProp.setName("index");
dateProp.setName("date");
set.put(indexProp);
set.put(dateProp);
} catch (NoSuchMethodException ex) {
ErrorManager.getDefault();
}
sheet.put(set);
return sheet;
}</pre>
</li>
<li>Press Ctrl-Shift-I to Fix Imports.</li>
<li>Right click the module suite and choose Run to launch a copy of NetBeans
with the suite's modules installed.</li>
<li>Use File &gt; Open Editor to show your editor.</li>
<li>Select Window &gt; Properties to show the NetBeans property sheet.</li>
<li>Click in your editor window and move the selection between different
nodes, and notice the property sheet updating, just as your <code>MyViewer</code>
component does, as shown below:
<img alt="" src="../../images/tutorials/nodes-2/property-sheet-60.png"/>
</li>
</ol>
<p>The above code makes use of a very convenient class: <code>PropertySupport.Reflection</code>,
which may simply be passed an object, a type, and getter and setter method names, and
it will create a Property object that can read (and optionally write) that property
of the object in question. So you use <code>PropertySupport.Reflection</code> a
simple way to wire one <code>Property</code>
object up to the <code>getIndex()</code> method of <code>APIObject</code>.</p>
<p>If you want <code>Property</code> objects for nearly all of the getters/setters
on an underlying model object, you may want to use or subclass <code>BeanNode</code>,
which is a full implementation of <code>Node</code> that can be given a random object
and will try to create all the necessary properties for it (and listen for changes)
via reflection (how exactly they are presented can be controlled by creating a
<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/beans/BeanInfo.html"><code>BeanInfo</code></a>
for the class of the object to be represented by the node).</p>
<blockquote>
<p><font color="red"><b>Caveat:</b> Setting the <code>name</code> of your properties is
very important. Property objects test their equality based on names. If you
are adding some properties to a <code>Sheet.Set</code> and they seem to be
disappearing, very probably their name is not set&#8212;so putting one property
in a <code>HashSet</code> with the same (empty) name as another is causing
later added ones to displace earlier added ones.</font></p>
</blockquote>
<h2><a name="read-write-properties"></a>Read-Write Properties</h2>
<p>To play with this concept further, what you really need is a read/write property.
So the next step is to add some additional support to <code>APIObject</code> to
make the <code>Date</code> property settable.</p>
<ol>
<li>Open <code>org.myorg.myapi.APIObject</code> in the code editor.</li>
<li>Remove the <code>final</code> keyword from the line declaring the <code>date</code> field</li>
<li>Add the following setter and property change support methods to <code>APIObject</code>:
<pre class="examplecode">
private List listeners = Collections.synchronizedList(new LinkedList());
public void addPropertyChangeListener (PropertyChangeListener pcl) {
listeners.add (pcl);
}
public void removePropertyChangeListener (PropertyChangeListener pcl) {
listeners.remove (pcl);
}
private void fire (String propertyName, Object old, Object nue) {
//Passing 0 below on purpose, so you only synchronize for one atomic call:
PropertyChangeListener[] pcls = (PropertyChangeListener[]) listeners.toArray(new PropertyChangeListener[0]);
for (int i = 0; i < pcls.length; i++) {
pcls[i].propertyChange(new PropertyChangeEvent (this, propertyName, old, nue));
}
}</pre>
</li>
<li>Now, within the <tt>APIObject</tt>, call the <tt>fire</tt> method above:
<pre>public void setDate(Date d) {
Date oldDate = date;
date = d;
fire("date", oldDate, date);
}</pre> </li>
<li>In <code>MyNode.createSheet()</code>, change the way <code>dateProp</code> is
declared, so that it will be writable as well as readable:
<pre class="examplecode">
Property dateProp = new PropertySupport.Reflection(obj, Date.class, &quot;date&quot;);</pre>
Now, rather than specifying explicit getters and setters, you are just providing
the property name, and <code>PropertySupport.Reflection</code> will find the
getter and setter methods for us (and in fact it will also find the
<code>addPropertyChangeListener()</code> method automatically).
</li>
<li><p>Re-run the module suite, and notice that you can now select an instance of
<code>MyNode</code> in <code>MyEditor</code> and actually edit the date value, as shown
below:</p>
<p><img alt="" src="../../images/tutorials/nodes-2/date-readwrite-60.png"/></p>
<p><b>Note:</b> The result is persisted when you restart the IDE.</p></li>
</ol>
<p>However, there is still one bug in this code: When you change the Date property,
you should also update the display name of your node. So you will make one more
change to <code>MyNode</code> and have it listen for property changes on
<code>APIObject</code>.</p>
<ol>
<li>Modify the signature of <code>MyNode</code> so that it implements
<code>java.beans.PropertyChangeListener</code>:
<pre class="examplecode">
public class MyNode extends AbstractNode implements PropertyChangeListener {</pre>
</li>
<li>Press Ctrl-Shift-I to Fix Imports.</li>
<li>Placing the caret in the signature line, accept the hint &quot;Implement
All Abstract Methods&quot;.</li>
<li>Add the following line to the constructor which takes an argument of
<code>APIObject</code>:
<pre class="examplecode">obj.addPropertyChangeListener(WeakListeners.propertyChange(this, obj));</pre>
Note that here you are using a utility method on <code>org.openide.util.WeakListeners</code>.
This is a technique for avoiding memory leaks&#8212;an <code>APIObject</code> will only
weakly reference its <code>MyNode</code>, so if the <code>Node</code>'s parent
is collapsed, the <code>Node</code> can be garbage collected. If the <code>Node</code>
were still referenced in the list of listeners owned by <code>APIObject</code>,
it would be a memory leak.
In your case, the <code>Node</code> actually owns the <code>APIObject</code>,
so this is not a terrible situation&#8212;but in real world programming, objects in
a data model (such as files on disk) may be much longer-lived than <code>Node</code>s
displayed to the user. Whenever you add a listener to an object which you never
explicitly remove, it is preferable to use <code>WeakListeners</code>&#8212;otherwise
you may create memory leaks which will be quite a headache later. If you instantiate
a separate listener class, though, be sure to keep a strong reference to it from
the code that attaches it&#8212;otherwise it will be garbage collected almost as soon
as it is added.
</li>
<li>Finally, implement the <code>propertyChange()</code> method:
<pre class="examplecode">
public void propertyChange(PropertyChangeEvent evt) {
if ("date".equals(evt.getPropertyName())) {
this.fireDisplayNameChange(null, getDisplayName());
}
}</pre>
</li>
<li><p>Run the module suite again, select a <code>MyNode</code> in the
<code>MyEditor</code> window and change its <code>Date</code> property&#8212;notice
that the display name of the <code>Node</code> is now updated
correctly, as shown below, where the year 2009 and is now reflected both
on the node and in the property sheet:</p>
<img alt="" src="../../images/tutorials/nodes-2/changed-date-nodes-60.png"/>
</li>
</ol>
<h2><a name="separate-property-groups"></a>Grouping Property Sets</h2>
<p>You may have noticed when running Matisse, NetBeans IDE's
form editor, that there is a set
of buttons at the top of the property sheet, for switching between groups of
property sets.</p>
<p>Generally this is only advisable if you have a really large number of
properties, and generally it's not advisable for ease-of-use <i>to</i> have a
really large number of properties. Nonetheless, if you feel you need to split
out your sets of properties into groups, this is easy to accomplish.</p>
<p><code>Property</code> has the methods <code>getValue()</code> and
<code>setValue()</code>, as does <code>PropertySet</code> (both of them
inherit this from
<a href="http://java.sun.com/j2se/1.4.2/docs/api/java/beans/FeatureDescriptor.html"><code>java.beans.FeatureDescriptor</code></a>).
These methods can be used in certain cases, for passing ad-hoc &quot;hints&quot;
between a given <code>Property</code> or <code>PropertySet</code>
and the property sheet or certain kinds of property
editor (for example, passing a default filechooser directory to an editor for
<code>java.io.File</code>).
And that is the technique by which you can specify a group name (to be
displayed on a button) for one or more <code>PropertySet</code>s. In real world
coding, this should be a localized string, not a hard-coded string as below:</p>
<ol>
<li>Open <code>MyNode</code> in the code editor</li>
<li>Modify the method <code>createSheet()</code> as follows (modified and
added lines are highlighted):
<pre class="examplecode">
@Override
protected Sheet createSheet() {
Sheet sheet = Sheet.createDefault();
Sheet.Set set = sheet.createPropertiesSet();
<b>Sheet.Set set2 = sheet.createPropertiesSet();
set2.setDisplayName("Other");
set2.setName("other");</b>
APIObject obj = getLookup().lookup (APIObject.class);
try {
Property indexProp = new PropertySupport.Reflection(obj, Integer.class, "getIndex", null);
Property dateProp = new PropertySupport.Reflection(obj, Date.class, "date");
indexProp.setName("index");
dateProp.setName ("date");
set.put (indexProp);
<b>set2.put (dateProp);
set2.setValue("tabName", "Other Tab");</b>
} catch (NoSuchMethodException ex) {
ErrorManager.getDefault();
}
sheet.put(set);
<b>sheet.put(set2);</b>
return sheet;
}</pre>
</li>
<li><p>Run the suite again, and notice that there are now buttons at the top
of the property sheet, and there is one property under each, as seen here:</p>
<p>
<img alt="" src="../../images/tutorials/nodes-2/other-tab-60.png"/></p>
</li>
</ol>
<h2><a name="caveats"></a>General Property Sheet Caveats</h2>
<p>If you used NetBeans 3.6 or earlier, you may
notice that older versions of NetBeans employed the property sheet very heavily
as a core element of the UI, whereas it's not so prevalent today. The reason is
simple: <i>property sheet based UIs are not terribly user-friendly</i>. That
doesn't mean don't use the property sheet, but use it judiciously. If you have
the option of providing a customizer with a nice GUI, do so&#8212;your users will
thank you. </p>
<p>And if you have an enormous number of properties on one object, try
to find some overall settings that encapsulate the most probable combinations of
settings. For example, think of what the settings for a tool for managing imports on a Java
class can be&#8212;you can provide integers for setting the threshold number of usages of a package
required for wildcard imports, the threshold number of uses of a fully qualified
class name required before importing it at all, and lots of other numbers ad
nauseum. Or you can ask yourself the question, <i>what is the user trying to
do?</i>. In this case, it's either going to be getting rid of import statements
or getting rid of fully qualified names. So probably settings
of <i>low noise, medium noise</i> and <i>high noise</i> where &quot;noise&quot;
refers to the amount of fully qualified class/package names in the
edited source file would do just as well and be much
easier to use. Where you can make life simpler for the user, do so.</p>
<h2><a name="review"></a>Review of Concepts</h2>
This tutorial has sought to get across the following ideas:
<ul>
<li>Nodes are a presentation layer</li>
<li>The display names of Nodes can be customized using a limited subset of HTML</li>
<li>Nodes have icons, and you can provide custom icons for nodes you create</li>
<li>Nodes have Actions; an Action which implements Presenter.Popup can provide
its own component to display in a popup menu; the same is true for main
menu items using Presenter.Menu, and toolbar items using Presenter.Toolbar</li>
<li>Nodes have properties, which can be displayed on the property sheet</li>
</ul>
<div class="feedback-box"><a href="https://netbeans.org/about/contact_form.html?to=3&amp;subject=Feedback:%20Nodes%20API%20Module%20Tutoria">Send Us Your Feedback</a></div>
<h2><a name="next"></a>Next Steps</h2>
<p>You've now begun to delve into how to get more out of the property sheet in
NetBeans. In the <a href="nbm-property-editors.html">next tutorial</a>, you will
cover how to write custom editors and provide a custom inline editor for use
in the property sheet.</p>
</body>
</html>