Creating and Using Properties

   

Previously in Figure 29.1, notice that the TextDisplayer Bean displayed itself with a white background and black text. It did so because that's how you set its properties in the default constructor. If you had set the FontColor property to red, it would have displayed the text in red. If the properties of a component cannot be changed by another Bean (or any other class that uses it), the usefulness of the Bean is reduced, as well as the reusability. For example, if you used the TextDisplayer Bean in an accounting package, you would need to change the Bean's FontColor property to red to indicate a negative value. So how do you let other Beans know that they can set (or read) this property? If you're coding from scratch, you can look at the documentation for the Bean. But what if you're in an application builder? Luckily, there's a way to do this without incurring any extra coding on your part. You'll see how that works a little later.

Two types of properties are supported by JavaBeans: single-value and indexed. In addition, properties can also be bound or constrained. A single-value property is a property for which there is only one assigned value at a time. As the name suggests, an indexed property has several values, each of which has a unique index. If a property is bound, it means that some other Bean is dependent on that property. In the continuing example, the TextReader Bean's InputText property is bound to the TextDisplayer Bean; the TextReader must notify the TextDisplayer whenever its InputText field changes. A property is constrained if it must check with other components before it can change.

Note

Constrained properties cannot change arbitrarily. One or more components might not allow the updated value.


Single-Value Properties

All properties are accessed by calling methods on the owning Bean instance. Readable properties have a getter method used to read the value of the property. Writable properties have a setter method used to change the value of a property. These methods are not constrained to returning the value of a field declared within a Bean; they can also perform calculations and return some other value. All the properties the Beans have in the examples here are single-value.

At this point, you're ready to start talking about introspection. The method by which other components learn of your Bean's properties depends on a few things. In general, however, this process is called introspection . In fact, the class java.beans.Introspector is the class that provides this information for other components. The Introspector class traverses the class hierarchy of a particular Bean. If it finds explicit information provided by the Bean, it uses that. However, it uses design patterns to implicitly extract information from those Beans that do not provide information. Note that this is what happens to your Beans in the example. Specific design rules should be applied when defining accessor methods so that the Introspector class can do its job. If you choose to use other names , you can still expose a Bean's properties, but it requires you to supply a BeanInfo class. For more about what a BeanInfo class is, see the "Introspection: Creating and Using BeanInfo Classes" section later in this chapter. Here are the design patterns you should use:

 public void set<PropertyName>( <PropertyType> value ); public <PropertyType> get<PropertyName>(); public boolean is<PropertyName>(); 

Note that the last pattern is an alternative getter method for Boolean properties only. setter methods are allowed to throw exceptions if they so choose. The accessor methods for the TextDisplayer Bean are shown in Listing 29.3. Notice that all the accessor methods have been declared as synchronized. Even though nothing serious could happen in this Bean, you should always assume that your Beans are running in multithreaded environments. Using synchronized accessor methods helps prevent race conditions from forming. TextReader requires a similar set of accessor methods for its properties.

Listing 29.3 TextDisplayer.java ” The Accessor Methods for the Properties in the TextDisplayer Bean
 public synchronized String getOutputText() {    return( OutputText ); } public synchronized void setOutputText( String text ) {    OutputText = text;    // force a paint    Graphics g = getGraphics();    if ( g != null ) {      update(g);    } } public synchronized Color getBGColor() {    return( BGColor ); } public synchronized void setBGColor( Color color ) {    BGColor = color;    setBackground( BGColor );   // set the Canvas's background color.    repaint(); } public synchronized Font getTextFont() {    return( TextFont ); } public synchronized void setTextFont( Font font ) {    TextFont = font;    setFont( TextFont );        // set the Canvas's font. } public synchronized Color getFontColor() {    return( FontColor ); } public synchronized void setFontColor( Color color ) {    FontColor = color;    setForeground( FontColor ); // set the Canvas's foreground color.    repaint(); } 

Figure 29.2 shows you what the property sheet of Sun's BeanBox shows for your TextDisplayer Bean. Notice that you can see the properties of the parent class, too. Your Bean inherits from java.awt.Canvas, which inherits from java.awt.Component, which inherits from java.lang.Object. The additional properties that you see are from the java.awt.Component class. This illustrates the principal drawback of using the automatic JavaBeans introspection methods. In your own Beans, this might be the motivation for providing a BeanInfo class. Again, more on that is in the "Introspection: Creating and Using BeanInfo Classes" section later in this chapter.

Figure 29.2. The PropertySheet of Sun's BeanBox showing the Bean's exposed properties. Notice the properties of the parent class.

graphics/29fig02.gif

Indexed Properties

An indexed property takes on multiple values simultaneously and can be treated as an array. You read or write the values associated with an indexed property by specifying an integer index or working with the entire array at once. The design patterns for indexed properties are as follows :

 public <PropertyType> get<PropertyName>( int index ); public void set<PropertyName>( int index, <PropertyType> value ); public <PropertyType>[] get<PropertyName>(); public void set<PropertyName>( <PropertyType>[] value ); 

To illustrate , assume there is a Meal property that consists of an array of Course s:

 public Course getMeal( int course ); public void setMeal( int course, Course dish ); public Course[] getMeal(); public void setMeal( Course[] dishes ); 

Bound Properties

As the programmer, you can decide which of your Bean's properties can be bound to other components. To provide bound properties in your Beans, you must define the following methods:

 public void addPropertyChangeListener( PropertyChangeListener l ); public void removePropertyChangeListener( PropertyChangeListener l ); 

To provide this functionality on a per-property basis, the following design pattern should be used:

 public void add<PropertyName>Listener( PropertyChangeListener l ); public void remove<PropertyName>Listener( PropertyChangeListener l ); 

Beans wanting to bind to other components'properties should implement the PropertyChangeListener interface, which consists of the following method:

 public void propertyChange( PropertyChangeEvent evt ); 

Whenever a bound property in a Bean is updated, it must call the propertyChange() method in all the components that have registered with it. The class java.beans.PropertyChangeSupport is provided to help you with this process. The code in Listing 29.4 shows you what is required in the TextReader Bean to allow its InputText property to be bound. The setInputText method shown here was included previously in Listing 29.2, but the call to firePropertyChange can now be uncommented.

Listing 29.4 TextReader.java ” Code Required to Make the InputText Property of the TextReader Bean a Bound Property
 // setter method for the InputText property.    public synchronized void setInputText( String newText ) {       String oldText = InputText;       InputText = newText;       setText( InputText );       changeAgent.firePropertyChange( "inputText", new String( oldText ),                                       new String( newText ) );    }    // these two methods allow this Bean to have bound properties.    public void addPropertyChangeListener( PropertyChangeListener l ) {       changeAgent.addPropertyChangeListener( l );    }    public void removePropertyChangeListener( PropertyChangeListener l ) {       changeAgent.removePropertyChangeListener( l );    }    protected PropertyChangeSupport changeAgent =      new PropertyChangeSupport( this ); 

After you make the Listing 29.4 changes to TextReader, you can implement the propertyChange method of TextDisplayer that was declared previously in Listing 29.1. You should implement this method to respond to changes in the TextReader InputText property as follows:

 // implement the PropertyChangeListener interface    public void propertyChange( PropertyChangeEvent evt ) {      // only interested in the inputText property of TextReader      if ( evt.getPropertyName().equals("inputText") ) {        // display the new value of the TextReader property        setOutputText( (String)evt.getNewValue() );      }    } 

Notice that this method refers to the property name as "inputText" even though we declared the TextReader property using the identifier InputText. In general, the rules used to extract property names based on getter and setter method declarations produce names that begin with a lowercase letter. The exception to this rule occurs when an accessor method contains a property name that begins with two or more uppercase letters . For example, getToolKitName corresponds to a property named toolKitName, but getGUIToolKitName corresponds to a property named GUIToolKitName.

Troubleshooting Tip

If you have trouble configuring a bound property, see "Bean Events Are Not Propagated" in the "Troubleshooting" section at the end of this chapter.


Constrained Properties

The process for providing constrained properties in your code is also fairly straightforward. You must define the following methods in your Bean:

 public void addVetoableChangeListener( VetoableChangeListener l ); public void removeVetoableChangeListener( VetoableChangeListener l ); 

Just as with bound properties, you can make individual properties constrained using the following design pattern:

 public void add<PropertyName>Listener( VetoableChangeListener l ); public void remove<PropertyName>Listener( VetoableChangeListener l ); 

Beans intended to constrain other components'properties should implement the VetoableChangeListener interface, which consists of the following method:

 public void vetoableChange( PropertyChangeEvent evt ) throws PropertyVetoException; 

Whenever a constrained property in a Bean is updated, it must call the vetoableChange() method in all the components that have registered with it. If a registered component disapproves of the property change, it can throw a PropertyVetoException to request that the previous value be retained instead. There is also a support class to help make this process easier. Use the class java.beans.VetoableChangeSupport to help manage your vetoable properties. The code in Listing 29.5 shows you what is required in the TextReader Bean to allow its Width property to be constrained.

Listing 29.5 TextReader.java ” Code Required to Make the Width Property of the TextReader Bean a Constrained Property
 // setter method for the Columns property.    public synchronized void setWidth( int newWidth )    throws PropertyVetoException {       int oldWidth = Width;       vetoAgent.fireVetoableChange( "width", new Integer( oldWidth ),                                     new Integer( newWidth ) );       // no one vetoed, so change the property.       Width = newWidth;       setColumns( Width );       Component p = getParent();       if ( p != null ) {          p.invalidate();          p.doLayout();       }       changeAgent.firePropertyChange( "width", new Integer( oldWidth ),                                       new Integer( newWidth ) );    }    // getter method for the Columns property.    public synchronized int getWidth() {       return Width;    }    // these two methods allow this Bean to have constrained properties.    public void addVetoableChangeListener( VetoableChangeListener l ) {       vetoAgent.addVetoableChangeListener( l );    }    public void removeVetoableChangeListener( VetoableChangeListener l ) {       vetoAgent.removeVetoableChangeListener( l );    }    protected VetoableChangeSupport vetoAgent =      new VetoableChangeSupport( this ); 

In this particular example, the Width property is bound and constrained. A property does not have to be bound to be constrained. For example, to make the Width property constrained but not bound, you would remove the following line from Listing 29.5:

 changeAgent.firePropertyChange( "width", new Integer( oldWidth ),                                       new Integer( newWidth ) ); 
   


Special Edition Using Java 2 Standard Edition
Special Edition Using Java 2, Standard Edition (Special Edition Using...)
ISBN: 0789724685
EAN: 2147483647
Year: 1999
Pages: 353

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net