Constrained 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 ]

 

 

A Bean can be set up to listen for a property value change and "make a fuss" if the value is not to its liking. In fact, the listener can prevent the property change from happening, by vetoing it.

A property that is prepared to handle this is called a constrained property.

Constrained Property Requirements

A constrained property is also a bound property (a constrained property that is not a bound property doesn't make much sense, as you'll see).

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

  1. The Bean must implement the following two methods to allow other classes to register as VetoableChangeListeners:
    public void addVetoableChangeListener(
    		VetoableChangeListener listener)
    public void removeVetoableChangeListener(
    		VetoableChangeListener listener)
  2. Whenever the value of the constrained property changes, the Bean must send a PropertyChangeEvent to all registered VetoableChangeListeners.

The VetoableChangeListener Interface

The VetoableChangeListener interface has a single method:

public void vetoableChange(PropertyChangeEvent evt) 
		throws PropertyVetoException

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

The VetoableChangeSupport Class

In a manner similar to the case of PropertyChangeListeners, the java.beans package contains a convenience class, VetoableChangeSupport, that makes it trivial to write the addVetoableChangeListener and removeVetoableChangeListener methods:

private void VetoableChangeSupport m_vetoSupport =
	new VetoableChangeSupport(this);
public void addVetoableChangeListener(VetoableChangeListener l)
{
    m_vetoSupport.addVetoableChangeListener(l);
}
public void removeVetoableChangeListener(VetoableChangeListener l)
{
    m_vetoSupport.removeVetoableChangeListener(l);
}

VetoableChangeSupport also makes it simple to "fire" a vetoable PropertyChangeEvent:

m_vetoSupport.fireVetoableChange(propertyName,
			   	 oldValue,
				 newValue);

As before, if the property has a primitive type, then you must "wrap" the values:

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

Example: More on 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 identical to the bound properties version.

The only difference is that before, you could set the IntBox to contain a value outside the range 0 - 100. If you did that, the BarChart would ignore the value you're trying to set.

In this version, when you attempt to enter a value outside the range, the previous value is forced back into the IntBox.

This is implemented using a constrainted property in the InBox Bean, and a VetoableChangeListener in the BarChart Bean.

Here's the code for the modified BarChart Bean, with changes highlighted:

package javaBeans;

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

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.VetoableChangeListener;
import java.beans.PropertyVetoException;

/**
 *  A BarChart JavaBean class.
 *
 *  @author Bryan J. Higgs, 12 March, 2000
 */
public class BarChart extends Canvas 
                      implements PropertyChangeListener,
                                 VetoableChangeListener
{
    /**
     *  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);
    }
    
    public Dimension getPreferredSize()
    {
        return new Dimension(getBarWidth(), getBarHeight());
    }
    
    // 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());
    }

    // Respond to a vetoable property change event for the 
    // property "value" by checking the new value for validity
    // If it's not valid, a PropertyVetoException is thrown.
    // (We leave it to the propertyChange() method above to
    // actually change the value.)
    public void vetoableChange(PropertyChangeEvent event)
        throws PropertyVetoException
    {
        int newPercent = ((Integer)event.getNewValue()).intValue();
        if (newPercent < 0 || newPercent > 100)
            throw new PropertyVetoException("No way, Jose!", event);
    }

    /**
     *  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's the new version of IntBox, with changes highlighted:

package javaBeans;

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

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.VetoableChangeListener;
import java.beans.VetoableChangeSupport;
import java.beans.PropertyVetoException;

/**
*   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);
    }

    /**
    *   Allow other objects to register interest in my constrained properties
    */
    public void addVetoableChangeListener(VetoableChangeListener listener)
    {
        m_vcs.addVetoableChangeListener(listener);
    }

    /**
    *   Allow other objects to remove interest in my constrained properties
    */
    public void removeVetoableChangeListener(VetoableChangeListener listener)
    {
        m_vcs.removeVetoableChangeListener(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 newValue)
    {
        int oldValue = m_prevValue;
        
        try
        {
            Integer oldObject = new Integer(oldValue);
            Integer newObject = new Integer(newValue);
            
            // This call sends a vetoable property change event
            m_vcs.fireVetoableChange("value", oldObject, newObject);
            
            // If we get here, none of the VetoableChangeListeners vetoed it.
            
            // This call sends a property change event.
            m_pcs.firePropertyChange("value", oldObject, newObject);

            // Set the new value and show it in the text field.
            m_prevValue = newValue;
            setText(Integer.toString(newValue));
        }
        catch (PropertyVetoException ex)
        {
            // It was vetoed by someone, so reset to previous value.
            setText(Integer.toString(oldValue));
        }
    }

    /**
    *   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);
    // Delegate vetoable property change operations to this object    
    private VetoableChangeSupport m_vcs =
                        new VetoableChangeSupport(this);
}

and finally, the ColorBarApplet applet, with changes highlighted:

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);
	    
	    // Set the BarChart to listen for vetoable property change events.
	    m_intBox.addVetoableChangeListener(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