Now that you have the feel from the user’s perspective, let’s build some Beans. In this section we will become the Magic Beans company. We will create some Beans, package them for distribution, and use them in the BeanBox to build a very simple application. (The complete JAR file, along with all of the example code for this chapter, is on the CD-ROM that accompanies this book, and at http://www.oreilly.com/catalog/learnjava.)
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 you can drag it into the BeanBox. It’s important to realize that JavaBeans really doesn’t give a hard and fast definition for what a Bean is required to be. In practice though, we’ll want Beans that are a little more useful.
We created a nifty
Dial
component in Chapter 15.
What would it take to turn it into a Bean? Well, 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 do to inform a tool like BeanBox what properties it has and
how to work with them. In addition,
Dial
is a custom Swing component, and if you
look, you’ll see that the JComponent
class
follows the same rules for its important properties (for example, its
font).
In order to use our Dial
, we put it in a package
named magicbeans
, and store it in a JAR file that
can be loaded by the BeanBox. 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
, put the source files into
the magicbeans
directory, and compile them
(javac
magicbeans/Dial.java
) to
create class files.
Next, we need to create a manifest
file that tells the BeanBox which of the classes in the JAR file are
Beans and which are support files or unrelated. At this point, we
only have one Bean, Dial.class, so we 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 the section on JARs and manifest files in Chapter 3, 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 (like DialEvent
and
DialListener
) as Beans, because we won’t
want to manipulate them directly with the BeanBox; in fact, we
don’t need to mention them in the manifest at all. The
jar
utility will add appropriate entries for
them automatically.
To create the JAR file, including our manifest information, enter the following command:
% jar cvmf magicbeans.manifest magicbeans.jar magicbeans/*.class
Now we can load our JAR into the BeanBox using the Load JAR option
under the File menu. Use the File
dialog to locate our JAR and select it. An entry for
Dial
should appear in the Bean palette. We have
loaded our first Bean! Drop an instance of Dial
Bean into the BeanBox.
As Figure 19.7 shows, the dial’s
properties: value
, minimum
, and
maximum
are on the properties sheet and can be
modified by the BeanBox. (At this point, you’ll see several
other properties inherited from the JComponent
class as well. This picture shows the Dial
Bean as
it will appear later in this chapter, after we’ve learned about
the BeanInfo
class). We’re almost there. But
these properties are not very useful to other Beans unless we can
notify them when the dial’s value changes. We need to make
value
a bound property, by firing
PropertyChangeEvent
s
when the value changes. (Alternately, we could have other beans that
know about DialEvent
s. It really depends on how we
plan to design our suite of beans.) It won’t be hard to add
PropertyChangeEvent
s to our Bean because the
JComponent
class already handles them for us.
We insert the firePropertyChange
statement as the
first line of the Dial
’s setvalue( )
method:
public void setValue(int value) { firePropertyChange( "value", this.value, value ); ...
That’s all it takes to make Dial
a source of
PropertyChangeEvent
s. To fire an event, we use the
firePropertyChange( )
method built into
JComponent
. The JComponent
also
handles all of the listener registration for us.
The firePropertyChange( )
method takes three
arguments: the name of the property and the old and new values. It
may seem superfluous to send both the old and new values, but there
is one bonus when we do: the firePropertyChange( )
method doesn’t generate an event if the value has not actually
changed. This saves us the trouble of implementing
“event-avoidance” logic over and over. (It also prevents
looping and other bad behavior.) Various overloaded versions of
firePropertyChange( )
in the
JComponent
class accept different argument types,
including Object
and all of the primitive numeric
types.
Now we’re ready to put the Dial
to use.
Recompile and re-JAR the classes. Next, reload the
Juggler
example that we asked you to save in the
first section of this chapter. (Did you save it?) Add an instance of
our new magic Dial
Bean to the scenario, as shown
in Figure 19.8.
Let’s try to bind the value
property of the
Dial
to the animationRate
of
the Juggler
. The Bind
Property option should now be available under the
Edit menu because the BeanBox
recognizes that we are a source of
PropertyChangeEvents
. (Note that there are a
lot of properties listed here. Again,
we’ll show you later how to limit what is presented.) 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.
We said earlier that tools like BeanBox 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:
publicpropertyType
getPropertyName
( )
Method for setting the value of a property:
public void setPropertyName
(propertyType
arg )
public boolean isPropertyName
( )
The last method is used only for properties with
boolean
values, and is optional. (You could just
use the get method in this situation.)
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)
BeanBox uses the reflection API to find out about the
Dial
Bean’s methods (both its own and the
methods it inherits); it then uses the design patterns to figure out
what properties are available. When we use the properties sheet to
change a value, the BeanBox dynamically invokes the correct
set method to change the value.
But wait—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? BeanBox doesn’t know how to display or edit a cursor, so
it simply ignores those properties in the properties sheet.
BeanBox
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.
JavaBeans allows read-only and write-only properties, which are implemented simply by leaving out the get or set method.
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 application will perform a simple calculation after data is entered on the form.
One thing that we will need in almost any application is a plain old text label. Fortunately, Swing provides us with one for free.
We make a trivial subclass and package it as a Bean:
//file: TextLabel.java package magicbeans; import javax.swing.JLabel; public class TextLabel extends JLabel { public void setText( String s ) { super.setText(s); if ( getParent( ) != null ) { invalidate( ); getParent().validate( ); } } }
The only thing we’ve added here is a bit of code to make the
JLabel
behave better in the BeanBox. We
override the setText( )
method to have it revalidate its container whenever the label
changes. This will cause the label to snap to the correct size
automatically when you change the text in the property sheet.
Recreate the JAR file and try out the label in the BeanBox. Don’t forget to add TextLabel.class to the manifest and to specify that it’s a Bean.
Another component that
we’re sure to need in a form is a text field that accepts
numeric data. Let’s build a text-entry Bean that accepts and
validates numbers and makes the values available as a property. You
should recognize almost all of the parts of the
NumericField
Bean:
//file: NumericField.java package magicbeans; import javax.swing.JTextField; import java.awt.event.*; import java.beans.*; public class NumericField extends JTextField implements ActionListener { private double value; public NumericField( ) { super(6); addActionListener( this ); } public void actionPerformed( ActionEvent e ) { try { setValue( Double.parseDouble( getText( ) ) ); } catch ( NumberFormatException ex ) { select(0, getText().length( )); } } 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 heart of NumericField
is in the
actionPerformed( )
method. You’ll recall
that a JTextField
generates
ActionEvent
s whenever the user presses Return to
enter the data. We catch those events and try to parse the
user’s entry as a number, giving it a Double
value. If we succeed, we update the value
property
using our setValue( )
method. setValue( )
then fires a PropertyChangeEvent
to
notify any interested Beans. This event firing enables us to bind
NumericField
’s value
property to some other property. (We’ll see how in the next
example.)
If the text doesn’t parse properly as a number, we give feedback to the user by selecting (highlighting) the text. The field defaults to a width of six columns, but you can change its size by dragging it.
Verify the operation of NumericField
by placing
two of them in the BeanBox and binding the value
property of one to the other. You should be able to enter a new
floating point value and see the change reflected in the other.
Finally, let’s
try our hand at an invisible
Bean: one that performs a calculation rather than providing 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; } 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); } }
Because a Multiplier
is invisible, it
doesn’t extend the
JComponent
class. To make a Multiplier
a source of
PropertyChangeEvent
s, we enlist the help of a
PropertyChangeSupport
object. When we need to
invoke 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
PropertyChangeEvent
s.
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 any
of its properties.
Finally, let’s demonstrate that we can put our Beans together
in a useful way. Arrange three
TextLabel
s,
three NumericField
s, and a
Multiplier
into the scene shown in Figure 19.9.
Bind the values of the first two NumericField
s to
the A and B properties of the Multiplier
; bind the
C value to the third NumericField
. Now we have a
simple calculator. You could use this as a tip calculator, but
it’s important to realize that much more is possible. Try some
other arrangements. Can you build a calculator that squares a number?
Can you see how you might build a simple spreadsheet?
Before moving on, save this work so that you can reuse it later. This time, use the BeanBox’s Serialize component command to serialize the BeanBox container itself. To select the top-level BeanBox, click on the background of the workspace. The dashed line should appear around the entire BeanBox. Then use the Serialize component command to save your work. By serializing the BeanBox container, we save all of the Beans it contains and all of their interconnections. Later in this chapter, we’ll show you how to put these to use. (BeanBox’s Save command also stores the state of the BeanBox, but it may or may not use serialization to do so.)
So far, everything the BeanBox has known
about our Beans has been determined by low-level
reflection—that is, by looking
at the methods of our classes. The
java.beans.Introspector
gathers information on a Bean using
reflection, then analyzes and describes a Bean to any tool that wants
to know about it. The introspection process works only if we follow
design patterns that restrict what we call our methods; furthermore,
it gives us little control over exactly what properties and events
appear in the BeanBox menus. We have been forced to live with all of
the stuff that we inherit from the base Swing components, for
example. We can change all that by creating
BeanInfo
classes for our Beans. A
BeanInfo
class provides BeanBox’s
introspector with explicit information about the properties, methods,
and events of a Bean; we can even use it to customize the text that
appears in the BeanBox’s menus.
A BeanInfo
class implements the
BeanInfo
interface. That’s a complicated
proposition; in most situations, the introspector’s default
behavior is reasonable. So instead of implementing the
BeanInfo
interface, we extend the
SimpleBeanInfo
class, which implements all of
BeanInfo
’s methods. We can override specific
methods to provide the information we want; when we don’t
override a method, we’ll get the introspector’s default
behavior.
In the next few sections, we’ll develop a
DialBeanInfo
class that provides explicit
information about our Dial
Bean.
We’ll start out by describing the
Dial
’s properties. To do so, we must
implement
the
getPropertyDescriptors( )
method. This method simply returns an array of
PropertyDescriptor
objects—one for each
property we want to publicize.
To create a
PropertyDescriptor
, we call its constructor with two
arguments: the property’s name and the class. In the following
code, we create descriptors for the Dial
’s
value
, minimum
, and
maximum
properties. Then we call a few methods of
the PropertyDescriptor
class to provide additional
information about each property. In this example, we call the
setBound( )
method to state that minimum
and
maximum
are not bound properties but that
value
is a bound property. Our code also is
prepared to catch
an
IntrospectionException
, which can occur if
something goes wrong while creating the property descriptors:
//file: DialBeanInfo.java package magicbeans; import java.beans.*; public class DialBeanInfo extends SimpleBeanInfo { public PropertyDescriptor[] getPropertyDescriptors( ) { try { PropertyDescriptor value = new PropertyDescriptor("value", Dial.class); PropertyDescriptor minimum = new PropertyDescriptor("minimum", Dial.class); PropertyDescriptor maximum = new PropertyDescriptor("maximum", Dial.class); value.setBound(true); minimum.setBound(false); maximum.setBound(false); return new PropertyDescriptor [] { value, minimum, maximum }; } catch (IntrospectionException e) { return null; } } }
Perhaps the most interesting thing about
DialBeanInfo
is that by providing explicit
information for our properties, we automatically hide any other
properties that introspection might find. If you don’t provide
any property information, a development tool like BeanBox will find
out about all sorts of properties, including properties inherited
from the superclass. When we loaded the Dial
into
the BeanBox, we saw a number of properties inherited from
Component
. If you compile
DialBeanInfo
, package it with the
Dial
, and load the resulting JAR file into the
BeanBox, you’ll see that the Component
properties no longer appear in the properties sheet.
A PropertyDescriptor
can provide a lot of other
information about a property: it can provide the names of the
accessor methods (if you decide not to use the design patterns);
whether the property is constrained; and a class to use as a property
editor (if the standard property editors aren’t sufficient).
The Dial
defines its own event:
the DialEvent
. We’d like to tell development
tools about this event, so that we can build applications using it.
The process for telling the world about our event is similar to what
we did previously: we add a method to the
DialBeanInfo
class called
getEventSetDescriptors( )
, which returns an array of
EventSetDescriptor
s.
Events are described in terms of their listener interfaces, not in
terms of the event classes themselves, so our
getEventSetDescriptors( )
method creates a
descriptor for the DialListener
interface. We also
have to tell the world that we generate
PropertyChangeEvent
s, so we create a descriptor
for the PropertyChangeListener
. Here’s the
code to add to the DialBeanInfo
class:
public EventSetDescriptor[] getEventSetDescriptors( ) { try { EventSetDescriptor dial = new EventSetDescriptor( Dial.class, "dialAdjusted", DialListener.class, "dialAdjusted"); dial.setDisplayName("Dial Adjusted"); EventSetDescriptor changed = new EventSetDescriptor( Dial.class, "propertyChange", PropertyChangeListener.class, "propertyChange"); changed.setDisplayName("Bound property change"); return new EventSetDescriptor [] { dial, changed }; } catch (IntrospectionException e) { return null; } }
In this method, we create two EventSetDescriptor
objects: dial
and changed
. The
constructor for an EventSetDescriptor
takes four
arguments: the class that generates the event, the name of the event
(the name that will be displayed, by default, by a development tool),
the listener class, and the name of the method to which the event can
be delivered. (Other constructors let you deal with listener
interfaces that have several methods.) After creating these objects,
we call the setDisplayName( )
method to provide a more friendly name to be displayed by development
tools like the BeanBox. (This overrides the default name specified in
the constructor.)
Just as the property descriptors we supply hide the properties that
were discovered by reflection, the
EventSetDescriptor
s hide the other events that are
inherited from the base component classes. Therefore, when you
recompile DialBeanInfo
, package it in a JAR, and
load it into the BeanBox, you’ll no longer see mouse events,
action events, and all the other AWT/Swing events. You will see only
the two events that we have explicitly described: our own
DialEvent
and
PropertyChangeEvent
(displayed as “Dial
Adjusted” and “Bound property change”).
Once you have an EventSetDescriptor
, you can
provide other kinds of information about the event. In particular,
you can state that the event is unicast, which
means that it can only have one listener.
Some of the Beans that come with the
BeanBox are displayed on the palette with a cute icon. This makes
life more pleasant for everyone. To supply an icon for the
BeanInfo
object we have been
developing, we
have it implement the getIcon( )
method. You may
supply as many as four icons: they may be either 16 × 16 or 32
× 32, and either color or monochrome. Here’s the
getIcon( )
method for
DialBeanInfo
:
public class DialBeanInfo extends SimpleBeanInfo { ... public java.awt.Image getIcon(int iconKind) { if (iconKind == BeanInfo.ICON_COLOR_16x16) { return loadImage("DialIconColor16.gif"); } else if (iconKind == BeanInfo.ICON_COLOR_32x32) { return loadImage("DialIconColor32.gif"); } else if (iconKind == BeanInfo.ICON_MONO_16x16) { return loadImage("DialIconMono16.gif"); } else if (iconKind == BeanInfo.ICON_MONO_32x32) { return loadImage("DialIconMono32.gif"); } return null; }
This method is called with a constant indicating what kind of icon is
being requested; for example,
BeanInfo.ICON_COLOR_16x16
requests a 16 × 16
color image. If an appropriate icon is available, it loads the image
and returns an Image
object. If the icon
isn’t available, it returns null
. For
convenience, you can package the images in the same JAR file as the
Bean and its BeanInfo
class.
Though we haven’t used them here, you can also use a
BeanInfo
object to provide information about other
public methods of your Bean, array-valued properties, and other
features.
JavaBeans
also lets you provide a customizer for your Beans. Customizers are
objects that do advanced customization for a Bean as a whole; they
let you provide your own GUI for tweaking your Bean. (For example,
the Select
Bean uses a customizer rather than the
standard properties sheet.) We won’t show you how to write a
customizer; it’s not too difficult, but it’s beyond the
scope of this chapter. Suffice it to say that a customizer
must implement the java.beans.Customizer
interface, and should extend Component
(or
JComponent
), so that it can be displayed.
A property editor isn’t quite as fancy as a customizer.
Property editors are a way of giving the properties sheet additional
capabilities. For example, you would supply a property editor to let
you edit a property type that is specific to your Bean. You could
provide a property editor that would let you edit an object’s
price in dollars and cents. We’ve already seen a couple of
property editors: the editor used for Color
-valued
properties is fundamentally no different from a property editor you
might write yourself. In addition, the Molecule
Bean used a property editor to specify its
moleculeName
property.
Again, describing how to write a property editor is beyond the scope
of this chapter. Briefly, a property editor must implement the
PropertyEditor
interface; it usually does so by
extending the PropertyEditorSupport
class, which
provides
default
implementations for most
of the methods.
Get Learning Java now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.