Sun Java Solaris Communities My SDN Account Join SDN
 
Tutorials & Code Camps

JavaBeans 101, Part II

 
Online Training Index

101, Part II:

Introduction | Page 2 | Page 3 | Page 4



Adding Properties

The previous lesson illustrated how to add simple properties to a Bean, plus compile the Bean and test it in the BeanBox. In this part, we'll see how to implement other types of properties in a Bean, including:

  • Bound properties
  • Constrained properties

These more complex types of properties require that Beans communicate with each other. Recall that Beans use events to communicate with other Beans. A source Bean sends or fires events and a listener Bean, after it registers its interest in the event with the source Bean, receives or handles events.

Bound Properties

A bound property gives a JavaBean the capability of notifying another JavaBean when there is a change to the value of that property. The JavaBean that is notified can then react to the change. For example, you might have a button in one JavaBean that, when pressed, causes another Bean to display some information.

Because other Beans are notified when there is a change to a Bean's bound property, there has to be a means for Beans to communicate. Beans communicate using events. A change to a bound property causes an event to be fired. These events are referred to as property change events.

When you set up a bound property, you also set up other Beans as interested listeners to changes in the bound property. Then, whenever the value of the bound property of the first JavaBean changes, notification is sent to those Beans that have registered as interested listeners.

Let's look at the Bean MyButton.java first, which uses bound properties. MyButton includes code to notify other interested Beans when its properties change. Once we're done with MyButton, we'll set up a listener Bean that responds to changes in MyButton's properties.

Setting Up Bound Properties

These are the basic steps for setting up bound properties in a Bean:

  1. Import the java.beans package to gain access to certain convenience classes defined by the package. MyButton imports the package as follows:
    import java.beans.*;


  2. Instantiate the java.beans.PropertyChangeSupport class.
    private PropertyChangeSupport changes = new 			
    		PropertyChangeSupport(this);
    

    MyButton creates a new object, called changes, which instantiates the PropertyChangeSupport class. The changes variable holds a collection of listener objects that will be notified about property changes to the bound property. It defines two supporting methods, addPropertyChangeListener and removePropertyChangeListener, which provide a public interface that allows interested listeners to register themselves with MyButton.


  3. Implement the methods defined by the PropertyChangeSupport class.

    The PropertyChangeSupport class includes methods to add and remove listener objects, specifically PropertyChangeListener objects. The addPropertyChangeListener method adds a new listener to the list, while the removePropertyChangeListener method removes a listener from the list. The PropertyChangeSupport class also includes a third method&-- firePropertyChange --to fire PropertyChangeEvent objects to these interested listeners. MyButton includes the following code to implement the add and remove listener methods.

    Note that the parameter l refers to the property change listener Bean that registers or removes its interest as a property change listener.
    
    public void addPropertyChangeListener(
                PropertyChangeListener l) {
    	changes.addPropertyChangeListener(l);
    }public void removePropertyChangeListener(
                     PropertyChangeListener l) {
        changes.removePropertyChangeListener(l);
    }
    



  4. Modify the Bean's setter methods for the bound properties.

    For those properties that you want to be bound properties, modify the Bean's setter methods to include code to fire a property change event when the property value changes. MyButton calls the firePropertyChange method within each method that sets a new property value. For example, when an application or user changes the button's font, this action executes the MyButton.setFont method. Because the firePropertyChange method requires both the old and new value of the changed property, the setFont method first captures the old value by calling the getFont method. It sets the new, changed value, then calls changes.firePropertyChange to notify interested listeners. It passes three parameters to the method: the name of the changed property, the old value of the property, and the property's new value.

    public void setFont(Font f) {
    	Font old = getFont();
    	super.setFont(f);
    	sizeToFit();
    	changes.firePropertyChange(
    	   "font", old, f);
    }

What happens under the covers with the firePropertyChange method? The method bundles its three parameters into a PropertyChangeEvent object. It then calls the method propertyChange , passing it the PropertyChangeEvent object, on each registered listener. Keep in mind that propertyChange treats the old and new values of the property as Object values. This is important if your property values are primitive types. If they are, you must use the object wrapper version for the specific type, such as java.lang.Integer for an int primitive type, before calling firePropertyChange.

Keep in mind that with bound properties, the property value changes first, then the property change event is fired.

Setting Up a Property Listener

Now we set up a listener Bean that receives and perhaps responds to property change events in MyButton.

  1. Implement the PropertyChangeListener interface. There is one method in this interface, the propertyChange method:

    public abstract void propertyChange(
                PropertyChangeEvent evt)
     
    Beans that fire property change events call the propertyChange method to notify property change listeners of changed properties.

  2. In the listener Bean's implementation of the propertyChange method define the actions you want to take as a result of the property change.

    You make the connection between the source Bean—the Bean that fires the property change event—and the listener Bean that reacts to the changed property in the BeanBox (or in another builder tool). You can also manually make this connection by writing a special adapter class.

    For example, the listener Bean MyChangeReporter includes a method reportChange whose parameter is a PropertyChangeEvent object. The method extracts the property name and new value from the passed object, formats its text window, and displays the information.

    public void reportChange(PropertyChangeEvent evt) {
     String text = evt.getPropertyName() 
      + " := " + evt.getNewValue();
     int width = getSize().width - 10;
     Font f = getFont();
      if (f != null) {
      // Trim the text to fit.
         FontMetrics fm = getFontMetrics(f);
          while (fm.stringWidth(text) > width) {
    	   text = text.substring(
    	              0, text.length()-1);
      }
    }				 
      setText(text);
    }

    You can also make the connection between the source and listener Beans in the code itself. To do this, write an adapter class to catch the property change event.

    You set up the adapter class to call the correct method within the listener object.

  3. Set up the listener Bean to call the listener registration method on the source Bean. For example, our listener invokes the following method on MyButton:

    MyButton.addPropertyChangeListener(
               aPropertyChangeListener);
    

Connecting the Beans in the BeanBox

The BeanBox recognizes when a Bean correctly defines a bound property—that it can broadcast property change events—and the BeanBox includes a propertyChange interface item for that Bean. The propertyChange interface item appears in the Events menu for the selected Bean that defines a bound property.

  1. Select Events in the Bean Edit menu.

    Once you select propertyChange, connect the source Bean to the listener Bean.

  2. Extending the rubber band line from the source Bean to the listener Bean.

    An EventTargetDialog appears.

  3. Select the appropriate listener method (in our example, this is the reportChange method).

Beneath the surface, the BeanBox generates an event hookup adapter when you connect the propertyChange event from the source Bean containing the bound property to the listener Bean. The event hookup adapter implements the PropertyChangeListener interface and generates a propertyChange method implementation that calls the listener Bean's reportChange method. Because the BeanBox has generated the event hookup adapter class, and that class does the work of hooking up the source Bean to the listener Bean, your listener Bean does not have to implement the PropertyChangeListener interface itself.

Constrained Properties

Constrained properties are bound properties that go one step further. With a constrained property, an outside object--either a listener Bean or the source Bean itself--can veto a property change. The JavaBeans API provides an event mechanism for handling constrained properties that is similar to the one used for bound properties.

To implement a constrained property, you must have:

  • A source Bean that defines a constrained property.

  • Listener objects that implement the VetoableChangeListener interface.

  • A PropertyChangeEvent object that contains the property name, its old value, and its new value. (Note that this is the same object used by bound properties.)

Setting Up Constrained Properties

A source Bean sets up constrained properties as follows:

  • Implements a mechanism to allow listener objects who have implemented the VetoableChangeListener interface to register and unregister their interest in receiving property change notification. Rather than receive notification that a property has already changed, these listeners receive notification that a change to a property has been proposed.

    The Bean accomplishes this using the VetoableChangeSupport utility class, which it either inherits or instantiates. The utility class includes methods to add and remove VetoableChangeListener objects to a listener list, plus a method that fires property change events and also catches and responds appropriately to veto responses.

  • fireVetoableChange(<property name>, 
           <old value>, <new value>);
             addVetoableChangeListener(listener);
          removeVetoableChangeListener(listener);
    


  • Fires proposed property change events to interested listeners. Because these changes may be vetoed, the source Bean fires the event before the change takes place. Source Beans fire the PropertyChangeEvent by calling the listener's vetoableChange method.

  • Provides a means for listener objects to keep the original property value should any one of the listeners veto the change. To do this, the source Bean issues the vetoableChange call to all listeners a second time, and passes the listeners the original property value.

Walking Through an Example

The JellyBean JavaBean implements a constrained property, which it calls priceInCents. The Bean begins by instantiating two new objects: a VetoableChangeSupport object vetos to maintain the list of vetoable listeners and a PropertyChangeSupport object changes to hold the list of property change listeners. It does this as follows:

private VetoableChangeSupport vetos = 
         new VetoableChangeSupport(this);

private PropertyChangeSupport changes = new PropertyChangeSupport(this);

Then, JellyBean implements the VetoableChangeSupport interface methods to register and unregister constrained property change listeners:

public void addVetoableChangeListener(
              VetoableChangeListener l) {
	vetos.addVetoableChangeListener(l);
}
public void removeVetoableChangeListener(
                VetoableChangeListener l) {
     vetos.removeVetoableChangeListener(l);
}

The above add and remove listener methods apply to all constrained properties of the Bean. It's possible to specify that the listener be attuned only to a specific constrained property within the Bean. If so, these two methods can include an additional String parameter containing the property name. The property name appears first in the parameter list, followed by the listener object. For example, to add a listener Bean for a specific property:

public void addVetoableChangeListener(
                      String propertyName,
		      VetoableChangeListener
		                 listener);
You can also register and unregister vetoable change listeners on a per property basis by specifying the particular property name, as follows:

void add<PropertyName>Listener(
              VetoableChangeListener p);
void remove<PropertyName>Listener(
               VetoableChangeListener p);

JellyBean also defines a method to get the current price and another method to set a new price. In the method to set the new price, JellyBean invokes the method fireVetoableChange to notify registered listeners of the property change.

public void setPriceInCents(int newPriceInCents) 
         throws PropertyVetoException {
	vetos.fireVetoableChange("priceInCents", 
	new Integer(oldPriceInCents),
	new Integer(newPriceInCents));
	//if vetoed (PropertyVetoException thrown) 
	// don't catch 
// exception here so routine exits. //if not vetoed, make the property // change ourPriceInCents = newPriceInCents; changes.firePropertyChange( "priceInCents", new Integer(oldPriceInCents), new Integer(newPriceInCents)); }

When the Bean executes the fireVetoableChange method, it notifies interested listeners of the proposed change to the priceInCents property, and informs them of the current price and proposed new price. If the method executes successfully--that is, no exception is thrown--it means that no listener has vetoed the change. The setPriceInCents method then executes the firePropertyChange method to fire a property change event to notify listeners that the property has now changed.

However, if the fireVetoableChange method causes a PropertyVetoException to be thrown, that indicates that a listener has vetoed the proposed change. The property change does not go into effect. The setPriceInCents method does not catch the exception, thus exiting the method and allowing the exception to be handled at a higher level.

Setting Up Constrained Property Listeners

Constrained property listener objects implement the VetoableChangeListener interface, which includes the vetoableChange method. When the source Bean fires a constrained property change event, it calls vetoableChange method on each registered listener. The listener uses this method to receive the change event and to accept or reject proposed changes. If they reject a proposed change, they throw a PropertyVetoException.

Similar to bound properties, your listener Bean can implement the VetoableChangeListener interface and vetoableChange method itself, or you can use the BeanBox to generate an event hookup adapter class that provides this implementation for your Bean. If you have the BeanBox generate the adapter class, then the listener Bean does not need to implement the VetoableChangeListener interface. You only need to implement the vetoableChange method, which the Voter Java Bean does as follows:

public void vetoableChange(
                        PropertyChangeEvent x)
	         hrows PropertyVetoException {
	if (vetoAll) {
throw new PropertyVetoException( "NO!", x); } }

Constrained Properties in the BeanBox

The BeanBox handles constrained properties very much like bound properties.

  1. Drop the Bean that defines the constrained property into the BeanBox.

    Our example uses the JellyBean.

  2. Drop in a Bean that listens for changes to that property and that can potentially veto such a change.

    In our example, we use the Voter Bean, which vetoes changes to that property.

  3. Choose the vetoableChange event for the JellyBean. Select the JellyBean and, from the Edit menu, select the Events > vetoableChange > vetoableChange menu item.

  4. lesson3-3

  5. Connect the event to the listener Bean and select its vetoableChange method.

  6. Connect the rubber band to the Voter Bean instance. When the EventTargetDialog panel appears, select the vetoableChange method.

    The BeanBox generates the event hookup adapter.


  7. Test the constrained property.

    Select the JellyBean and, in the Property sheet, try changing its priceInCents property. In the terminal window from which you started the BeanBox, you see a message indicating that an exception was thrown and the change is not allowed.

For more practice setting up properties, see the next section, Manipulating Properties.

Coffecup Logo

Introduction | Page 2 | Page 3 | Page 4