Building Beans

Now that you have a feel for how beans look from the user's perspective, let's build some. In this section, we will become the Magic Beans Company. We will create some beans, package them for distribution, and use them in NetBeans to build a very simple app. (The complete JAR file, along with all the example code for this chapter, is on the DVD that accompanies this tutorial and at http://www.oracle.com/catalog/learnjava3.) The first thing we'll remind you of is that absolutely anything can be a bean. Even the following class is a bean, albeit an invisible one:

 public class Trivial implements java.io.Serializable {}


Of course, this bean isn't very useful: it doesn't have any properties, and it doesn't do anything. But it's a bean nonetheless, and we can drag it into NetBeans as long as we package it correctly. If we modify this class to extend JComponent, we suddenly have a graphical bean that be seen in the layout, with lots of standard Swing properties, such as size and color information:

 public class TrivialComponent extends JComponent {}


Next, let's look at a bean that's a bit more useful.

The Dial Bean

We created a nifty Dial component in . What would it take to turn it into a bean? Surprise: it is already a bean! The Dial has a number of properties that it exposes in the way prescribed by JavaBeans. A get method retrieves the value of a property; for example, getValue( ) retrieves the dial's current value. Likewise, a set method (setValue( )) modifies the dial's value. The dial has two other properties, which also have get and set methods: minimum and maximum. This is all the Dial needs to inform a tool such as NetBeans what properties it has and how to work with them. Because Dial is a JComponent, it also has all the standard Swing properties, such as color and size. The JComponent provides the set and get methods for all its properties. To use our Dial, we'll put it in a Java package named magicbeans and store it in a JAR file that can be loaded by NetBeans. The source code, which can be found on the accompanying CD (view its content online at http://examples.oracle.com/learnjava3/CD-ROM/), includes an Ant build file (see ) that compiles the code and creates the final JAR file. If you were starting from scratch, it would go like this: First, create a directory called magicbeans to hold our beans, add a package statement to the source files Dial.java, DialEvent.java, and DialListener.java (from ), put the source files into the magicbeans directory, and compile them (using the command javac magicBeans/Dial.java) to create class files. Next, we need to create a manifest file that tells NetBeans which of the classes in the JAR file are beans and which are support files or unrelated. At this point, we have only one bean, Dial.class, so create the following file, called magicBeans.manifest:

 Name: magicbeans/Dial.class
 Java-Bean: True


The Name: label identifies the class file as it will appear in the JAR: magicbeans/Dial.class. Specifications appearing after an item's Name: line and before an empty line apply to that item. (See for more details.) We have added the attribute Java-Bean: True, which flags this class as a bean to tools that read the manifest. We will add an entry like this for each bean in our package. We don't need to flag support classes (such as DialEvent and DialListener) as beans because we won't want to manipulate them directly with NetBeans; in fact, we don't need to mention them in the manifest at all. To create the JAR file, including our manifest information, enter this command:

 % jar-cvmf magicbeans.manifest magicbeans.jar magicbeans/*.class


If you loaded the precompiled examples as instructed earlier, then you already have the Dial bean loaded into NetBeans. The version supplied in the precompiled magicbeans.jar file has additional packaging that allows it to appear with a spiffy icon in the palette, as we'll discuss a bit later. (If you haven't loaded the example JAR, you can import the one we just created by selecting Palette Manager from the Tools menu, as described earlier in this chapter.) If you want to replace the Dial bean on your palette, you can remove it by right-clicking on the icon and selecting Delete before importing the new JAR. (Actually, NetBeans 4.0 should reload the JAR automatically if you overwrite it.) You should now have an entry for Dial in the bean palette. Drop an instance of the Dial bean into a new JFrame Form file in NetBeans. As Screenshot-8 shows, the Dial's propertiesvalue, minimum, and maximum appear in the Properties pane and can be modified by NetBeans. If you just created the Dial JAR following our minimal instructions, you'll see these properties along with all the Swing properties inherited from the JComponent class. The figure shows the Dial bean as it appears later in this chapter (with the supplied packaging), after we've learned about the BeanInfo class. We're almost there. Now we're ready to put the Dial to use. Reopen the Juggler file that we created in the first section of this chapter. Add an instance of our new magic Dial bean to the scenario, as shown in Screenshot-9. Bind the value property of the Dial to the animationRate of the Juggler. Use the Connection Wizard, as before, selecting the Dial and then the Juggler. Select the DialEvent source and bind the animationRate property, selecting the Dial's value as the property source. When you complete the hookup, you should be able to vary the speed of the juggler by turning the dial. Try changing the maximum and minimum values of the dial to change the range.

Screenshot-8. The Dial component as a bean
Java ScreenShot

Screenshot-9. The Juggler with a dialable animation rate
Java ScreenShot

Design Patterns for Properties

We said earlier that tools such as NetBeans found out about a bean's properties by looking at its get and set methods. The easiest way to make properties visible is to follow these simple design patterns:

  • Method for getting the current value of a property:
     public PropertyType getPropertyName( )
    


  • Method for setting the value of a property:
     public void setPropertyName( PropertyType arg )
    


  • Method for determining whether a Boolean-valued property is currently true:
     public boolean isPropertyName( )
    


The last method is optional and is used only for properties with Boolean values. (You could use the get method for Boolean values as well.) The appropriate set and get methods for these features of our bean are already in the Dial class, either methods that we added or methods inherited from the java.awt.Component and javax.swing.JComponent classes:

 // inherited from Component
 public Color getForeground( )
 public void setForeground(Color c)
  
 public Color getBackground( )
 public void setBackground(Color c)
  
 public Font getFont( )
 public void setFont(Font f)
  
 // many others from Component and JComponent
 // part of the Dial itself
 public int getValue( )
 public void setValue(int v)
  
 public int getMinimum( )
 public void setMinimum(int m)
  
 public int getMaximum( )
 public void setMaximum(int m)


JavaBeans allows read and write-only properties, which are implemented simply by leaving out the get or set method. NetBeans uses the Reflection API to find out about the Dial bean's methods; it then uses these naming conventions to learn what properties are available. When we use the properties editor to change a value, NetBeans dynamically invokes the correct set method to change the value. If you look further at the JComponent class, you'll notice that other methods match the design pattern. For example, what about the setCursor( ) and getCursor( ) pair? NetBeans doesn't know how to display or edit a cursor, and we didn't supply an editor, so it ignores those properties in the properties sheet. NetBeans automatically pulls the property's name from the name of its accessor methods; it then lowercases the name for display on the properties sheet. For example, the font property is not listed as Font. Later, we'll show how to provide a BeanInfo class that overrides the way these properties are displayed, letting you provide your own friendly property names. Again, if you used the Dial bean from our precompiled example JAR you'll see only our three Dial properties. The JComponent properties are hidden by our special packaging (a BeanInfo class) that presents only the properties we wish to show.

Bean patterns in NetBeans

NetBeans automatically recognizes JavaBeans getter and setter method patterns in classes. In the File or Project Explorer (on the left), expand the folder for our LearningJava1 class and you'll see a Bean Patterns folder. You can add properties to any Java source class by right-clicking on the Bean Patterns folder and selecting Add, then Property. After you supply the name and type of the property, NetBeans automatically creates the necessary getter and setter methods. You can add instance variable and bound property support as well. This can save you a bit of typing if you need to add a lot of properties.

A (Slightly) More Realistic Example

We now have one nifty bean for the Magic Beans products list. Let's round out the set before we start advertising. Our goal is to build the beans we need to make a very simple form. The app performs a simple calculation after data is entered on the form.

A bean for validating numeric data

One component we're sure to need in a form is a text field that accepts numeric data. We're now going to build our NumericField bean that accepts and validates numbers and makes the values available as a property. You should recognize all the parts of the bean:

 //file: NumericField.java
 package magicbeans;
 import javax.swing.*;
 import java.awt.event.*;
 public class NumericField extends JTextField
 {
 private double value;
 static final int defaultSize = 6;
 public NumericField( ) {
 super( defaultSize );
 setInputVerifier( new InputVerifier( ) {
 public boolean verify( JComponent comp ) {
 JTextField field = (JTextField)comp;
 boolean passed = false;
 try {
 setValue( Double.parseDouble( field.getText( ) ) );
 } catch ( NumberFormatException e ) {
 comp.getToolkit( ).beep( );
 field.selectAll( );
 return false;
 }
 return true;
 }
 } );
 addActionListener( new ActionListener( ) {
 public void actionPerformed( ActionEvent e ) {
 getInputVerifier( ).verify( NumericField.this );
 }
 } );
 }
  
 public double getValue( ) {
 return value;
 }
 public void setValue( double newValue ) {
 double oldValue = value;
 value = newValue;
 setText( "" + newValue );
 firePropertyChange( "value", oldValue, newValue );
 }
 }


NumericField extends the Swing JTextField component. The constructor defaults the text field to a width of six columns, but you can change its size in NetBeans through the columns property. The heart of NumericField is the InputVerifier, which we have implemented as an anonymous inner class. Our verifier is called to parse the user's entry as a number, giving it a Double value. We have also added an ActionListener that validates when the user hits Enter in the field. If parsing succeeds, we update the value property using our setValue( ) method. setValue( ) then fires a PropertyChangeEvent to notify any interested beans. If the text doesn't parse properly as a number, we give feedback to the user by selecting (highlighting) the text. Earlier we verified the operation of this bean by placing two NumericFields in the workspace and binding the value property of one to the other. We saw that we were able to enter a new floating point value in one field and see the change reflected in the other.

An invisible multiplier

Now, let's make an invisible bean that performs a calculation rather than forming part of a user interface. Multiplier is a simple invisible bean that multiplies the values of two of its properties (A and B) to produce the value of a third read-only property (C). Here's the code:

 //file: Multiplier.java
 package magicbeans;
 import java.beans.*;
 public class Multiplier implements java.io.Serializable {
 private double a, b, c;
 synchronized public void setA( double val ) {
 a = val;
 multiply( );
 }
 synchronized public double getA( ) {
 return a;
 }
 synchronized public void setB( double val ) {
 b = val;
 multiply( );
 }
 synchronized public double getB( ) {
 return b;
 }
 synchronized public double getC( ) {
 return c;
 }
 synchronized public void setC( double val ) {
 multiply( );
 }
 private void multiply( ) {
 double oldC = c;
 c = a * b;
 propChanges.firePropertyChange(
 "c", new Double(oldC) , new Double(c) );
 }
 private PropertyChangeSupport propChanges =
 new PropertyChangeSupport(this);
 public void addPropertyChangeListener(
 PropertyChangeListener listener)
 {
 propChanges.addPropertyChangeListener(listener);
 }
 public void removePropertyChangeListener(
 PropertyChangeListener listener)
 {
 propChanges.removePropertyChangeListener(listener);
 }
 }


To make a Multiplier a source of PropertyChangeEvents, we enlist the help of a PropertyChangeSupport object. To implement Multiplier's methods for registering property-change listeners, we simply call the corresponding methods in the PropertyChangeSupport object. Similarly, a Multiplier fires a property change event by calling the PropertyChangeSupport object's firePropertyChange( ) method. This is the easiest way to get an arbitrary class to be a source of PropertyChangeEvents. The code is straightforward. Whenever the value of property a or b changes, we call multiply( ), which multiplies their values and fires a PropertyChangeEvent. So, we can say that Multiplier supports binding of its c property.

Putting them together

Finally, let's demonstrate that we can put our beans together in a useful way. Arrange three JLabels, three NumericFields, and a Multiplier, as shown in Screenshot-10.

Screenshot-10. TextLabels, NumericFields, and a Multiplier
Java ScreenShot

Bind the values of the first two NumericFields to the a and b properties of the Multiplier, respectively; bind the c value of the Multiplier to the third NumericField. Now, we have a simple calculator. Try some other arrangements. Can you build a calculator that squares a number? Can you see how you might build a simple spreadsheet? Well, perhaps not. We'll address some of the limitations in the next section.

Java ScreenShot
Comments