22.8 Weitere Eigenschaften
22.8.1 Gebundene Eigenschaften
Die gebundenen Eigenschaften einer Bean erlauben es, andere Komponenten über eine Zustandsänderung zu informieren. Damit werden direkt Eigenschaften und Ereignisse verbunden. Die interessierten Beans empfangen vom Auslöser ein PropertyChange-Ereignis, das sie auswerten können. Die Beans lassen sich mit addPropertyChangeListener() und removePropertyChangeListener()-Methoden als Zuhörer einfügen und abhängen. Bei einer Veränderung werden alle registrierten Zuhörer durch ein PropertyChangeEvent informiert. Die Interessierten implementieren dafür PropertyChangeListener. Über dieses Ereignis-Objekt erfahren wir den alten und neuen Wert, gleichzeitig erfahren wir etwas über den Typ und den Namen der Eigenschaft. Die Zuhörer werden erst nach der Änderung des internen Zustands informiert.
Beispiel Unsere Komponente ändert den internen String über setString(). Nach der Änderung werden alle Listener informiert. Sie macht sonst nichts Großartiges.
|
Listing 22.6 PropertyChangeStringBean.java
import java.beans.*;
import java.awt.*;
public class PropertyChangeStringBean extends Canvas
{
private String string = "";
private PropertyChangeSupport changes = new PropertyChangeSupport( this );
public void setString( String newString )
{
String oldString = string;
string = newString;
changes.firePropertyChange( "string", oldString, newString );
}
public String getString()
{
return string;
}
public void addPropertyChangeListener( PropertyChangeListener l ) {
changes.addPropertyChangeListener( l );
}
public void removePropertyChangeListener( PropertyChangeListener l ) {
changes.removePropertyChangeListener( l );
}
}
Der Methode setString() kommt zentrale Bedeutung zu. Der erste Parameter von firePropertyChange() ist der Name der Eigenschaft. Er ist nur für die Änderung von Belang und hat nichts mit dem Namen der Bean-Eigenschaft gemeinsam. Die Methode informiert alle angemeldeten Zuhörer über die Änderung mit einem PropertyChangeEvent. Im Ereignis stehen der alte und der neue Stand des Werts. Angemeldete Listener können dann darauf reagieren. So könnten abhängige Klassen zum Beispiel ihr Layout daraufhin ändern.
interface java.beans.PropertyChangeListener
extends java.util.EventListener
|
|
void propertyChange( PropertyChangeEvent evt )
Wird aufgerufen, wenn sich die gebundene Eigenschaft ändert. Über das Property ChangeEvent erfahren wir die Quelle und den Inhalt der Eigenschaft. |
class java.beans.PropertyChangeEvent
extends java.util.EventObject
|
|
public PropertyChangeEvent( Object source, String propertyName,
Object oldValue, Object newValue )
Erzeugt ein neues Objekt mit der Quelle, die das Ereignis auslöst, einem Namen, dem alten und dem gewünschten Wert. Die Werte werden intern in privaten Variablen gehalten und lassen sich später nicht mehr ändern. |
|
String getPropertyName()
Liefert den Namen der Eigenschaft. |
|
Object getNewValue()
Liefert den neuen Wert. |
|
Object getOldValue()
Liefert den alten Wert. |
class java.beans.PropertyChangeSupport
implements Serializable
|
|
PropertyChangeSupport( Object sourceBean )
Konstruiert ein PropertyChangeSupport-Objekt, welches sourceBean als auslösende Bean beachtet. |
|
synchronized void addPropertyChangeListener( PropertyChangeListener listener )
Fügt einen Listener hinzu. |
|
synchronized void addPropertyChangeListener( String propertyName,
PropertyChangeListener listener )
Fügt einen Listener hinzu, der nur auf Ereignisse mit dem Namen propertyName hört. |
|
synchronized void removePropertyChangeListener( PropertyChangeListener listener )
Entfernt einen Listener. |
|
synchronized void removePropertyChangeListener( String propertyName,
PropertyChangeListener listener )
Entfernt den Listener, der auf propertyName hörte. |
|
void firePropertyChange( String propertyName, Object oldValue, Object newValue )
Informiert alle Listener über eine Werteänderung. Sind alte und neue Werte gleich, werden keine Events ausgelöst. |
|
void firePropertyChange( String propertyName, int oldValue, int newValue )
void firePropertyChange( String propertyName, boolean oldValue, boolean newValue )
Varianten von firePropertyChange() mit Integer- und Boolean-Werten. |
|
void firePropertyChange( PropertyChangeEvent evt )
Informiert alle Interessenten mit einem PropertyChangeEvent, indem es propertyChange() aufruft. |
|
synchronized boolean hasListeners( String propertyName )
Liefert true, wenn es mindestens einen Listener für die Eigenschaft gibt. |
22.8.2 Anwendung von PropertyChange bei AWT-Komponenten
Selbst das AWT und besonders Swing nutzt PropertyChange-Ereignisse an einigen Stellen, an denen ein spezielles AWT-Ereignis nicht vorgesehen ist. Das gilt etwa bei Änderungen von Hintergrund, Vordergrund oder Zeichensatz. Ein Component-Ereignis deckt dies nicht ab. Damit wir auch über diese Änderungen informiert werden, fügen wir einen Listener hinzu und horchen auf »foreground«, »background« oder »font«.
22.8.3 Veto-Eigenschaften. Dagegen!
Bei gebundenen Eigenschaften informieren Komponenten andere Komponenten, falls sich ein Zustand ändert. Nun kann es aber sein, dass diese Komponenten etwas dagegen haben. In diesem Fall kann die hörende Bean ihr Veto mit einer PropertyVetoException einlegen und so eine Werteänderung verhindern. Bevor wir die Änderung machen, holen wir also erst die Zustimmung dafür ein. Eine Veto-Eigenschaft ist somit der gebundenen Eigenschaft verwandt, nur dass diese nicht meckern kann. Programmieren wir eine setXXX()-Methode mit Veto, so kann der Aufrufer jetzt durch eine PropertyVetoException nach außen anzeigen, dass er dagegen war.
fireVetoableChange( String propertyName, boolean, boolean )
Beispiel Auf einem Konto-Objekt lässt sich mit setMoney() der Kontostand verändern. Wir wollen die Methode so auszeichnen, dass sie erst um Erlaubnis fragt. Der Kontostand ist intern mit value vermerkt.
|
private PropertyChangeSupport changes = new PropertyChangeSupport( this );
private VetoableChangeSupport vetos = new VetoableChangeSupport( this );
public void setMoney( int newMoney ) throws PropertyVetoException
{
int old = value;
vetos.fireVetoableChange( "money", old, newMoney );
value = newMoney;
changes.firePropertyChange( "money", old, newMoney );
}
Wie wir in dem Beispiel sehen, ist zusätzlich zum Veto noch eine gebundene Eigenschaft mit dabei. Das ist die Regel, damit Interessierte auch mitbekommen, dass die Änderungen für alle gültig wurden. Denn wenn einer der Kandidaten gegen die Änderung ist, dann wird der neue Stand niemals angenommen. Die Interessenten wissen ja nichts voneinander. War jedoch keiner gegen die Änderung, so bekommen sie auch alle ein PropertyChange und wissen somit, dass alles in Ordnung ist. Alle sind mit dem neuen Wert einverstanden.
Gibt es mehrere Listener, die kein Veto einlegen und aus dem Veto-Event das Ergebnis herausnehmen, hat das den Nachteil, dass ein Listener, der Veto einlegt, alles abbricht und die alten Ergebnisse ungültig macht. Glücklicherweise entschuldigt sich dann VetoableChangeSupport, indem für die schon abgearbeiteten Listener noch einmal ein Veto mit dem alten Wert geschickt wird. Implementieren wir die Behandlung selbst, so müssen wir den Zustand selbst wiederherstellen und die Hörer informieren. Jetzt dürfen keine Vetos mehr auftauchen. Wenn doch, dann sollten sie ignoriert werden. Das entspricht einem einfachen Undo. Damit diese Entschuldigung nicht erst beachtet werden muss, reicht es aus, auf die Auslösung von PropertyChange zu warten, um dann sicher zu sein, dass alle das Ergebnis akzeptiert haben. Wenn wir auf ein Veto hören, hat das den Vorteil, dass wir sukzessive den Prozess verfolgen können. Wir können so immer wieder Vorschläge einreichen und sehen, ob sie akzeptiert werden.
Obwohl der Wunsch für eine Veto-Änderung auch einen PropertyChange darstellt, ist es fehleranfällig, für Veto- und auch gebundene Eigenschaften gleichzeitig PropertyChange Event-Objekte einzusetzen. Während bei Veto-Objekten vor der Zustandsänderung ein PropertyChangeEvent erzeugt wird, informieren die gebundenen Eigenschaften nach der Änderung ihre Zuhörer mit einem PropertyChangeEvent. Daher bedeutet das Aufkommen eines PropertyChangeEvent zweimal etwas Unterschiedliches.
|