Bound Properties
Home ] Up ] What are JavaBeans? ] The Bean Development Kit ] The BeanBox ] JavaBean Properties ] Simple and Indexed Properties ] [ Bound Properties ] Constrained Properties ] Bean Deployment ] Introspection ] Property Editors ] Customizers ] Bean Persistence ] New JavaBean Features ]

 

 

When the value of a Bean property changes, it's sometimes useful for interested Bean listeners to be informed of those changes.

Bound properties tell these interested listeners when their value changes. They cause events to be sent to those interested listeners when a value changes, to allow those listeners to take some appropriate action in response. For example, a SpreadSheet Bean might tell a PieChart bean to redraw itself whenever the spreadsheet changes.

Bound Property Requirements

To implement a bound property, you must follow the normal property naming conventions, and also implement the following:

  1. The Bean must implement the following two methods to allow other classes to register as PropertyChangeListeners:
    public void addPropertyChangeListener(
    		PropertyChangeListener listener)
    public void removePropertyChangeListener(
    		PropertyChangeListener listener)
  2. Whenever the value of the bound property changes, the Bean must send a PropertyChangeEvent to all registered listeners

The PropertyChangeListener Interface

The PropertyChangeListener interface has a single method:

public void propertyChange(PropertyChangeEvent evt)

When a class implements this interface, it must implement this method. The method is called, if the class is registered as a PropertyChangeListener, when it receives a PropertyChangeEvent.

The PropertyChangeSupport Class

The java.beans package contains a convenience class, PropertyChangeSupport, that makes it trivial to write the addPropertyChangeListener and removePropertyChangeListener methods:

private void PropertyChangeSupport m_changeSupport =
	new PropertyChangeSupport(this);
public void addPropertyChangeListener(PropertyChangeListener l)
{
    m_changeSupport.addPropertyChangeListener(l);
}
public void removePropertyChangeListener(PropertyChangeListener l)
{
    m_changeSupport.removePropertyChangeListener(l);
}

PropertyChangeSupport also makes it simple to "fire" a PropertyChangeEvent:

m_changeSupport.firePropertyChange(propertyName,
				   oldValue,
				   newValue);

If the property has a primitive type, then you must "wrap" the values:

m_changeSupport.firePropertyChange(propertyName,
				   new Integer(oldValue),
				   new Integer(newValue));

Example: BarChart, InBox, and ColorBar Beans

The example is an extension of the earlier BarChart Bean. Here's what it looks like as an applet:

You'll note that it looks somewhat similar to the previous version.

It has the BarChart Bean, as before (although it's a modified version), but in place of the two buttons, it now has an input text field to supply the value for the bar chart.

The input text field is an InBox Bean, which will only allow you to enter certain characters -- digits, Enter, Delete, Left & Right Arrows, etc. All others will be ignored. This means that you can't type in an invalid integer.

Whenever the value is changed (enter a new value, and hit Enter, or Tab), it will cause the bar chart to display appropriately.

This is implemented using a bound property in the InBox Bean, and a PropertyChangeListener in the BarChart Bean.

Here's the code for the modified BarChart Bean:

package javaBeans;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

/**
 *  A BarChart JavaBean class.
 *
 *  @author Bryan J. Higgs, 12 March, 2000
 */
public class BarChart extends Canvas 
                      implements PropertyChangeListener
{
    /**
     *  Constructs a BarChart instance
     */
    public BarChart()
    {
        setSize(m_barWidth, m_barHeight);
    }
    
    //// Accessor methods ////
    
    public void setFloodColor(Color floodColor)
    {
        m_floodColor = floodColor;
    }

    public Color getFloodColor()
    {
        return m_floodColor;
    }

    public void setFillColor(Color fillColor)
    {
        m_fillColor = fillColor;
    }

    public Color getFillColor()
    {
        return m_fillColor;
    }

    public void setBorderColor(Color borderColor)
    {
        m_borderColor = borderColor;
    }

    public Color getBorderColor()
    {
        return m_borderColor;
    }

    public void setBorderWidth(int borderWidth)
    {
        m_borderWidth = borderWidth;
    }
    
    public int getBorderWidth()
    {
        return m_borderWidth;
    }

    public void setBarWidth(int barWidth)
    {
        m_barWidth = barWidth;
    }

    public int getBarWidth()
    {
        return m_barWidth;
    }

    public void setBarHeight(int barHeight)
    {
        m_barHeight = barHeight;
    }

    public int getBarHeight()
    {
        return m_barHeight;
    }
    
    /**
     *  Sets the percent for bar display.
     */
    public void setPercent(int percent)
    {
        if (percent <= 100 && percent >= 0)
        {
            m_percent = percent;
            repaint();
        }
    }
    
    /**
     *  Gets the percent for bar display.
     */
    public int getPercent()
    {
        return m_percent;
    }
    
    /**
    *   Allows the user to set the bar size visually.
    */
    public void setBounds(int x, int y, int width, int height)
    {
        super.setBounds(x, y, width, height);
        setBarWidth(width);
        setBarHeight(height);
    }

    // Respond to a property change event for the property
    // "value" by updating my value to a new value.
    public void propertyChange(PropertyChangeEvent event)
    {
        Integer newValue = (Integer)event.getNewValue();
        setPercent(newValue.intValue());
    }

    /**
     *  Paints the BarChart using the specified Graphics.
     */
    public void paint(Graphics g)
    {
        // Use fill color to fill entire component
        g.setColor(m_fillColor);
        g.fillRect(0, 0, m_barWidth, m_barHeight);
        
        int f = (int)(0.5 + (m_percent/100.0) * 
                            (m_barHeight - (2 * m_borderWidth)));
        g.setColor(m_floodColor);
        g.fillRect(0, m_barHeight - m_borderWidth - f, 
                   m_barWidth, f);
                   
        // Use border color for border, if applicable
        if (m_borderWidth > 0)
        {
            g.setColor(m_borderColor);
            g.fillRect(0, 0, m_borderWidth, m_barHeight);
            g.fillRect(0, 0, m_barWidth, m_borderWidth);
            g.fillRect(0, m_barHeight - m_borderWidth, 
                       m_barWidth, m_borderWidth);
            g.fillRect(m_barWidth - m_borderWidth, 0, 
                       m_borderWidth, m_barHeight);
        }
        
    }
    
    //// Private data ////
    
    private int     m_percent = 0;
    private Color   m_floodColor = Color.blue;
    private Color   m_fillColor = Color.red;
    private Color   m_borderColor = Color.black;
    private int     m_barWidth = 25;
    private int     m_barHeight = 150;
    private int     m_borderWidth = 2;
}

Here is the code for the InBox Bean:

package javaBeans;

import java.awt.TextField;
import java.awt.event.KeyEvent;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

/**
*   An IntBox Bean is a TextField that only accepts positive integers.
*
*   @author Bryan J. Higgs, 8 April, 2000
*/
public class IntBox extends TextField 
{
    /**
    *   Default constructor.
    */
    public IntBox()
    {
        // Must enable key events on this component
        enableEvents(java.awt.AWTEvent.KEY_EVENT_MASK);
    }

    /**
    *   Allow other objects to register interest in my bound properties
    */
    public void addPropertyChangeListener(PropertyChangeListener listener)
    {
        m_pcs.addPropertyChangeListener(listener);
    }

    /**
    *   Allow other objects to remove their interest in my bound properties
    */
    public void removePropertyChangeListener(PropertyChangeListener listener)
    {
        m_pcs.removePropertyChangeListener(listener);
    }

    /**
    *   Return the integer value of this object.
    *   (It's zero if we can't parse it.)
    */
    public int getValue()
    {
        try 
        {
            int i = Integer.parseInt(getText());
            return i;
        }
        catch (NumberFormatException ex) {
            return 0;
        }
    }
    
    /**
    *   Set new value
    */
    public void setValue(int value)
    {
        int oldValue = m_prevValue;
        setText(Integer.toString(value));
        int newValue = getValue();

        // This call sends a property change event to all listeners.
        m_pcs.firePropertyChange("value",
                                 new Integer(oldValue),
                                 new Integer(newValue));
        m_prevValue = newValue;
    }

    /**
    *   Intercept key events so we can discard non-numeric and
    *   non-special characters. (For example, we don't allow any
    *   of the alphabetic characters, punctuation, etc.)
    */
    public void processKeyEvent(KeyEvent event)
    {
        int id = event.getID();         // Get the event type
        int c = event.getKeyCode();     // and the key code
        if (id == KeyEvent.KEY_TYPED)   // KEY_TYPED doesn't have a code
            c = (int)event.getKeyChar();// So get the character instead 
        
        // Only these characters receive normal processing.
        if (Character.isDigit((char)c) ||
            c == KeyEvent.VK_DELETE ||
            c == KeyEvent.VK_BACK_SPACE ||
            c == KeyEvent.VK_LEFT ||
            c == KeyEvent.VK_RIGHT ||
            c == KeyEvent.VK_TAB ||
            c == KeyEvent.VK_ENTER
           ) 
        {
            if (id == KeyEvent.KEY_PRESSED &&   // Only do it once!
                (c == KeyEvent.VK_ENTER || c == KeyEvent.VK_TAB)
               )
            {
                setValue(getValue());
            }
            
            // Allow normal processing
            super.processKeyEvent(event);
            return;
        }
        
        // Discard all other characters
        event.consume();
    }

    //// Private Data ////
    
    // The previous value of the IntBox.
    private int m_prevValue = 0;

    // Delegate property change operations to this object    
    private PropertyChangeSupport m_pcs =
                        new PropertyChangeSupport(this);
}

and finally, the code for the ColorBarApplet (which is also a Bean):

package javaBeans;

import java.awt.BorderLayout;
import java.awt.Panel;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import java.applet.Applet;

/**
*   The ColorBar Applet Bean contains two beans: IntBox and BarChart.
*
*   @author Bryan J. Higgs, 8 April, 2000
*/
public class ColorBarApplet extends Applet
{
    /**
    *   Constructor
    */
    public ColorBarApplet()
    {
	    setLayout(new BorderLayout());

	    // Create and initialize BarChart.
	    m_barChart = new BarChart();
	    m_barChart.setPercent(0);

	    // Create and initialize IntBox
	    m_intBox = new IntBox();
	    m_intBox.setText("0");

	    Panel p = new Panel();
	    p.add(m_barChart);
	    add(p, BorderLayout.NORTH);
	    add(m_intBox, BorderLayout.SOUTH);

	    // Set the BarChart to listen for IntBox property change events.
	    m_intBox.addPropertyChangeListener(m_barChart);
	    
	    // Ensure that when the applet displays, that 
	    // the bar is properly sized.
	    addComponentListener( new ComponentAdapter()
	        {
	            public void componentShown(ComponentEvent ev)
	            {
	                Rectangle bounds = getBounds();
	                setBounds(bounds);
	                doLayout();
	            }
	        }
	    );
    }
    
    /**
    *   This allows the user to set the size visually.
    */
    public void setBounds(int x, int y, int width, int height)
    {
	    super.setBounds(x, y, width, height);
	    Rectangle intBoxBounds = m_intBox.getBounds();
	    m_barChart.setBarWidth(width - 20);
	    m_barChart.setBarHeight(height - intBoxBounds.height - 10);
    }
    
    //// Private Data ////
    private BarChart    m_barChart;
    private IntBox      m_intBox;
}
 
The page was last updated February 19, 2008