4.3. Bound Properties
In
the previous example, property changes can take place when one of the
setStocks() methods are invoked. This results in
the state of the WatchList being changed. What
would happen if two objects were using the
WatchList and one of them changed the
Stocks property? It's possible that users
of the WatchList will want to know if its
properties are changed.
A Bean component can provide change notifications for its properties.
These notifications take the form of events, and they conform to the
event model described in the earlier chapters. Properties that
support change notifications are known as bound
properties, because other objects can bind themselves to changes in
their value.
4.3.1. Non-Specific Property Binding
An object can bind itself to non-specific property changes. In this
case the source object sends notifications to the bound object
whenever the value of one of its bound properties has changed. The
notification takes the form of a
java.beans.PropertyChangeEvent. The public methods
for this class are shown below, without their implementations:
public class java.beans.PropertyChangeEvent extends java.util.EventObject { // contructor PropertyChangeEvent(Object source, // the source of the event String propertyName, // the property name Object oldValue, // the old value Object newValue); // the new value // get the new value of the property public Object getNewValue(); // get the old value of the property public Object getOldValue(); // get the event propagation id public Object getPropagationId(); // get the name of the property that changed public String getPropertyName(); // set the event propagation id public void setPropagationId(); }
The
java.beans.PropertyChangeEvent
class treats old and new values as
instances of class Object. If the property that
changed uses one of the primitive data types like
int or float, you'll have
to use the object version of the type here, such as
java.lang.Integer or
java.lang.Float. It is possible that when the
event is fired the old or new values are not known, or that multiple
values have changed. In this case null can be
returned from the getOldValue() or
getNewValue() method. This is also true of the
property name. If multiple properties have changed, the
getPropertyName() method may return
null. The PropertyChangeEvent
also contains something called a propagation ID.
This is reserved for future use. If you receive a
PropertyChangeEvent and then in turn generate and
fire another PropertyChangeEvent, you must
propagate the propagation ID as well.
A PropertyChangeEvent
for a bound property should only be fired after its internal state
has been updated. This means that the event signifies a change that
has already taken place. If an object supports bound properties, it
will provide the following methods for registering and unregistering
the associated event listeners:
public void addPropertyChangeListener(PropertyChangeListener p); public void removePropertyChangeListener(PropertyChangelistener p);
The
java.beans.PropertyChangeListener
interface extends the base Java class
java.util.EventListener. This interface supports a
single method called
propertyChange(),
which takes only one parameter as an argument, of type
java.beans.PropertyChangeEvent. If an object wants
to receive notifications of bound property changes, it would
implement the java.beans.PropertyChangeListener
interface and register itself with the source object by calling its
addPropertyChangeListener()
method. A diagram of this interaction is
shown in Figure 4.1.
Figure 4.1. Notifying a listener that a property has changed
Let's go back to our Temperature class.
Earlier we added a read-only property to this class named
CurrentTemperature. If we wanted to implement
this as a bound property, we would implement the
addPropertyChangeListener() and
removePropertyChangeListener() methods. Then
whenever CurrentTemperature changed, we would
fire a PropertyChangeEvent to all listeners.
Originally we designed the Temperature class to
fire a TempChangedEvent to listeners that
implement the TempChangeListener interface. For
now we'll remove the code related to the firing of
TempChangeEvent events. Now that we understand
properties, let's modify the Temperature
class to fire the PropertyChangeEvent instead.
Because the object does not keep track of the previous temperature,
the oldValue parameter of the
PropertyChangeEvent constructor is set to
null. The code would now look like this:
Code View: Scroll / Show All
package BeansBook.Simulator; import java.beans.*; import java.util.Vector; public class Temperature { // the current temperature in Celsius protected double currentTemp = 22.2; // the collection of objects listening for property changes protected Vector propChangeListeners = new Vector(); // the constructors public Temperature(double startingTemp) { this(); currentTemp = startingTemp; } public Temperature() { } // the get method for property CurrentTemperature public double getCurrentTemperature() { return currentTemp; } // add a property change listener public synchronized void addPropertyChangeListener(PropertyChangeListener l) { // add a listener if it is not already registered if (!propChangeListeners.contains(l)) { propChangeListeners.addElement(l); } } // remove a property change listener public synchronized void removePropertyChangeListener(PropertyChangeListener l) { // remove it if it is registered if (propChangeListeners.contains(l)) { propChangeListeners.removeElement(l); } } // notify listening objects of CurrentTemperature property changes protected void notifyTemperatureChange() { // create the event object PropertyChangeEvent evt = new PropertyChangeEvent(this, "CurrentTemperature", null, new Double(currentTemp)); // make a copy of the listener object vector so that it cannot // be changed while we are firing events Vector v; synchronized(this) { v = (Vector) propChangeListeners.clone(); } // fire the event to all listeners int cnt = v.size(); for (int i = 0; i < cnt; i++) { PropertyChangeListener client = (PropertyChangeListener)v.elementAt(i); client.propertyChange(evt); } } }
With this change in place, we have to change the implementation of
the Thermometer class as well. Instead of
implementing the TempChangeListener interface it
will implement the PropertyChangeListener
interface. We remove the
tempChanged() method and replace it with
a propertyChange() method. For the sake of
simplicity, let's revert to the case where the
Thermometer works with a single
Temperature object named
theTemperature instead of the two that we worked
with previously. The code for the changed constructor and
propertyChange() method is shown below:
Code View: Scroll / Show All
// constructor Thermometer(Temperature temperature) { theTemperature = temperature; // register for property change events theTemperature.addPropertyChangeListener(this); } // handle the property change events public void propertyChange(PropertyChangeEvent evt) { // determine if the CurrentTemperature property of the temperature // object is the one that changed if (evt.getSource() == theTemperature && evt.getPropertyName() == "CurrentTemperature") { Temperature t = (Temperature)evt.getSource(); // get the new value object Object o = evt.getNewValue(); double newTemperature; if (o == null) { // go back to the object to get the temperature newTemperature = t.getCurrentTemperature(); } else { // get the new temperature value newTemperature = ((Double)o).doubleValue(); } } }
The propertyChange() method first determines if
the source of the event was its instance of the
Temperature class, and if the property that
changed is called CurrentTemperature. If these
two things are true, the source is cast to type
Temperature and the new value object is retrieved
from the event object. Since the new value object can be
null, there is a check for that condition. If it
is null, it goes back to the source to get the
value of the CurrentTemperature property. If it
isn't null, the value is retrieved directly
from the new value object.
A support class called
java.beans.PropertyChangeSupport
can be used to fire property change
events to the registered listeners. You can either inherit from this
class, or directly use an instance of it.
PropertyChangeSupport implements the
java.io.Serializable
interface, which we discuss in Chapter 5. The method signatures of this class are as
follows:
public class java.beans.PropertyChangeSupport implements java.io.Serializable { // construct the object public PropertyChangeSupport(Object source); // add a property change listener public synchronized void addPropertyChangeListener(PropertyChangeListener l); // fire a property change event to any listeners public void firePropertyChange(String propertyName, Object oldValue, Object newValue); // remove a property change listener public synchronized void removePropertyChangeListener(PropertyChangeListener l); }
We could reimplement the Temperature class by
inheriting from the PropertyChangeSupport class.
This would eliminate the code that deals with the property change
events. Note that the version of the constructor that takes no
parameters calls the superclass constructor to make itself the event
source. Although we aren't going to keep the
Temperature class like this, here's what the
code would look like if we did:
Code View: Scroll / Show All
package BeansBook.Simulator; import java.beans.*; import java.util.Vector; public class Temperature extends PropertyChangeSupport { // the current temperature in Celsius protected double currentTemp = 22.2; // the constructors public Temperature(double startingTemp) { this(); currentTemp = startingTemp; } public Temperature() { super(this); } // the get method for property CurrentTemperature public double getCurrentTemperature() { return currentTemp; } // notify listening objects of CurrentTemperature property changes protected void notifyTemperatureChange() { // fire the event firePropertyChange("CurrentTemperature", null, new Double(currentTemp)); } }
కామెంట్లు లేవు:
కామెంట్ను పోస్ట్ చేయండి