| <!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>Integrated Property Editors in NetBeans</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="Techniques for using property editors in NetBeans."> |
| <style type="text/css"> |
| .tips { |
| margin: 5px; |
| border-style: solid; |
| border-width: 2px; |
| border-color: #CCFFCC; |
| background-color: #F5FFF5; |
| padding-top: 5px; |
| padding-bottom: 5px; |
| padding-left: 35px; |
| padding-right: 25px; |
| } |
| .caveat { |
| margin: 20px; |
| border-style: solid; |
| border-width: 2px; |
| border-color: #CCAAAA; |
| background-color: #EECCCC; |
| padding-top: 5px; |
| padding-bottom: 5px; |
| padding-left: 25px; |
| padding-right: 25px; |
| } |
| .td { |
| border-style: solid; |
| border-width: 1px; |
| border-color: #CCCCCC; |
| font-family: 'Courier New'; |
| background-color: #CCCCCC; |
| font-weight: bold; |
| } |
| </style> |
| <!-- Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. --> |
| <!-- Use is subject to license terms.--> |
| </head> |
| <body> |
| <h1>Integrated Property Editors in NetBeans</h1> |
| <p>The NetBeans IDE includes a visual editor for Swing user interfaces |
| (UIs). Components are controlled using property editors which |
| follow the Java Beans™ specification. This tutorial will show |
| how to distribute components inside a NetBeans plug-in, how to |
| provide custom property editors for individual properties or an |
| entire class of properties of those components; and how to write |
| property editors that are able to call into and use features of |
| the NetBeans IDE. |
| |
| <h2>Why is this so complicated?</h2> |
| <p>The Java Beans specification defines how components should interact |
| with an integrated development environment (IDE), and various classes |
| that both an IDE and a component vendor will use to communicate with |
| the IDE and vice-versa. The fundamental problem with the beans |
| specification is that, when it was created and cast in stone, |
| <i>there were no Java IDEs</i>. The specification tends to be in |
| some cases naively defined (BeanInfo), and in other cases |
| woefully underspecified (PropertyEditor) with respect to what an IDE |
| or a component will actually need to provide a good user experience. |
| </p> |
| <p>It is possible to create generic property editors that will integrate |
| with an IDE without depending on that IDE — and where this makes |
| sense, it is encouraged to do that. However, there are many cases |
| where that is not sufficient. For example, the specification for |
| <code>java.beans.PropertyEditor</code> simply allows a property |
| editor to return a <code>java.awt.Component</code> to provide a |
| custom editor dialog. In practice, that component will be shown |
| to the user in a dialog - and if it is possible for the user to |
| provide invalid input, good UI design dictates that the OK button |
| for that dialog should be disabled when the input is not usable. |
| But there is no communication path for a component to tell an IDE |
| about such a situation. Similarly, a property editor that lets the |
| user supply an image needs to allow the user to choose a file on |
| disk to use. If the image is not on the classpath of the project, |
| it needs to be copied into some directory where it can be found at |
| runtime — but there is no way for an IDE to communicate to a |
| third party property editor anything about files or directories or |
| classpaths. |
| </p> |
| <p>For these cases, NetBeans provides APIs that fill some of the gaps |
| in the beans spec. NetBeans also provides sophisticated |
| user interface controls |
| that can save time and create a better user experience if used. |
| <center> |
| <img src="../../images/tutorials/property-editors/borderCustomEditor.png" alt="Border Custom Editor"/> |
| <br/> |
| <i> |
| NetBeans' custom editor for Borders uses NetBeans' <br/>Nodes and |
| Explorer API to provide the list of available <br/>borders in an |
| embedded property sheet. |
| </i> |
| </center> |
| <h2><a name="step1">Step 1: Creating Some Beans</a></h2> |
| The first step in packaging up some components is creating some |
| components, so we will start by creating a simple Java project. |
| |
| <ol class="instructions"> |
| <li>In NetBeans, select |
| <b>File > New Project</b> and choose <b>Java |
| > Java Class Library</b>. On the second page of the |
| new project wizard, give the project the name |
| <code>Bean</code>. Click Finish.</li> |
| <li>Once the project is open, create a new Java Class called |
| <code>Bean1</code> in a |
| package named <code>bean</code>.</li> |
| <li>Add the following code to it (it will use a class that |
| does not exist yet, so don't worry about warnings for |
| <code>ColorValue</code>):</li> |
| </ol> |
| <h3><a name="listing1">Listing 1: Bean1.java</a></h3> |
| <pre> |
| public class Bean1 extends JComponent { |
| private ColorValue colorValue = new ColorValue (255, 235, 128); |
| |
| public ColorValue getColorValue() { |
| return colorValue; |
| } |
| |
| public void setColorValue(ColorValue val) { |
| ColorValue old = this.colorValue; |
| this.colorValue = val; |
| if ((old == null) != (val == null) || old != null && !old.equals(val)) { |
| firePropertyChange("colorValue", old, val); |
| repaint(); |
| } |
| } |
| |
| @Override |
| public Dimension getPreferredSize() { |
| return new Dimension (24, 24); |
| } |
| |
| @Override |
| public Dimension getMinimumSize() { |
| return getPreferredSize(); |
| } |
| |
| @Override |
| public void paint (Graphics g) { |
| if (colorValue != null) { |
| g.setColor(colorValue.toColor()); |
| g.fillRect(0, 0, getWidth(), getHeight()); |
| } |
| } |
| } |
| </pre> |
| <h2>Creating A Class That Will Need A Custom Editor</h2> |
| We will start with a fairly simple (if slightly artificial) |
| class. We need to create the |
| <code>ColorValue</code> class which is the type of one of |
| <code>Bean1</code>'s properties (later we will handle a much |
| more complex data type requiring a more sophisticated custom |
| editor). |
| <ol class="instructions"> |
| <li>Create a new Java class, <code>bean.ColorValue</code>.</li> |
| <li>Populate it as shown in <a href="#listing2">Listing 2</a>.</li> |
| </ol> |
| <h3><a name="listing2">Listing 2: ColorValue</a></h3> |
| <pre>package bean; |
| import java.awt.Color; |
| import java.beans.PropertyChangeListener; |
| import java.beans.PropertyChangeSupport; |
| import java.io.Serializable; |
| public class ColorValue implements Serializable { |
| private final PropertyChangeSupport supp = new PropertyChangeSupport(this); |
| private int red; |
| private int green; |
| private int blue; |
| |
| public ColorValue() {} |
| |
| public ColorValue(int red, int green, int blue) { |
| if (red < 0 || red > 255) { |
| throw new IllegalArgumentException("" + red); |
| } |
| if (green < 0 || green > 255) { |
| throw new IllegalArgumentException("" + green); |
| } |
| if (blue < 0 || blue > 255) { |
| throw new IllegalArgumentException("" + blue); |
| } |
| this.red = red; |
| this.green = green; |
| this.blue = blue; |
| } |
| |
| public int getBlue() { |
| return blue; |
| } |
| |
| public int getGreen() { |
| return green; |
| } |
| |
| public int getRed() { |
| return red; |
| } |
| |
| public void setGreen(int green) { |
| if (green < 0 || green > 255) { |
| throw new IllegalArgumentException("" + green); |
| } |
| int old = this.green; |
| this.green = green; |
| if (green != old) { |
| supp.firePropertyChange("green", old, green); |
| } |
| } |
| |
| public void setBlue(int blue) { |
| if (blue < 0 || blue > 255) { |
| throw new IllegalArgumentException("" + blue); |
| } |
| int old = blue; |
| this.blue = blue; |
| if (old != blue) { |
| supp.firePropertyChange("blue", old, blue); |
| } |
| } |
| |
| public void setRed(int red) { |
| if (red < 0 || red > 255) { |
| throw new IllegalArgumentException("" + red); |
| } |
| int old = this.red; |
| this.red = red; |
| if (old != red) { |
| supp.firePropertyChange("red", old, red); |
| } |
| } |
| |
| public Color toColor() { |
| return new Color(red, green, blue); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || ColorValue.class != obj.getClass()) { |
| return false; |
| } |
| final ColorValue other = (ColorValue) obj; |
| return red == other.red && green == other.green && blue == other.blue; |
| } |
| |
| @Override |
| public int hashCode() { |
| //evenly distribute 3 byte values across 32 bits |
| return red + (green << 12) + (blue << 24); |
| } |
| |
| public void removePropertyChangeListener(PropertyChangeListener pl) { |
| supp.removePropertyChangeListener(pl); |
| } |
| |
| public void addPropertyChangeListener(PropertyChangeListener pl) { |
| supp.addPropertyChangeListener(pl); |
| } |
| }</pre> |
| <h2><a name="creatingModule">Creating the Plug-In</a></h2> |
| Now we need to create a NetBeans plug-in (module) which will |
| do three things: |
| <ol> |
| <li>Integrate our class library into the IDE, so that it appears |
| in the list of libraries available to users (found in |
| <b>Tools > Libraries</b> in the IDE). While we could ask |
| users to put the JAR for our library on the classpath of |
| every project they use it in, this approach is much more |
| convenient.</li> |
| <li>Add <code>Bean1</code> to the Component Palette, so that |
| users can simply drag the component into their user interfaces. |
| </li> |
| <li>Provide our property editors for our property classes and |
| integrate them into the IDE. |
| </li> |
| </ol> |
| NetBeans comes with built-in support for creating modules, so setting |
| up a new module project is quite simple: |
| <ol class="instructions"> |
| <li>Select <b>File > New Project</b> in the main menu.</li> |
| <li>In the New Project wizard, choose |
| <b>NetBeans Modules> Module</b> on the first page, then click Next.</li> |
| <li>On the second page of the wizard, name the project <code>BeanLibraryModule</code>.</li> |
| <li>On the third page of the wizard, enter <code>org.netbeans.demo.form.beanlib</code> |
| for the <b>Code Name Base</b>, and <code>Bean Library Module</code> for the |
| display name. Check the <b>Generate XML Layer</b> checkbox and click |
| <b>Finish</b>.</li> |
| </ol> |
| |
| <h2><a name="requireRestart">Setting The Module To Require an IDE Restart</a></h2> |
| Modules which install Java libraries — particuarly ones which add components |
| to the component palette should always require a restart of the IDE. There |
| are two reasons for this: |
| <p> |
| <ul> |
| <li><i>MS Windows File Locking</i>—The IDE can reload a module without |
| restarting. However, on the Windows platform, if something is using a |
| JAR file, it will be locked at the operating system level, so updating |
| the module may fail with an exception if the old JAR file cannot be |
| overwritten with the new one.</li> |
| <li><i>Form Editor Reloading</i>—If the user has a form open, which |
| is using a component from the JAR file, the component will not be replaced |
| with one from the new JAR file (this could be very complicated if the file |
| is modified but not saved). For the updated component to |
| be used, we need to be sure both that the form is reloaded, and also that |
| any cached class data from the JAR is discarded.</li> |
| </ul> |
| Causing a module to request that the IDE restart itself before it is |
| installed is as simple as checking a checkbox: |
| <ol> |
| <li>Once the project is created, right click it and choose Properties</li> |
| <li>When the <b>Project Properties</b> dialog appears, click the <b>Build > Packaging</b> |
| item in the category list to show the Packaging page of the dialog; check |
| the <b>Needs Restart on Install</b> checkbox and click <b>OK</b> to |
| save this setting. |
| </li> |
| </ol> |
| |
| <h2><a name="tweakBuildScript">Modifying the Plug-In's Build Script</a></h2> |
| We now have a plug-in. However, we will want it to bundle the |
| <code>Bean</code> project. So before going further, it would be useful to |
| do the following: |
| <ol> |
| <li>Modify the module's build script to recompile the Bean project — |
| this way, the module will always contain the latest version of the |
| project</li> |
| <li>Modify the build script to copy the Bean project into the place |
| it needs to be to bundle <code>Bean.jar</code> into our module.</li> |
| </ol> |
| Doing these things involves overriding two targets in our module project's |
| build script. Ant supports a crude sort of target inheritance, in which |
| we replace a target from one build script, but call the original target by |
| referring to <code>[projectname].[targetname]</code> (the project name in |
| this case is the name defined in the <code><project></code> tag at the |
| top of any Ant build script). |
| <ol class="instructions"> |
| <li>Open the build script by expanding the module project in the <b>Projects</b> |
| tab in the IDE, and the <b>Important Files</b> node under it, and |
| double clicking the <b>Build Script</b> node. This corresponds to |
| the file <code>build.xml</code> in the <code>BeanLibraryModule</code> |
| directory which is the root of our module project.</li> |
| <li>Add the code found in <a href="listing3">Listing 3</a> to the build |
| script, below the line <code><import file="nbproject/build-impl.xml"/></code>. |
| </ol> |
| <h3><a name="listing3">Listing 3: Module Build Script Changes</a></h3> |
| <pre><target name="build-init" depends="harness.build-init"> |
| <echo>Rebuilding Bean JAR</echo> |
| <ant antfile="../Bean/build.xml" target="jar" inheritall="false" inheritrefs="false"/> |
| <mkdir dir="release/libs"/> |
| <copy file="../Bean/dist/Bean.jar" todir="release/libs"/> |
| </target> |
| |
| <target name="clean" depends="projectized-common.clean"> |
| <echo>Cleaning and deleting copy of Bean JAR</echo> |
| <ant antfile="../Bean/build.xml" target="clean" inheritall="false" inheritrefs="false"/> |
| <delete file="${basedir}/release/libs/Bean.jar"/> |
| </target></pre> |
| |
| <p class="tips"> |
| Most of the targets in the <code>build.xml</code> for a module project |
| are in other files — specifically, in <code>nbproject/build-impl.xml</code> |
| and in <code>$HARNESS/build.xml</code> and <code>$HARNESS/common.xml</code> |
| (<code>$HARNESS</code> is a directory under the copy of NetBeans you |
| are building against, which may or may not be your IDE). |
| To find out what file a target you are calling or overriding is in, |
| find the <code>build.xml</code> in the <b>Files</b> tab in the IDE. |
| Expand its node and you will see all of the targets (even ones in |
| other files). Right click the |
| target you are wondering about and choose <b>Open</b> to open the |
| file which contains that target in the IDE. The path on disk to the |
| file will be shown in the tooltip of its tab in the editor. |
| |
| <p>This code will build the <code>Bean</code> project, and copy the resulting |
| JAR file to <code>BeanLibraryModule/release/libs</code>. The build script |
| will bundle anything under the <code>release</code> subdir of a module into |
| the NBM file you will deliver to your users (for example, via an update |
| server found via <b>Tools > Plugins</b>). |
| <p/> |
| At this point, it is a good time to make sure everything is working correctly. |
| You can test this by right clicking <code>BeanLibraryModule</code> in the |
| <b>Projects</b> tab, and choosing <b>Build</b> from the popup menu (or |
| by pressing F11). |
| <p/> |
| |
| <h2>Adding Bean.jar to Tools > Libraries</h2> |
| Now we need to add some metadata to our module — no code yet — to |
| make <code>Bean.jar</code> appear in the list of libraries for users who |
| have installed our module. This involves two steps: |
| <ol class="instructions"> |
| <li>Open the module's <i>layer file</i> — you can find it under |
| the <b>Important Files</b> node below the module project's node in |
| the <b>Projects</b> tab (if you don't see it, you did not check the |
| <b>Generate XML Layer</b> button when you created the module project). |
| This file provides declarative metadata to NetBeans at runtime. One |
| of the things it can do is tell NetBeans about a library a module |
| is installing.</li> |
| <li>Between the <code><filesystem></code> tags, add the XML from |
| <a href="#listing4">listing 4</a>.</li> |
| </ol> |
| |
| <h3><a name="listing4">Listing 4: Adding Library Metadata to a Module's XML Layer</a></h3> |
| <pre> |
| <folder name="org-netbeans-api-project-libraries"> |
| <folder name="Libraries"> |
| <file name="Bean.xml" url="Bean.xml"/> |
| </folder> |
| </folder> |
| </pre> |
| The <code>url</code> attribute of the <code>file</code> tag is important — |
| the XML we have entered defines a <i>virtual file</i> — but a file name |
| is usually useless without some content. The URL attribute is a path, relative |
| to the layer file, in the location where it really lives on disk. The next |
| step is to actually create a file called <code>Bean.xml</code>. |
| <ol class="instructions"> |
| <li>With the layer XML file open, press Ctrl-Shift-1 (Command-Shift-1 on |
| Macintosh) to reveal the file, inside the package |
| <code>org.netbeans.demo.form.beanlib</code> in the module project's |
| source code.</li> |
| <li>Right click that package, and choose <b>New > Other</b>. In the |
| New File Wizard which opens, choose <b>XML > XML Document</b>.</li> |
| <li>Name the file <code>Bean</code> on the second page of the |
| wizard and click <b>Finish</b> to create the file.</li> |
| <li>Populate the file with the XML content in <a href="#listing5">listing 5</a>.</li> |
| </ol> |
| <h3><a name="listing5">Listing 5: An XML Library Definition for Bean.jar</a></h3> |
| <pre> |
| <?xml version="1.0" encoding="UTF-8"?> |
| <!DOCTYPE library PUBLIC "-//NetBeans//DTD Library Declaration 1.0//EN" "https://netbeans.org/dtds/library-declaration-1_0.dtd"> |
| <library version="1.0"> |
| <name>Bean</name> |
| <type>j2se</type> |
| <localizing-bundle>org.netbeans.demo.form.beanlib.Bundle</localizing-bundle> |
| <volume> |
| <type>classpath</type> |
| <resource>jar:nbinst://org.netbeans.demo.form.beanlib/modules/ext/Bean.jar!/</resource> |
| </volume> |
| <volume> |
| <type>src</type> |
| </volume> |
| <volume> |
| <type>javadoc</type> |
| </volume> |
| </library> |
| </pre> |
| <div class="tips"> |
| Note that there are placeholders in this file for Javadoc documentation |
| and source files. If you want to include these later, just create targets |
| in <code>Bean/build.xml</code> to build and zip the javadoc and sources |
| into zip files, and modify <code>BeanLibraryModule</code> to call those |
| targets in the Bean project and copy the additional files into the |
| same directory as <code>Bean.jar</code>; then add <code><resource></code> |
| tags similar to the one already in this file, but pointing to the zip files. |
| Such files are helpful for users who want instantly available documentation, |
| or wish to step through your component's code in a debugger. |
| </div> |
| |
| <h2>Localizing Library and Other Names</h2> |
| All user-visible strings in NetBeans are localized — put into |
| resource-bundle files, so they can be translated into other human languages. |
| Things which are installed declaratively via <code>layer.xml</code> files |
| are no exception. You may have noticed that a <i>localizing bundle</i> |
| is mentioned in some of the XML we have already entered. This is a pointer |
| to a file named <code>Bundle.properties</code>, which should live in the |
| package <code>org.netbeans.demo.form.beanlib</code> alongside our other |
| files. If it does not exist, create it as follows: |
| <ol class="instructions"> |
| <li>Right click the package <code>org.netbeans.demo.form.beanlib</code> |
| and choose <b>New > Other</b> from the popup menu.</li> |
| <li>Choose <b>Other > Properties File</b> on |
| the first step of the New File Wizard and click Next.</li> |
| <li>On the second step of the wizard, name the file <code>Bundle</code> |
| and click <b>Finish</b>.</li> |
| <li>Add the content in <a href="#listing6">listing 6</a> to the |
| newly created resource bundle file (the content includes entries |
| for files we are about to create in order to add <code>Bean1</code> to |
| the Component Palette).</li> |
| </ol> |
| <h3><a name="listing6">Listing 6: Localized Names for Library and Component Palette Items</a></h3> |
| <pre> |
| Bean=Bean |
| FormDesignerPalette/Bean=Beans! |
| NAME_bean-Bean1=The Bean |
| HINT_bean-Bean1=This is a Bean |
| </pre> |
| |
| At this point, we have a working module to bundle Bean as a library. |
| To try it out, right click the Bean Library Module project and choose |
| <b>Run</b>. This will start another copy of NetBeans. When it is started, |
| look for your library in the library manager dialog that opens when you |
| select <b>Tools > Libraries</b> from the main menu. |
| |
| <h2>Including <code>Bean.jar</code> on the Module's Class Path</h2> |
| We are bundling the JAR file as a library. However, if we want property |
| editors which can talk to both our Java Bean classes and to NetBeans itself, |
| we will need to put <code>Bean.jar</code> onto our module's classpath as |
| well. NetBeans is very strict about what JARs a module can see classes |
| from, and by default, a library is for use in the projects a user creates, |
| not for loading in to the VM NetBeans is running in. So we need to |
| explicitly include <code>Beans.jar</code> in our module's classpath if we |
| want to be able to use classes from it in our module — and if we want |
| to provide <i>NetBeans-aware property editors</i> we need to do that. |
| |
| <p class="tips"> |
| Not everybody needs property editors that interact with |
| the IDE beyond the very limited ways the Java Beans specification allows. |
| If you are writing ordinary property editors, you can simply skip the rest of this step, then |
| follow the later steps to add your beans to the component |
| palette and stop there: |
| <ol> |
| <li>Create another Java Class Library project called BeanEditors.</li> |
| <li>Put the Beans project on its classpath.</li> |
| <li>Create the <code>beans</code> package in the new project.</li> |
| <li>Write your properties (and optionally BeanInfo) there.</li> |
| <li>Add another <code><resource></code> entry to <code>Bean.xml</code> |
| below the first one, which refers to <code>BeanEditors.jar</code></li> |
| <li>Modify the module project's build script to build that project. |
| too, and copy <code>BeanEditors.jar</code> to <code>release/libs</code>.</li> |
| </ol> |
| |
| <p>To add <code>Bean.jar</code> to the classpath of <i>classes in your module</i>, |
| do the following: |
| <ol class="instructions"> |
| <li>Under the <b>Important Files</b> node under the Bean Library Module |
| project, double click the node <b>Project Metadata</b> to open the |
| project's <code>nbproject/project.xml</code> file in the editor.</li> |
| <li>Add the code in <a href="#listing7">listing 7</a> to the bottom |
| of this file, just above the closing <code></data></code> tag.</li> |
| <li>Build the Bean Library Module project, to ensure that the JAR is |
| where it needs to be.</li> |
| <li>Shut down and restart the IDE (module projects are not terribly |
| intelligent about rescanning the classpath when the project metadata |
| is manually modified, so you need to do this to have code-completion |
| and parsing work in the editor later, when you use |
| classes from <code>Bean.jar</code> in your |
| module. This may be improved in future release of NetBeans).</li> |
| </ol> |
| <h3><a name="listing7">Listing 7: Adding Bean.jar to our Module's Classpath</a></h3> |
| <pre> |
| <class-path-extension> |
| <runtime-relative-path>ext/Bean.jar</runtime-relative-path> |
| <binary-origin>../Bean/dist/Bean.jar</binary-origin> |
| </class-path-extension></pre> |
| |
| <div class="tips"> |
| The “runtime relative path” is the path to Bean.jar from |
| the location of the module JAR at runtime. The NBM file |
| which is created when you right click the module project and choose |
| <b>Create NBM</b> is unpacked onto disk when the user installs it. |
| You can build the NBM and then expand in the <b>Files</b> tab in the |
| IDE to browse its contents. You will find the module JAR under the |
| <code>modules/</code> folder in the NBM. You will also find |
| <code>modules/ext/Bean.jar</code> there — Bean.jar is added to |
| the module's classpath using the standard Java mechanism of including |
| <code>Class-Path: ext/Bean.jar</code> in the module's JAR manifest. |
| </div> |
| |
| <h2>Adding Bean1 to the Component Palette</h2> |
| We have our library embedded in our module — next we need to put |
| our component on the Component Palette, so users will be able to drag |
| and drop it into their user interfaces. Doing that is quite simple, and |
| very similar to the way we added <code>Bean.jar</code> as a library — |
| it will again involve editing the <code>layer.xml</code> file, adding a |
| reference to an external XML file and then creating that file. |
| <ol class="instructions"> |
| <li>Open the <code>layer.xml</code> file, either by clicking |
| <b>Important Files > XML Layer</b> under your project, |
| or the node for <code>layer.xml</code> in the package |
| <code>org.netbeans.demo.form.beanlib</code>.</li> |
| <li>Add the code in <a href="#listing8">listing 8</a> after the |
| initial <code><filesystem></code> tag.</li> |
| <li>Create a new XML file called <code>Bean1_paletteItem.xml</code> |
| next to the <code>layer.xml</code> file in the same package.</li> |
| <li>Replace the new XML file's contents with the XML code in |
| <a href="#listing9">listing 9</a>. |
| </ol> |
| |
| <h3><a name="listing8">Listing 8: Adding a <code>palette_item</code> file to the layer.xml</a></h3> |
| <pre> |
| <folder name="FormDesignerPalette"> |
| <folder name="Bean"> |
| <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.demo.form.beanlib.Bundle"/> |
| <file name="Bean1.palette_item" url="Bean1_paletteItem.xml"/> |
| </folder> |
| </folder></pre> |
| <h3><a name="listing9">Listing 9: XML File Defining a Component on the Palette</a></h3> |
| <pre> |
| <?xml version="1.0" encoding="UTF-8"?> |
| <palette_item version="1.0"> |
| <component classname="bean.Bean1"/> |
| <description localizing-bundle="org.netbeans.demo.form.beanlib.Bundle" |
| display-name-key="NAME_bean-Bean1" |
| tooltip-key="HINT_bean-Bean1" /> |
| <classpath> |
| <resource type="library" name="Bean"/> |
| </classpath> |
| </palette_item> |
| </pre> |
| |
| <P>At this point, the work of embedding our library and our component is done. |
| Run the module now to try out the result — create a new project in |
| the copy of NetBeans that starts, then use <b>New > JPanel Form</b> to |
| show the form editor (aka “Matisse”). There will be a new |
| category, <b>Beans!</b> on the Component Palette. Expand it, and you will |
| see <code>Bean1</code>, listed as <b>The Bean</b> (these are the strings |
| we defined in our <code>Bundle.properties</code> file). Drag it onto the |
| form to use it. |
| <p>Notice also that, after you add a <code>Bean1</code> to a form, if you |
| expand the <b>Libraries</b> node under the project, the <code>Bean</code> |
| library has automatically been added to the project's classpath. |
| |
| <p class="tips"> |
| The Java Beans specification allows a <code>BeanInfo</code> |
| class for a component to define a localized name for it, along with |
| icons. In the example above, we defined the localized name in the |
| <code>palette_item</code> file's definition and the <code>Bundle.properties</code> |
| file. You can use either one (just leave out the line about the resource |
| bundle in the XML file to use the BeanInfo); if you are going |
| to need a <code>BeanInfo</code> anyway, you can just define it there. |
| However, since they are Java classes, BeanInfos use memory and are |
| an inefficient way to define this sort of thing. If possible, avoid |
| having a <code>BeanInfo</code> and just use this mechanism. |
| <p/> |
| If you want to provide icons via the <code>palette_item</code> XML file, |
| you can do that too — just add the following lines inside the |
| <code><palette_item></code> tags in the file, replacing the file |
| name with a .gif or .png file name, and the path with the path in your module |
| to the package they are in: |
| <pre><icon16 urlvalue="nbres:/org/netbeans/modules/form/beaninfo/awt/panel.gif" /> |
| <icon32 urlvalue="nbres:/org/netbeans/modules/form/beaninfo/awt/panel32.gif" /></pre> |
| |
| <h2>Creating A Property Editor</h2> |
| Now we are ready to create a property editor. We will put our editors in |
| another package, <code>org.netbeans.demo.form.beanlib.editors</code> — |
| in accordance with the Java Beans specification, that package will be registered |
| with <code>java.beans.PropertyEditorManager</code>. We don't need |
| <code>PropertyEditorManager</code> to be able to find other classes that are part |
| of our module, but are not our property editors or classes our property |
| editors use. So keeping unrelated classes invisible to our property editors |
| is good sense both from a perfomance and a security perspective. |
| <ol class="instructions"> |
| <li>Right-click the <code>org.netbeans.demo.form.beanlib.editors</code> package |
| and choose <b>New > Java Class</b>.</li> |
| <li>When the New File Wizard opens, name the class <code>ColorValueEditor</code>.</li> |
| <li>Replace the template code that initially appears in the new Java file with |
| the code in <a href="#listing10">listing 10</a>. |
| </ol> |
| <h3><a name="listing10">Listing 10: A PropertyEditor for ColorValue Objects</a></h3> |
| <pre>package org.netbeans.demo.form.beanlib.editors; |
| import bean.ColorValue; |
| import java.awt.Component; |
| import java.awt.Graphics; |
| import java.awt.Rectangle; |
| import java.beans.PropertyChangeListener; |
| import java.beans.PropertyChangeSupport; |
| import java.beans.PropertyEditor; |
| import org.openide.explorer.propertysheet.ExPropertyEditor; |
| import org.openide.explorer.propertysheet.PropertyEnv; |
| public class ColorValueEditor implements PropertyEditor, ExPropertyEditor { |
| private ColorValue value; |
| public void setValue(Object o) { |
| this.value = (ColorValue) o; |
| } |
| |
| public Object getValue() { |
| return value; |
| } |
| |
| public boolean isPaintable() { |
| return false; |
| } |
| |
| public void paintValue(Graphics grphcs, Rectangle rctngl) { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| public String getJavaInitializationString() { |
| return "new ColorValue(" + value.getRed() + ',' + |
| value.getGreen() + ',' + value.getBlue() + ')'; |
| } |
| |
| public String getAsText() { |
| return "" + value.getRed() + ',' + value.getGreen() + ',' + |
| value.getBlue(); |
| } |
| |
| public void setAsText(String string) throws IllegalArgumentException { |
| String[] rgb = string.split(","); |
| if (rgb == null || rgb.length != 3) { |
| throw new IllegalArgumentException ("Should be in format " + |
| "'red,green,blue'"); |
| } |
| try { |
| int red = Integer.parseInt(rgb[0].trim()); |
| int green = Integer.parseInt(rgb[1].trim()); |
| int blue = Integer.parseInt(rgb[2].trim()); |
| setValue (new ColorValue(red, green, blue)); |
| } catch (NumberFormatException nfe) { |
| throw new IllegalArgumentException(nfe); |
| } |
| } |
| |
| public String[] getTags() { |
| return null; |
| } |
| |
| public Component getCustomEditor() { |
| return null; |
| } |
| |
| public boolean supportsCustomEditor() { |
| return false; |
| } |
| |
| private final PropertyChangeSupport supp = new PropertyChangeSupport(this); |
| public void addPropertyChangeListener(PropertyChangeListener pl) { |
| supp.addPropertyChangeListener(pl); |
| } |
| |
| public void removePropertyChangeListener(PropertyChangeListener pl) { |
| supp.removePropertyChangeListener(pl); |
| } |
| |
| private PropertyEnv env; |
| public void attachEnv(PropertyEnv pe) { |
| env = pe; |
| } |
| } |
| </pre> |
| |
| <h2>Registering The Property Editor Package</h2> |
| <p>We now have a property editor for <code>ColorValue</code> objects. The next |
| step is to register our property editor package, so that, when our module is |
| run in the IDE, <code>java.beans.PropertyEditorManager</code> can find our |
| editor and it will be used in the Property Sheet of the Form Editor. |
| <p>While most of the time, the way you install things in NetBeans, so that the IDE |
| can find your module's classes at runtime, is declarative — using the |
| <code>layer.xml</code> file and similar mechanisms — <code>PropertyEditorManager</code> |
| is not part of NetBeans, it is part of the JDK. It expects registration |
| to be done programmatically, via Java code that runs during IDE startup. |
| Running code during startup is generally to be avoided, since it means the |
| user will be looking at the startup splash-screen for longer, but in this |
| case there is no other way. |
| <p/> |
| To register our property editor, we need to create a subclass of |
| <code>org.openide.modules.ModuleInstall</code>, |
| and add a reference to it to our module's JAR manifest. Fortunately, there |
| is a file template built into NetBeans' module-writing tools that will take |
| care of creating the subclass and adding the manifest entry — we can use |
| that and then just add the code we need to the resulting <code>ModuleInstall</code>. |
| To do that: |
| <ol class="instructions"> |
| <li>Right click the <code>org.netbeans.demo.form.beanlib</code> package and |
| choose <b>New > Other</b> from the popup menu.</li> |
| <li>In the New File Wizard, choose <b>Module Development > Module Installer</b> |
| and click <b>Next</b>, then click <b>Finish</b>. A Java file called |
| <code>Installer</code> will be created in the package.</li> |
| <li>Replace the <code>restored()</code> method with the contents of |
| <a href="#listing11">listing 11</a></li> |
| </ol> |
| <h3><a name="listing11">Listing 11: Registering The Property Editors Package At IDE Startup</a></h3> |
| <pre> |
| public void restored() { |
| String[] old = PropertyEditorManager.getEditorSearchPath(); |
| List <String> l = new ArrayList<String>(Arrays.asList(old)); |
| l.add ("org.netbeans.demo.form.beanlib.editors"); |
| PropertyEditorManager.setEditorSearchPath(l.toArray(new String[l.size()])); |
| }</pre> |
| <div class="tips"> |
| <p><code>java.beans.PropertyEditorManager</code> uses a naming convention |
| to recognize property editors: It expects the class name of an editor |
| for a type to be the name of the class it edits plus "Editor" |
| (i.e. the editor class for a ColorValue must be called ColorValueEditor). |
| PropertyEditorManager also allows you to register a specific |
| editor class to edit a specific class. The code above would look like |
| <code>PropertyEditorManager.registerEditor (ColorValue.class, ColorValueEditor.class)</code> |
| if we took that approach. |
| <p>Package name based registration has the |
| advantage that neither the ColorValue nor the ColorValueEditor class |
| needs to be loaded into the VM unless the user actually uses it. |
| </div> |
| |
| <h2>NetBeans Form Editor Classloader Black Magic</h2> |
| <p>At this point we are almost ready to run our module with our property |
| editor. There is one bit of arcana left to take care of. As mentioned |
| earlier, NetBeans does various tricks with classloaders — in particular, |
| limiting classes a module can see to only those ones it says it needs |
| access to. |
| <p>A Swing GUI and its libraries are classes that belong to the user — they |
| are not parts of NetBeans. The form editor takes a similar approach — |
| Java classes used in a Swing UI <i>are</i> loaded into the Java Virtual Machine |
| NetBeans is running in; however, they are loaded in their own classloader, |
| which normally does not allow random components access to classes from |
| a module. This has two beneficial effects: |
| <ol class="instructions"> |
| <li>A foreign Swing component cannot interfere with the operation of the |
| rest of the IDE, just because a user dropped it on a form.</li> |
| <li>Misbehaving or memory-leaking components can be discarded when the |
| form is closed and the classloader it used is discarded — |
| limiting the potential damage a buggy component can do.</li> |
| </ol> |
| <p>We have already set up the classpath so that our module can see classes |
| from <code>Bean.jar</code>. We need to set up the reverse situation — |
| allow our properties to call into classes in our module and the rest of |
| NetBeans when they are loaded inside the sandbox of the classloader the |
| form editor uses for loading the user's components. |
| <p>This is accomplished via a bit of black magic with the form editor's classloader. |
| The form editor allows us to define a special text file in our <code>layer.xml</code> |
| file, which contains a list of classes and/or packages that should be visible |
| to components living inside a Swing form. To accomplish this: |
| <ol class="instructions"> |
| <li>Open <code>layer.xml</code> again in the text editor.</li> |
| <li>Add the XML fragment from <a href="#listing12">listing 12</a> before |
| the closing <code></filesystem></code> tag.</li> |
| <li>Right click the <code>org.netbeans.demo.form.beanlib</code> package, |
| and choose <b>New > Other</b>.</li> |
| <li>In the New File Wizard, choose <b>Other > Empty File</b> and click |
| <b>Next</b>.</li> |
| <li>In the second page of the New File Wizard, name the file <code>BeanClasses.txt</code> |
| (note that because we are using the <b>Empty File</b> template, we |
| need to specify the file extension — normally you do not do this |
| or you end up with file names such as <code>Foo.xml.xml</code>).</li> |
| <li>Paste the contents of <a href="#listing13">listing 13</a> into the |
| new text file.</li> |
| </ol> |
| <h3><a name="listing12">Listing 12: Registering Classes That Should Be Visible in layer.xml</a></h3> |
| <pre> |
| <folder name="org-netbeans-modules-form"> |
| <folder name="classloader"> |
| <folder name="system"> |
| <file name="BeanClasses.txt" url="BeanClasses.txt"/> |
| </folder> |
| </folder> |
| </folder></pre> |
| <p class="tips"> |
| If your property editors or components also need to be able |
| to see classes or resources (such as images) that are part of |
| the user's project, you can register the |
| class list in the folder <code>system_with_project</code> instead of |
| <code>system</code>. If some do and some do not, register two lists, |
| including only those that really need to see classes from the user's |
| project in <code>system_with_project</code>. |
| |
| |
| <h3><a name="listing13">Listing 13: Listing Module Classes That Should Be Visible to Components in the Form Editor</a></h3> |
| <pre>org.netbeans.demo.form.beanlib.editors.** |
| bean.** |
| </pre> |
| Now at last we have working property editors which are registered by our |
| module. You can run the module project, add a <code>Bean1</code> to a |
| Swing form, and the property <code>colorValue</code> will use our property |
| editor. |
| <p/> |
| <p class="tips">This file can list individual classes, or it can list packages |
| including all subpackages of those classes by using the suffix <code>**</code>, |
| or limit the search to only the one specified package but using the |
| suffix <code>*</code>. |
| The next step is to create a custom editor that will interact with the |
| IDE, controlling its (NetBeans-provided) OK button. |
| |
| <h2>Adding A Custom Editor</h2> |
| To really interact with the IDE, we should add support for a custom |
| (pop-up window) editor for our <code>ColorValue</code> property. |
| To do that: |
| <ol class="instructions"> |
| <li>Right-click the <code>org.netbeans.demo.form.beanlib.editors</code> package |
| and choose <b>New > JPanel Form</b> from the popup window.</li> |
| <li>In the New File Wizard that opens, name the file <code>ColorValueCustomEditor</code>.</li> |
| <li>In the newly created JPanel form, add the following components |
| from the Component Palette, arranging the UI as shown in |
| <a href="#figure2">figure 2</a> and setting the variable names |
| as shown below (to set the name, slow-double-click the name of |
| each component in the <b>Inspector</b> window, then type the new |
| name; component type shown in parentheses): |
| <p><center> |
| <a name="figure2"><img src="../../images/tutorials/property-editors/colorValueCustomEditorUI.png" alt="Color Value Custom Editor"></a><br/> |
| <i>Figure 2: Color Value Custom Editor User Interface</i> |
| <p> |
| <table> |
| <tr> |
| <td class="td">redLabel (JLabel)</td><td class="td">redSlider (JSlider)</td><td class="td">redValue (JLabel)</td> |
| </tr> |
| <tr> |
| <td class="td">greenLabel (JLabel)</td><td class="td">greenSlider (JSlider)</td><td class="td">greenValue (JLabel)</td> |
| </tr> |
| <tr> |
| <td class="td">blueLabel (JLabel)</td><td class="td">blueSlider (JSlider)</td><td class="td">blueValue (JLabel)</td> |
| </tr> |
| <tr> |
| <td class="td">intLabel (JLabel)</td><td class="td" colspan="2" align="center">intValue (JTextField)</td> |
| </tr> |
| <tr> |
| <td class="td" colspan="3" align="center">sample (JLabel)</td> |
| </tr> |
| </table> |
| <i>Table 1: Component Variable Names and Types in ColorValueCustomEditor</i> |
| </center> |
| </li> |
| <li>Set the property <b>Opaque</b> to true on the property sheet for |
| <code>sample</code>.</li> |
| <li>Select all (shift-click) of the JSliders in the form editor, and set |
| their <code>maximum</code> |
| property to <code>255</code>.</li> |
| <li>Replace the constructor of <code>ColorValueCustomEditor</code> with the |
| content of <a href="#listing14">listing 14</a>. |
| <li>Change the class signature of <code>ColorValueCustomEditor</code> to look |
| like <a href="#listing15">listing 15, implementing <code>ChangeListener</code> |
| and <code>DocumentListener</code></a>.</li> |
| <li>Add the code in <a href="listing16">listing 16</a> to |
| <code>ColorValueCustomEditor</code> to implement the listener |
| interfaces and handle events.</li> |
| </ol> |
| <h3><a name="listing14">Listing 14: ColorValueCustomEditor Constructor</a></h3> |
| <pre> |
| private final ColorValueEditor ed; |
| public ColorValueCustomEditor(ColorValueEditor ed, PropertyEnv env) { |
| initComponents(); |
| ColorValue cv = (ColorValue) ed.getValue(); |
| if (cv != null) { |
| Color c = cv.toColor(); |
| setColor(c); |
| intValue.setText(c.getRGB() + ""); |
| } |
| env.setState(PropertyEnv.STATE_VALID); |
| this.ed = ed; |
| redSlider.getModel().addChangeListener(this); |
| greenSlider.getModel().addChangeListener(this); |
| blueSlider.getModel().addChangeListener(this); |
| intValue.getDocument().addDocumentListener(this); |
| }</pre> |
| |
| <h3><a name="listing15">Listing 15: Changing the Class Signature of ColorValueCustomEditor to Implement Listeners</a></h3> |
| <pre>final class ColorValueCustomEditor extends javax.swing.JPanel implements ChangeListener, DocumentListener {</pre> |
| |
| <h3><a name="listing16">Listing 16: Listener Interface Implementation for ColorValueCustomEditor</a></h3> |
| <pre> |
| private ColorValue getPropertyValue() { |
| return new ColorValue(redSlider.getValue(), greenSlider.getValue(), |
| blueSlider.getValue()); |
| } |
| |
| private boolean inUpdate; |
| public void stateChanged(ChangeEvent ce) { |
| if (inUpdate) { |
| return; |
| } |
| inUpdate = true; |
| try { |
| redValue.setText(redSlider.getValue() + ""); |
| greenValue.setText(greenSlider.getValue() + ""); |
| blueValue.setText(blueSlider.getValue() + ""); |
| ColorValue v = getPropertyValue(); |
| Color c = v.toColor(); |
| intValue.setText(c.getRGB() + ""); |
| sample.setBackground(c); |
| ed.setValue(v); |
| } finally { |
| inUpdate = false; |
| } |
| } |
| |
| public void insertUpdate(DocumentEvent de) { |
| changedUpdate(de); |
| } |
| |
| public void removeUpdate(DocumentEvent de) { |
| changedUpdate(de); |
| } |
| |
| void setColor (Color c) { |
| boolean old = inUpdate; |
| inUpdate = true; |
| try { |
| redSlider.setValue(c.getRed()); |
| greenSlider.setValue(c.getGreen()); |
| blueSlider.setValue(c.getBlue()); |
| } finally { |
| inUpdate = old; |
| } |
| } |
| |
| public void changedUpdate(DocumentEvent de) { |
| if (!inUpdate) { |
| try { |
| int val = Integer.parseInt(intValue.getText().trim()); |
| setColor(new Color(val)); |
| } catch (NumberFormatException e) { |
| PropertyEnv env = ed.env; |
| if (env != null) { |
| env.setState(PropertyEnv.STATE_VALID); |
| } |
| } |
| } |
| } |
| </pre> |
| |
| Now we just need to modify <code>ColorValueEditor</code> to actually |
| create a <code>ColorValueCustomEditor</code>. To do that: |
| <ol class="instructions"> |
| <li>Update the <code>supportsCustomEditor()</code> and <code>getCustomEditor()</code> |
| to look like <a href="#listing17">listing 17</a></li> |
| </ol> |
| <h3><a name="listing17">Listing 17: Creating Our Custom Editor</a></h3> |
| <pre> |
| public Component getCustomEditor() { |
| return new ColorValueCustomEditor(this, env); |
| } |
| |
| public boolean supportsCustomEditor() { |
| return true; |
| }</pre> |
| |
| At this point, we are ready to run the project, and when you click on the |
| [...] button for the <code>colorValue</code> property of an instance of |
| <code>Bean1</code> on your form, our newly finished custom editor will open. |
| Notice that if you type an invalid number in the text area, the |
| OK button will become disabled. |
| |
| <h3>What ColorValueEditor Does</h3> |
| <p>You may have noticed that <code>ColorValueEditor</code> implements a |
| NetBeans interface, <code>org.openide.explorer.propertysheet.ExPropertyEditor</code>, |
| in addition to the standard JDK <code>PropertyEditor</code> interface. |
| This interface, or more importantly the <code>PropertyEnv</code> object that |
| is passed to it is the path for our property editor to escape the |
| prison of the Java Beans spec and interact with the environment (the IDE) |
| that instantiated it. |
| <p><code>PropertyEnv</code> is an enigmatic little class, but it offers a lot |
| of power. In our case, we are using it very simply, just to let our |
| custom editor control the OK button of the dialog it appears in. To do that, |
| we call <code>env.setState(PropertyEnv.STATE_INVALID)</code> to disable the |
| OK button, and <code>env.setState(PropertyEnv.STATE_VALID)</code> to reënable |
| it. Here are some of the other things we could do with it: |
| <ul> |
| <li>Delay figuring out if the user's input is good or not until the |
| user presses enter, by calling <code>env.setState(PropertyEnv.STATE_NEEDS_VALIDATION)</code> and |
| attaching a <code>VetoableChangeListener</code> which can veto a change |
| to <code>STATE_VALID</code> which will happen when the user presses |
| OK</code></li> |
| <li>Get the <code>Node.Property</code> object which created the property |
| editor and represents the property being useful, using <code>env.getFeatureDescriptor()</code>. |
| This is useful for passing hints to the property sheet about how an |
| editor should behave when in the property sheet. Two useful hints are |
| <ul> |
| <li>Call <code>env.getFeatureDescriptor().setValue("canEditAsText", Boolean.FALSE)</code> |
| to make the property non-editable inside the property sheet |
| (so the only way to change the property is to open the custom |
| editor). |
| </li> |
| <li>Call <code>env.getFeatureDescriptor().setValue("suppressCustomEditor", Boolean.TRUE)</code> |
| from a <code>PropertyEditor</code> subclass, to hide the |
| [...] custom editor button on a property that would otherwise |
| have one. |
| </li> |
| </ul> |
| </li> |
| <li>Register an <code>InplaceEditor.Factory</code> which can provide the UI |
| component that is shown in the property sheet when the user edits the property |
| without opening a custom editor (a tutorial on how to do that can |
| <a href="https://platform.netbeans.org/tutorials/nbm-property-editors.html">be found here</a>)</li> |
| <li>Get the Node for the file being edited like this: |
| <pre> |
| Node n = null; |
| for (Object o : env.getBeans()) { |
| if (o instanceof Node) { |
| n = (Node) o; |
| break; |
| } |
| } |
| </pre> |
| and use that to |
| <ul> |
| <li>Get the file that is being edited — <code>n.getLookup().lookup(DataObject.class).getPrimaryFile()</code></li> |
| <li>Find the project that owns the file being edited and interrogate its classpath (for example, |
| to list possible directories the user might want to save an icon file |
| to):<pre>FileObject fo = n.getLookup().lookup(DataObject.class).getPrimaryFile(); |
| Project project = FileOwnerQuery.getOwner(fo); |
| if (project != null) } { |
| ClassPathProvider provider = project.getLookup().lookup(ClassPathProvider.class); |
| ...</pre> |
| </li> |
| </ul> |
| </li> |
| </ul> |
| <div class="caveat"> |
| Be aware that <code>attachEnv()</code> may be called more than once for |
| your property editor. To make sure you are using the right instance of |
| <code>PropertyEnv</code>, store the value from the most recent call in |
| a field of your property editor, and pass that to the custom editor. |
| </div> |
| |
| |
| <h2>Writing XML Instead Of Serializing</h2> |
| <p>The last thing we may want to do is more about plumbing than anything the |
| user sees directly: When you are editing a Swing form in the NetBeans Form |
| Editor, you are really editing two files (though you only see the Java file |
| in the projects tab). The form editor is really an editor for an invisible (in NetBeans) |
| XML file that sits next to the Java source file. The form editor is really |
| an editor of that XML file. Whenever you make a change in the form editor, |
| the data about how components are positioned and their properties |
| is saved in that file. That file is then used to generate the |
| <code>initComponents()</code> method and other code inside the non-editable |
| blue code blocks in your Java source. Whenever the XML file changes (because |
| you made a change in the form editor and saved the file), those blue |
| <i>guarded blocks</i> are regenerated into the Java source file. |
| <p>It is worth taking a look at the <code>.form</code> file after you have |
| modified a <code>ColorValue</code> and saved the form (make sure you save it!). |
| To do this, you will need to go outside of NetBeans and use a text |
| editor (if on Windows, |
| use a text editor that understands lines that end with just a carriage |
| return character — WordPad, usually located in <code>C:\Program Files\Windows NT\Accessories</code> |
| on Windows will do). What you will see is something like this: |
| <pre><Property name="colorValue" type="bean.ColorValue" editor="org.netbeans.demo.form.beanlib.editors.ColorValueEditor"> |
| <SerializedValue value="-84,-19,0,5,115,114,0,15,98,101,97,110,46,67, |
| 111,108,111,114,86,97,108,117,101,95,6,-80,34,96, |
| <i style="color:gray">[remainder omitted]</i> |
| </Property></pre> |
| <p>What are all of these numbers? This is a <i>serialized</i> object. |
| The form editor knows nothing about your component, but needs some way |
| to save the state of our <code>ColorValue</code> object. The only |
| built-in way Java has to do that is to use serialization to save the |
| in-memory representation of your object as an array of bytes. The form |
| editor then translates that array of bytes into a terribly inefficient |
| comma-delimited string of numbers. |
| <p>There are four big problems with using serialization to write out an |
| object into the form file: |
| <ol> |
| <li>It's inefficent — it takes up a lot of space in the file, |
| and takes longer to read and write</li> |
| <li>It's not human-readable — If a form were corrupted in some |
| way, the user doesn't have any chance to figure out what the value |
| of this object actually was</li> |
| <li>It's fragile — the data structure depends on the JVM's in-memory |
| data structure for the class. If you add a field or a method |
| to <code>ColorValue</code> in the future, the data in all existing |
| <code>.form</code> files will be unusable. That means users will |
| lose their components and have to recreate them, or they must never |
| edit a form with a <code>Bean1</code> on it again. |
| </li> |
| </ol> |
| There is another way. Although nobody truly loves writing DOM code, you can |
| implement XMLPropertyEditor. What happens then is: |
| <ol> |
| <li>When the form is saved, the form editor will make an instance of |
| the property editor for the property |
| </li> |
| <li>The property editor will be passed the XML <code>Document</code> and |
| asked to provide a document node that contains data about the component</li> |
| <li>The next time the form is opened, the form editor will read the name |
| <code>org.netbeans.demo.form.beanlib.editors.ColorValueEditor</code> in |
| the XML, make an instance of our editor, and ask it to read the |
| XML that was written out and create an instance of <code>ColorValue</code> |
| to display in the form editor.</li> |
| </ol> |
| By using XML instead of serialization, we get to choose what data we store in |
| the <code>.form</code> file, how it is stored, and our code is in charge of |
| reading it back. If new properties or fields have been added to <code>ColorValue</code> |
| and we are reading an old form, we can just ignore missing values and use some |
| reasonable default value. The result is that our users are protected from |
| having corrupted, unopenable forms caused by upgrading to a new version of |
| <code>Bean.jar</code>. To use this approach instead, |
| <ol class="instructions"> |
| <li>Modify the class signature of <code>ColorValueEditor</code> so that |
| it implements <code>org.openide.explorer.propertysheet.editors.XMLPropertyEditor</code>. |
| </li> |
| <li>Implement the methods of <code>XMLPropertyEditor</code> as shown in |
| <a href="#listing18">listing 18</a>.</li> |
| </ol> |
| <h3><a name="listing18">Listing 18: Implementing XMLPropertyEditor</a></h3> |
| <pre>public void readFromXML(Node node) throws IOException { |
| NamedNodeMap attrs = node.getAttributes(); |
| Node red = attrs.getNamedItem("red"); |
| Node green = attrs.getNamedItem("green"); |
| Node blue = attrs.getNamedItem("blue"); |
| if (red != null && green != null && blue != null) { |
| value = new ColorValue( |
| Integer.parseInt(red.getNodeValue()), |
| Integer.parseInt(green.getNodeValue()), |
| Integer.parseInt(blue.getNodeValue()) |
| ); |
| } else { |
| value = new ColorValue(); //use default value |
| } |
| } |
| |
| public Node storeToXML(Document doc) { |
| Element el = doc.createElement("ColorValue"); |
| if (value != null) { |
| el.setAttribute("red", "" + value.getRed()); |
| el.setAttribute("green", "" + value.getGreen()); |
| el.setAttribute("blue", "" + value.getBlue()); |
| } |
| return el; |
| }</pre> |
| |
| <p>The above results in: |
| <pre><Property name="colorValue" type="bean.ColorValue" editor="org.netbeans.demo.form.beanlib.editors.ColorValueEditor"> |
| <ColorValue blue="128" green="235" red="26"/> |
| </Property></pre> |
| |
| <h2>Packaging Your Component</h2> |
| Once you have your module the way you like it, the next step is to package |
| it up so that others can install it. In NetBeans, this is extremely simple: |
| Just right click the module project and choose <b>Create NBM</b>. This will |
| create an NBM (NetBeans Module) file which includes your module and your |
| library in a single file any user can install using <b>Tools > Plugins</b>. |
| Users of your component do not have to deal with separate JAR and documentation |
| downloads, and if they want an updated version of your components, all they |
| have to do is install a new NBM to get them. |
| |
| <p class="tips">A useful way to deliver components is to set |
| up your own <i>update center</i>. An update center has a list of NBM |
| files for download. Users of NetBeans can just add the URL for your update |
| center on the <b>Settings</b> tab in <b>Tools > Plugins</b>. The IDE |
| will automatically check with your server on a regular basis to see if |
| there are updates available. |
| <p/> |
| You can make this process even easier by doing two things: |
| <ol> |
| <li>Automatically generate the update index using NetBeans: Just |
| create a <b>Module Suite</b> project and add the module to it. |
| Right click that project and choose <b>Create NBMs</b>. Along |
| with the NBM file, you get the <code>update.xml</code> file which |
| is what the IDE reads to figure out if any updates are there.</li> |
| <li>Use <a href="http://hudson.dev.java.net">Hudson</a> to run continuous |
| builds of your suite and publish the resulting files. This way |
| you can completely automate publishing new versions of your |
| module and libraries. More information about setting up |
| automated builds with Hudson <a href="http://xtest.netbeans.org/XTest_for_Platform.html#Build_application_using_Hudson">can be found here</a></li> |
| </ol> |
| |
| |
| <h2>Wrap Up</h2> |
| NetBeans provides a powerful way to deliver Java components to your users, |
| including all of the documentation and sources to your users and potential |
| users in a single easy-to-use deliverable. By creating property editors |
| that integrate tightly with NetBeans, you can further enhance the ease of |
| use of working with your components. |
| |
| <!-- |
| The first is simply an artificial substitute |
| for a color, which holds integer red, green and blue values. The |
| second, based on real-world requirements an organization which |
| was using NetBeans internally, is a considerably more complicated |
| class: An IdInfo has a string ID, representing one stream of data |
| the component will show at runtime. An IdInfo also has a <i>type</i> |
| for the data (Integer, Double or Enum). When the ID is supplied, |
| the component will contact a remote server, and pass it the ID. |
| The server will reply with the type of the data. The component (which |
| will represent a measurement of a physical process) will configure itself |
| to show data of that type. The |
| --> |
| |
| </body> |
| </html> |