A java.awt Example

The components provided by the java.awt package expose properties using the techniques just described. These user interface elements can be manipulated in the same way as any other object that conforms to the JavaBeans architecture. So let’s create an applet that illustrates the concepts of bound and constrained properties.

Our applet will have a data member named primaryLabel which is an instance of class NumberLabel. A NumberLabel is a subclass of java.awt.Label that has a bound and constrained property named Value. The Value property is of type int, and the label always displays the contents of this property. We also have another instance of the NumberLabel class, named mirrorLabel, that will bind itself to the Value property of primaryLabel, and will keep its display consistent with it. There is one button to decrement the Value property of primaryLabel, and one to increment it; these button instances will be named decButton and incButton, respectively. Lastly, we will create an instance of class Constrainer, named cnstr, that constrains the Value property of primaryLabel to be between 10 and 20. A diagram of the interactions that will take place between the objects is shown in Figure 4.3.

Constraining a property with a VetoableChangeEvent

Figure 4-3. Constraining a property with a VetoableChangeEvent

Let’s take a look at the code for the NumberLabel class first:

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import BeansBook.util.*;

// the NumberLabel class
class NumberLabel extends Label
        implements PropertyChangeListener
{
   // the support object for bound listeners
   protected PropertyChangeSupport boundSupport;

   // the support object for constrained listeners
   protected VetoableChangeSupport vetoSupport;

   // the implementation of the Value property
   protected int theValue = 15;

   // constructor
   public NumberLabel(String text)
   {
      // call the super class
      super(text);

      // construct the support objects
      boundSupport = new PropertyChangeSupport(this);
      vetoSupport = new VetoableChangeSupport(this);
   }

   // add a bound property listener
   public void addPropertyChangeListener(PropertyChangeListener l)
   {

      // defer to the support object
      boundSupport.addPropertyChangeListener(l);
   }

   // remove a bound property listener
   public void removePropertyChangeListener(PropertyChangeListener l)
   {
      // defer to the support object
      boundSupport.removePropertyChangeListener(l);
   }

   // add a constrained property listener
   public void addVetoableChangeListener(VetoableChangeListener l)
   {
      // defer to the support object
      vetoSupport.addVetoableChangeListener(l);
   }

   // remove a constrained property listener
   public void removeVetoableChangeListener(VetoableChangeListener l)
   {
      // defer to the support object
      vetoSupport.removeVetoableChangeListener(l);
   }

   // the get method for the Value property
   public int getValue()
   {
      return theValue;
   }

   // the set method for the Value property
   public void setValue(int newValue)
           throws PropertyVetoException
   {
      // fire the change to any constrained listeners
      vetoSupport.fireVetoableChange("Value", new Integer(theValue),
                                       new Integer(newValue));

      // no veto, so save the old value and then change it
      Integer oldVal = new Integer(theValue);
      theValue = newValue;
      setText(String.valueOf(theValue));
      repaint();

      // fire the change to any bound listeners
      boundSupport.firePropertyChange("Value", oldVal,
                                  new Integer(theValue));
   }

   // handle property change events from others
   public void propertyChange(PropertyChangeEvent evt)
   {
      // only interested in changes to Value properties
      if (evt.getPropertyName().equals("Value"))
      {
         // just change our own property
         Integer val = (Integer)evt.getNewValue();

         try
         {
            setValue(val.intValue());
         }
         catch (PropertyVetoException e)
         {
         }
      }
   }
}

The NumberLabel class extends java.awt.Label, a class for creating a static text field. NumberLabel implements the java.beans.PropertyChangeListener interface because we will use one instance of the NumberLabel class to monitor the Value property of another. Since NumberLabel supports a property named Value that is both bound and constrained, we use an instance of java.beans.PropertyChangeSupport as well as an instance of VetoableChangeSupport. The implementation of the methods addPropertyChangeListener(), removePropertyChangeListener(), addVetoableChangeListener(), and removeVetoable-ChangeListener() all defer to their respective support objects.

Since the Value property is read/write, we implement a getValue() method as well as a setValue() method. The latter declares that it can throw the java.beans.PropertyVetoException because the Value property is constrained as well as bound. The setValue() method first instructs the support object to fire a VetoableChangeEvent for the Value property by calling the fireVetoableChange () method. If the event is not vetoed, SetValue() stores the new value, updates the label’s text, and repaints. Finally, the other support object is instructed to fire a PropertyChangeEvent by invoking the firePropertyChange() method.

The NumberLabel class also implements the propertyChange() method to handle property changes from other objects. In this example, we know that a NumberLabel listens for property changes only from the other instance of NumberLabel. We can therefore take a shortcut and simply have the NumberLabel set its own Value property based on the new value of the PropertyChangeEvent that it received. This is guaranteed to keep our two labels in sync. Of course, we first check to make sure that it was the Value property that changed, and that we haven’t received a notification about some other property.

Now let’s take a look at the code for the Constrainer class:

class Constrainer implements VetoableChangeListener
{
   // handle the vetoable change event
   public void vetoableChange(PropertyChangeEvent evt)
           throws PropertyVetoException
   {
      // we constrain the value to between 10 and 20
      Integer val = (Integer)evt.getNewValue();
      if (val.intValue() < 10 || val.intValue() > 20)
      {
         throw new PropertyVetoException("Bad Value", evt);
      }
   }
}

The Constrainer class vetoes any property changes that attempt to change the Value property of a NumberLabel object to a value below 10 or above 20. The vetoableChange() method examines the new value and rejects it if it does not meet the criteria.

Now we can create an applet that ties all this together. The applet uses an instance of the class BeansBook.util.GenericButtonAdapter to route action events that occur when the two buttons are pressed. Here’s what the code looks like:

public class ExampleApplet4 extends Applet
{
   // the button adapter for handling button action events
   protected GenericButtonAdapter adapter;

   // the decrement button
   protected Button decButton = new Button("<<");

   // the increment button
   protected Button incButton = new Button(">>");

   // the constrained label
   protected NumberLabel primaryLabel = new NumberLabel("*****");

   // the label that mirrors the primary label
   protected NumberLabel mirrorLabel = new NumberLabel("*****");

   // the constraining object
   protected Constrainer cnstr = new Constrainer();

   // the constructor
   public ExampleApplet4()
   {
   }

   // the applet init
   public void init()
   {
      // add the user interface elements
      add(decButton);
      add(incButton);
      add(primaryLabel);
      add(mirrorLabel);

      // register the constrainer with the primary label
      primaryLabel.addVetoableChangeListener(cnstr);

      // register the mirroring label with the primary label
      primaryLabel.addPropertyChangeListener(mirrorLabel);

      // setup the button adapter
      try
      {
         adapter = new GenericButtonAdapter(this);
         adapter.registerActionEventHandler(decButton,
                                            "handleDecrement");
         adapter.registerActionEventHandler(incButton,
                                            "handleIncrement");
      }
      catch (NoSuchMethodException e)
      {
      }
      catch (ClassNotFoundException e)
      {
      }

      // start the labels at different values
      try
      {
         primaryLabel.setValue(15);
         mirrorLabel.setValue(5);
      }
      catch (PropertyVetoException e)
      {
      }
   }

   // handle the decrement button push
   public void handleDecrement(ActionEvent evt)
   {
      // get the current value and subtract 1
      int val = primaryLabel.getValue() - 1;

      // try to set the new value
      try
      {
         primaryLabel.setValue(val);
      }
      catch (PropertyVetoException e)
      {
      }
   }

   // handle the increment button push
   public void handleIncrement(ActionEvent evt)
   {
      // get the current value and add 1
      int val = primaryLabel.getValue() + 1;

      // try to set the new value
      try
      {
         primaryLabel.setValue(val);
      }
      catch (PropertyVetoException e)
      {
      }
   }
}

When the decrement button is pressed, the handleDecrement() method gets called. The Value property of the primary NumberLabel object is retrieved and its value is decremented and stored locally. Then handleDecrement() attempts to set the Value property to this new value. The PropertyVetoException must be caught in case the change is vetoed. When the increment button is pressed, the handleIncrement() method does the same thing, except that the value is incremented instead.

When the applet’s init() method is called, the buttons and labels are added first. Next, the instance of the Constrainer class is registered as a listener for VetoableChange events from the primary NumberLabel object, and the other NumberLabel object (mirrorLabel) is registered to listen for PropertyChange events. The button adapter is set up to route action events, and the two labels are initialized with two different values. Figure 4.4 shows what the applet looks like when it first starts up. The components are labeled with the variable names to show where they appear on the applet.

An applet using a constrained property: initial state

Figure 4-4. An applet using a constrained property: initial state

When the decrement (<<) and increment (>>) buttons are pressed, both labels update together. Press the buttons and watch how these two labels stay in sync by setting the Value property of one of them, while the other monitors those changes and updates its own Value property in response. Figure 4.5 shows an example.

An applet using a constrained property

Figure 4-5. An applet using a constrained property

If you continue to press the increment button, the labels will eventually have values of 20. If you try to increment again, the Constrainer object vetoes the change. The result is that the Value properties of both labels remain at 20. The same is true if you decrement the Value properties to 10. If you continue decrementing, the Constrainer object again vetoes the change and the Value properties remain at 10.

Get Developing Java Beans 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.