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.
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.
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.
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.