| <!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>Nodes API Tutorial for the NetBeans Platform</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, 2011, 2012, 2013, 2014 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><strong class="notes">Note: </strong>This document uses NetBeans Platform 8.0 and |
| NetBeans IDE 8.0. If you |
| are using an earlier version, see <a href="74/nbm-nodesapi2.html">the previous version |
| of this document</a>.</p> |
| |
| <p><b>Contents</b></p> |
| <p><img src="../images/articles/81/netbeans-stamp.png" class="stamp" width="114" height="114" alt="Content on this page applies to NetBeans IDE 8.0" title="Content on this page applies to NetBeans IDE 8.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 8.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 7 or above</td> |
| </tr> |
| </tbody> |
| </table> |
| |
| <p class="tips">For troubleshooting purposes, you are welcome to download the <a href="http://java.net/projects/nb-api-samples/sources/api-samples/show/versions/8.0/tutorials/selection-management/3-of-4/EventManager">completed tutorial source code</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—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>—a human readable, user-friendly display name</li> |
| <li><b>Description</b>—a human readable, user-friendly description, often shown as a tooltip</li> |
| <li><b>Icon</b>—some glyph that graphically indicates the type of object shown and possibly |
| its state</li> |
| <li><b>Actions</b>—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>EventChildFactory</code> class |
| to create <code>Node</code>s, by calling</p> |
| <pre class="examplecode"> |
| new AbstractNode(Children.create(new EventChildFactory(), 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, as instructed below.</p> |
| |
| <div class="indent"> |
| |
| <ol> |
| <li>In the My Editor project, right click the package <code>org.myorg.myeditor</code> |
| and choose New > Java Class. Name the class "EventNode" and press Enter |
| or click Finish.</li> |
| <li>Change the signature and constructors of the class as follows: |
| <pre class="examplecode">package org.myorg.myeditor; |
| |
| import org.myorg.myapi.Event; |
| import org.openide.nodes.AbstractNode; |
| import org.openide.nodes.Children; |
| import org.openide.util.lookup.Lookups; |
| |
| public class EventNode extends AbstractNode { |
| |
| public EventNode(Event obj) { |
| super (Children.create(new EventChildFactory(), true), Lookups.singleton(obj)); |
| setDisplayName ("Event " + obj.getIndex()); |
| } |
| |
| public EventNode() { |
| super (Children.create(new EventChildFactory(), true)); |
| setDisplayName ("Root"); |
| } |
| |
| }</pre> |
| </li> |
| <li>Open <code>MyEditor</code> from the same package, in the code editor. |
| Remove these lines in the constructor: |
| <pre class="examplecode"> |
| mgr.setRootContext(new AbstractNode(new EventChildFactory())); |
| setDisplayName ("My Editor");</pre> |
| Instead of the above, add this single line of code: |
| <pre class="examplecode"> |
| mgr.setRootContext(new EventNode());</pre> |
| </li> |
| <li>Now make a similar change to the <tt>EventChildFactory</tt> class. Open |
| it in the editor, and change its |
| <code>createNodeForKey</code> method as follows: |
| <pre class="examplecode"> |
| @Override |
| protected Node createNodeForKey(Event key) { |
| return new EventNode(key); |
| }</pre> |
| </li> |
| |
| </ol> |
| |
| </div> |
| |
| <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 <tt>Node</tt> subclass instead of just using <tt>AbstractNode</tt>.</p> |
| |
| <h2><a name="displayname-html"></a>Enhancing Display Names with HTML</h2> |
| |
| <p> |
| The first enhancement you will provide is an enhanced display name. The Nodes API |
| supports 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—font size and face settings are not supported, but color is, |
| using standard html syntax</li> |
| <li>font style tags—b,i,u and s tags—bold, italic, underline, strikethrough</li> |
| <li>A limited subset of SGML entities: &quot;, &lt;, &amp;, &lsquo;, |
| &rsquo;, &ldquo;, &rdquo;, &ndash;, |
| &mdash;, &ne;, &le;, &ge;, |
| &copy;, &reg;, &trade;, and &nbsp; |
| </li> |
| </ul> |
| |
| <p>Since |
| there is no terribly exciting data available from your <code>Event</code>, which |
| only has an integer and a creation date, you'll extend this artificial example, |
| and decide that odd numbered <code>Events</code> should appear with blue text.</p> |
| |
| <div class="indent"> |
| |
| <ol> |
| <li>Add the following method to <code>EventNode</code>: |
| <pre class="examplecode"> |
| @Override |
| public String getHtmlDisplayName() { |
| Event obj = getLookup().lookup (Event.class); |
| if (obj!=null && obj.getIndex() % 2 != 0) { |
| return "<font color='0000FF'>Event " + obj.getIndex() + "</font>"; |
| } 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>EventNode</code> |
| whose <code>Event</code> has an index not divisible by 2 will have a non-null |
| HTML display name. Run the Event Manager again and you should see the following:</p> |
| |
| <p> |
| <img alt="" src="../images/tutorials/nodes-2/73/html-display-1.png"/></p> |
| |
| </li> |
| |
| </ol> |
| |
| </div> |
| |
| <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 |
| <html> marker tags.</p> |
| |
| <p>You can enhance this further—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> |
| |
| <div class="indent"> |
| |
| <ol> |
| <li>Modify the <code>getHtmlDisplayName()</code> method as follows: |
| <pre class="examplecode"> |
| @Override |
| public String getHtmlDisplayName() { |
| Event obj = getLookup().lookup (Event.class); |
| <b>if (obj != null) { |
| return "<font color='#0000FF'>Event " + obj.getIndex() + "</font>" + |
| " <font color='AAAAAA'><i>" + obj.getDate() + "</i></font>"; |
| }</b> else { |
| return null; |
| } |
| }</pre> |
| </li> |
| <li><p>Run the Event Manager again and now you should see the following:</p> |
| |
| <p><img alt="" src="../images/tutorials/nodes-2/73/html-display-2.png"/></p> |
| </ol> |
| |
| </div> |
| |
| <p>One minor thing you can do to improve appearance here—you are currently using |
| hard-coded colors in your HTML. Yet the NetBeans Platform 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 |
| "textText", which returns the default color for text (usually black |
| unless using a look and feel with a dark-background theme), and |
| "controlShadow" which should give us a color that contrasts, but not |
| too much, with the default control background color.</p> |
| |
| <div class="indent"> |
| |
| <ol> |
| <li>Modify the <code>getHtmlDisplayName()</code> method as follows: |
| <pre class="examplecode"> |
| @Override |
| public String getHtmlDisplayName() { |
| Event obj = getLookup().lookup (Event.class); |
| if (obj != null) { |
| return "<font color='!textText'>Event " + obj.getIndex() + "</font>" + |
| " <font color='!controlShadow'><i>" + obj.getDate() + "</i></font>"; |
| } else { |
| return null; |
| } |
| }</pre> |
| </li> |
| <li><p>Run the Event Manager again and now you should see the following:</p> |
| |
| <p><img alt="" src="../images/tutorials/nodes-2/73/html-display-3.png"/></p> |
| |
| </ol> |
| |
| </div> |
| |
| <p class="notes"><b>Note:</b> 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—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>— |
| there are many people in the world who are colorblind.</p> |
| |
| <p>Providing an icon is quite simple—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> |
| |
| <div class="indent"> |
| |
| <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>EventNode</code> class: |
| <pre class="examplecode"> |
| @Override |
| public Image getIcon (int type) { |
| return ImageUtilities.loadImage ("org/myorg/myeditor/icon.png"); |
| }</pre> |
| <p class="notes"><b>Note:</b> It is possible to have different icon sizes and styles—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.</p> |
| </li> |
| <li><p>If you run the code now, you will notice one thing—the icon is used for |
| some nodes but not others!</p> |
| <p><img alt="" src="../images/tutorials/nodes-2/73/icon-display-1.png"/></p> |
| <p>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. Add the following additional method to the <code>EventNode</code>:</p> |
| <pre class="examplecode"> |
| @Override |
| public Image getOpenedIcon(int i) { |
| return getIcon (i); |
| }</pre> |
| |
| </p> |
| <p>Now if you run the Event Manager, all of the Nodes will have the correct icon, as shown below: |
| </p> |
| <p><img alt="" src="../images/tutorials/nodes-2/73/icon-display-2.png"/></p></li> |
| |
| </ol> |
| |
| </div> |
| |
| <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> |
| |
| <div class="indent"> |
| |
| <ol> |
| <li>Override the <code>getActions()</code> method of <code>EventNode</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>EventNode</code>: |
| <pre class="examplecode"> |
| private class MyAction extends AbstractAction { |
| |
| public MyAction () { |
| putValue (NAME, "Do Something"); |
| } |
| |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| Event obj = getLookup().lookup(Event.class); |
| JOptionPane.showMessageDialog(null, "Hello from " + obj); |
| } |
| |
| } </pre> </li> |
| |
| <li><p>Run the Event Manager 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/73/action-display-1.png"/></p></li> |
| <p>When you select the menu item, the action is invoked:</p> |
| <p><img alt="" src="../images/tutorials/nodes-2/73/action-display-2.png"/></p></li> |
| </ol> |
| |
| </div> |
| |
| <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> |
| |
| <div class="indent"> |
| |
| <ol> |
| <li><p>Add to the signature of <code>MyAction</code> that it implements <code>Presenter.Popup</code>:</p> |
| <pre class="examplecode">private class MyAction extends AbstractAction <b>implements Presenter.Popup</b> {</pre> |
| |
| <p>Press Ctrl-Shift-I to fix imports.</p></li> |
| |
| <li><p>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 "Implement All Abstract Methods". |
| Implement the newly created method <code>getPopupPresenter()</code> as follows:</p> |
| <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 Event Manager again and notice that you now have the following:</p> |
| <p> <img alt="" src="../images/tutorials/nodes-2/73/action-display-3.png"/></p> |
| </li> |
| |
| </ol> |
| |
| </div> |
| |
| <p>The result is not too exciting—you now have a submenu called "Submenu" with two |
| identical menu items. But again, you should get the idea of what is possible |
| here—if you want to return a <code>JCheckBoxMenuItem</code> or some other kind |
| of menu item, it is possible to do that.</p> |
| |
| <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 "property sheet" which can display the |
| "properties" of a <code>Node</code>. What exactly "properties" 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—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 |
| "property sets", which appear in the property sheet as groups of |
| properties.</p> |
| |
| <div class="indent"> |
| |
| <ol> |
| <li>Override <code>EventNode.createSheet()</code> as follows: |
| |
| <pre class="examplecode">@Override |
| protected Sheet createSheet() { |
| |
| Sheet sheet = Sheet.createDefault(); |
| Sheet.Set set = Sheet.createPropertiesSet(); |
| Event obj = getLookup().lookup(Event.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> |
| |
| <p>Press Ctrl-Shift-I to Fix Imports.</p></li> |
| <li>Right click the EventManager and choose Run and then, once it is started up, |
| select Window > IDE Tools > Properties to show the NetBeans Platform Properties window.</li> |
| <li><p>Move the selection between different |
| nodes, and notice the property sheet updating, just as your <code>MyViewer</code> |
| component does, as shown below:</p> |
| |
| <p><img alt="" src="../images/tutorials/nodes-2/73/prop-display-1.png"/></p> |
| |
| </li> |
| |
| </ol> |
| |
| </div> |
| |
| <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>Event</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—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>Event</code> to |
| make the <code>Date</code> property settable.</p> |
| |
| <div class="indent"> |
| |
| <ol> |
| <li>Open <code>org.myorg.myapi.Event</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>Event</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>Event</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>EventNode.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, "date");</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 Event Manager, and notice that you can now select an instance of |
| <code>EventNode</code> in <code>MyEditor</code> and actually edit the date value, as shown |
| below:</p> |
| <p> <img alt="" src="../images/tutorials/nodes-2/73/prop-display-2.png"/> |
| </p> |
| |
| <p class="notes"><b>Note:</b> The result is persisted when you restart the IDE.</p></li> |
| |
| </ol> |
| |
| </div> |
| |
| <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>EventNode</code> and have it listen for property changes on |
| <code>Event</code>.</p> |
| |
| <div class="indent"> |
| |
| <ol> |
| <li>Modify the signature of <code>EventNode</code> so that it implements |
| <code>java.beans.PropertyChangeListener</code>: |
| <pre class="examplecode"> |
| public class EventNode extends AbstractNode <b>implements PropertyChangeListener</b> {</pre> |
| <p>Press Ctrl-Shift-I to Fix Imports.</p></li> |
| <li>Placing the caret in the signature line, accept the hint "Implement |
| All Abstract Methods".</li> |
| <li>Add the following line to the constructor which takes an argument of |
| <code>Event</code>: |
| <pre class="examplecode">obj.addPropertyChangeListener(WeakListeners.propertyChange(this, obj));</pre> |
| <p class="notes"><b>Note:</b> Here you are using a utility method on <code>org.openide.util.WeakListeners</code>. |
| This is a technique for avoiding memory leaks—an <code>Event</code> will only |
| weakly reference its <code>EventNode</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>Event</code>, |
| it would be a memory leak. |
| In your case, the <code>Node</code> actually owns the <code>Event</code>, |
| so this is not a terrible situation—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>—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—otherwise it will be garbage collected almost as soon |
| as it is added.</p> |
| </li> |
| <li>Finally, implement the <code>propertyChange()</code> method: |
| <pre class="examplecode">@Override |
| public void propertyChange(PropertyChangeEvent evt) { |
| if ("date".equals(evt.getPropertyName())) { |
| this.fireDisplayNameChange(null, getDisplayName()); |
| } |
| }</pre> |
| </li> |
| <li><p>Run the module Event Manager again, select a <code>EventNode</code> in the |
| <code>MyEditor</code> window and change its <code>Date</code> property—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> |
| <p> <img alt="" src="../images/tutorials/nodes-2/73/prop-display-3.png"/> |
| </p> |
| |
| </li> |
| |
| </ol> |
| |
| </div> |
| |
| <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 "hints" |
| 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> |
| |
| <div class="indent"> |
| |
| <ol> |
| <li>Open <code>EventNode</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> |
| Event obj = getLookup().lookup (Event.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 Event Manager 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/73/prop-display-4.png"/> |
| </p> |
| |
| </li> |
| |
| </ol> |
| |
| </div> |
| |
| <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, such as via JavaFX, do so—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—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 "noise" |
| 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: |
| <p></p> |
| <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 <tt>Presenter.Popup</tt> can provide |
| its own component to display in a popup menu; the same is true for main |
| menu items using <tt>Presenter.Menu</tt>, and toolbar items using <tt>Presenter.Toolbar</tt>.</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&subject=Feedback:%20Nodes%20API%208.0%20Module%20Tutorial">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> |