|
| |
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:
- 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)
- 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;
} |
|