Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage
<< | < | > | >> | API | Kapitel 44 - Beans

## 44.4 Bean-Ereignisse

- 44.4 Bean-Ereignisse
  - 44.4.1 Bound Properties
  - 44.4.2 Constrained Properties
  - 44.4.3 Anwendungsbeispiel

Um die Kommunikation zwischen Beans zu vereinfachen und zu vereinheitlichen, sieht die Beans-Spezifikation einige Techniken vor, mit denen Beans untereinander kommunizieren können. Die wichtigste von ihnen ist die Möglichkeit, Nachrichten zu versenden, wenn sich eine bestimmte Eigenschaft der Bean geändert hat. Das dabei angewendete Schema entspricht genau dem Delegation Based Event Handling, das in Kapitel 28 erläutert wurde und das auch alle anderen Dialogelemente zur Kommunikation mit anderen Komponenten verwenden.

### 44.4.1 Bound Properties

Eigenschaften, bei deren Veränderung Ereignisse ausgesandt werden, bezeichnet man auch als gebundene Eigenschaften (Bound Properties). Nicht alle Eigenschaften müssen gebunden sein. Sie sollten es dann sein, wenn die Bean anderen Objekten die Möglichkeit geben will, auf Änderungen des Ereignisses direkt und in definierter (und für einen GUI-Designer erkennbaren) Weise zu reagieren.

Zusätzlich zu den bereits bekannten Ereignistypen definiert die Bean-Architektur dazu eine neue Ereignisklasse `PropertyChangeEvent`, die im Paket `java.beans` untergebracht ist. Sie ist aus `EventObject` abgeleitet und stellt unter anderem folgende Methoden zur Verfügung:

    public String getPropertyName()

    public Object getNewValue()
    public Object getOldValue()
| java.beans.PropertyChangeEvent

Mit `getPropertyName` kann der Name der veränderten Eigenschaft ermittelt werden. `getNewValue` liefert ihren neuen Wert und `getOldValue` den Wert, den sie vor der Änderung hatte. Die Werte werden stets als Objekttyp zurückgegeben. Primitive Typen werden in ihre korrespondierenden Wrapper-Klassen verpackt (siehe Abschnitt 10.2).

Eine Bean erzeugt immer dann ein `PropertyChangeEvent` und versendet es an alle registrierten Listener, wenn sich der Zustand einer gebundenen Eigenschaft verändert hat. Ein Listener muß das Interface `PropertyChangeListener` implementieren. Es enthält nur eine Methode:

    void propertyChange(PropertyChangeEvent evt)
| java.beans.PropertyChangeListener

Verändert sich die gebundene Eigenschaft, erzeugt die Bean ein `PropertyChangeEvent` mit dem neuen und dem alten Wert und ruft bei allen registrierten Listenern `propertyChange` auf. Die Registrierung bzw. Deregistrierung von Listenern erfolgt mit den Methoden `addPropertyChangeListener` und `removePropertyChangeListener`, die beide einen `PropertyChangeListener` als Argument erwarten.

|  | Damit ein GUI-Designer erkennen kann, daß eine Bean gebundene Eigenschaften besitzt, müssen die Methoden zum Registrieren und Deregistrieren exakt die hier gezeigten Namen- und Parameterkonventionen einhalten. Sie sind ebenso Bestandteil der Designkonventionen von Beans wie die in den vorigen Abschnitten erwähnten getter- und setter-Methoden. |  |
Hinweis

Um die Bean beim Umgang mit gebundenen Eigenschaften zu unterstützen, gibt es im Paket `java.beans` eine Hilfsklasse `PropertyChangeSupport`. Sie stellt einige nützliche Methoden zur Verfügung, mit denen eine Bean die bei der Event-Registrierung und -verteilung anfallenden Aufgaben delegieren kann:

    public void addPropertyChangeListener(
      PropertyChangeListener listener
    )
    public void removePropertyChangeListener(
      PropertyChangeListener listener
    )

    public void firePropertyChange(PropertyChangeEvent evt)

    public void firePropertyChange(
      String propertyName,
      Object oldValue,
      Object newValue
    )
    public void firePropertyChange(
      String propertyName,
      int oldValue,
      int newValue
    )
    public void firePropertyChange(
      String propertyName,
      boolean oldValue,
      boolean newValue
    )
| java.beans.PropertyChangeSupport

Die Bean instanziert bei Bedarf ein Objekt dieser Klasse und gibt Aufrufe der eigenen Methoden `addPropertyChangeListener` und `removePropertyChangeListener` einfach weiter. Ändert sich eine gebundene Eigenschaft, ruft die Bean eine der `firePropertyChange`-Methoden auf und sendet dadurch ein `PropertyChangeEvent` an alle registrierten Listener. Diese Methode steht einerseits vordefiniert für die Typen `int`, `boolean` und `Object` zur Verfügung. Andererseits kann aber auch manuell ein `PropertyChangeEvent` erzeugt und weitergegeben werden.

Wir wollen uns als erstes Beispiel eine Bean ansehen, die einen beleuchteten Taster implementiert. Der Taster kann wahlweise an- oder ausgeschaltet sein. In angeschaltetem Zustand leuchtet er, in ausgeschaltetem Zustand ist seine Oberfläche dunkel. Jeder Tastendruck schaltet zwischen den beiden Zuständen um. Die wichtigste Eigenschaft des Tasters heißt `lightOn`. Sie ist gebunden und zeigt an, ob der Taster an- oder ausgeschaltet ist:

    001 /* LightedPushButton.java */
    002
    003 import java.awt.*;
    004 import java.awt.event.*;
    005 import java.io.*;
    006 import java.beans.*;
    007
    008 public class LightedPushButton
    009 extends Canvas
    010 implements Serializable
    011 {
    012   //---Instanzvariablen----------------------------------------
    013   protected Color                 linecolor;
    014   protected Color                 framecolor;
    015   protected Color                 lightoncolor;
    016   protected Color                 lightoffcolor;
    017   protected boolean               lighton;
    018   transient protected PropertyChangeSupport pchglisteners;
    019   transient protected VetoableChangeSupport vchglisteners;
    020
    021   //---Methoden------------------------------------------------
    022   public LightedPushButton()
    023   {
    024     linecolor     = Color.black;
    025     framecolor    = Color.darkGray;
    026     lightoncolor  = Color.red;
    027     lightoffcolor = new Color(127, 0, 0); //dark red
    028     lighton       = false;
    029     initTransientState();
    030   }
    031
    032   //---Zustandsumschaltung Licht an/aus---
    033   public void setLightOn(boolean on)
    034   throws PropertyVetoException
    035   {
    036     boolean oldvalue = this.lighton;
    037     vchglisteners.fireVetoableChange("lighton", oldvalue, on);
    038     this.lighton = on;
    039     if (oldvalue != on) {
    040       repaint();
    041     }
    042     pchglisteners.firePropertyChange("lighton", oldvalue, on);
    043   }
    044
    045   public boolean getLightOn()
    046   {
    047     return this.lighton;
    048   }
    049
    050   //---Verwaltung der PropertyChangeListener---
    051   public void addPropertyChangeListener(PropertyChangeListener l)
    052   {
    053     pchglisteners.addPropertyChangeListener(l);
    054   }
    055
    056   public void removePropertyChangeListener(PropertyChangeListener l)
    057   {
    058     pchglisteners.removePropertyChangeListener(l);
    059   }
    060
    061   //---Verwaltung der VetoableChangeListener---
    062   public void addVetoableChangeListener(VetoableChangeListener l)
    063   {
    064     vchglisteners.addVetoableChangeListener(l);
    065   }
    066
    067   public void removeVetoableChangeListener(VetoableChangeListener l)
    068   {
    069     vchglisteners.removeVetoableChangeListener(l);
    070   }
    071
    072   //---Implementierung der Oberfläche---
    073   public void paint(Graphics g)
    074   {
    075     int width = getSize().width;
    076     int height = getSize().height;
    077     //Rahmen
    078     g.setColor(framecolor);
    079     g.fillOval(0, 0, width, height);
    080     //Beleuchtung
    081     g.setColor(lighton ? lightoncolor : lightoffcolor);
    082     g.fillOval(4, 4, width - 8, height - 8);
    083     //Konturlinien
    084     g.setColor(linecolor);
    085     g.drawOval(0, 0, width - 1, height - 1);
    086     g.drawOval(3, 3, width - 7, height - 7);
    087   }
    088
    089   public Dimension getPreferredSize()
    090   {
    091     return new Dimension(32, 32);
    092   }
    093
    094   public Dimension getMinimumSize()
    095   {
    096     return new Dimension(16, 16);
    097   }
    098
    099   //---Private Klassen---------------------------------------
    100   /**
    101    * Initialisierung der nicht-persistenten Instanzvariablen.
    102    */
    103   private void initTransientState()
    104   {
    105     pchglisteners = new PropertyChangeSupport(this);
    106     vchglisteners = new VetoableChangeSupport(this);
    107     addMouseListener(new MouseClickAdapter());
    108   }
    109
    110   /**
    111    * Wird überlagert, um nach dem Deserialisieren den transienten
    112    * Zustand zu initialisieren.
    113    */
    114   private void readObject(ObjectInputStream stream)
    115   throws IOException, ClassNotFoundException
    116   {
    117     stream.defaultReadObject();
    118     initTransientState();
    119   }
    120
    121   //---Lokale Klassen----------------------------------------
    122   class MouseClickAdapter
    123   extends MouseAdapter
    124   {
    125     public void mouseClicked(MouseEvent event)
    126     {
    127       try {
    128         setLightOn(!getLightOn());
    129       } catch (PropertyVetoException e) {
    130         //no change if vetoed
    131       }
    132     }
    133   }
    134 }
| LightedPushButton.java

Listing 44.6: Ein beleuchteter Taster

Die Klasse verwendet ein `PropertyChangeSupport`-Objekt, um die Listener zu verwalten und sie von Änderungen des Beleuchtungszustands zu unterrichten. Das hier ebenfalls verwendete `VetoableChangeSupport`-Objekt und die mit seiner Hilfe implementierten Methoden werden im nächsten Abschnitt erläutert und können zunächst ignoriert werden.

Interessant ist die Implementierung der Methode `setLightOn`. Sie merkt sich zunächst den bisherigen Zustand und schaltet dann auf den neuen Zustand um. Anschließend werden alle registrierten Listener über die Änderung benachrichtigt. Der Name der gebundenen Eigenschaft ist "lighton" und entspricht damit dem Namen der betroffenen Eigenschaft. Eine eindeutige Benennung erlaubt es registrierten Listenern, zwischen Benachrichtigungen für unterschiedliche Eigenschaften zu unterscheiden.

Abbildung 44.5 zeigt die Klasse `LightedPushButton` in ihren beiden möglichen Zuständen in der Beanbox.

### 44.4.2 Constrained Properties

Eine Erweiterung der gebundenen Eigenschaften wurde im vorigen Abschnitt schon angedeutet. Mit dem Konzept der Constrained Properties ("verbotene" oder "unterbundene" Eigenschaften) kann eine Bean registrierten Listenern die Möglichkeit geben, Eigenschaftenänderungen zu verhindern.

Ein Listener, der das tun will, muß das Interface `VetoableChangeListener` implementieren. Es stellt nur eine Methode zur Verfügung:

    public void vetoableChange(PropertyChangeEvent evt)
      throws PropertyVetoException
| java.beans.VetoableChangeListener

Immer wenn sich eine Constrained Property ändern soll, ruft die Bean vor der Änderung bei den registrierten Listenern die Methode `vetoableChange` auf. Der Empfänger prüft daraufhin das übergebene `PropertyChangeEvent` und entscheidet, ob er dem Änderungswunsch zustimmen soll oder nicht. Ist das nicht der Fall, löst er eine `PropertyVetoException` aus, die beim Empfänger dazu führt, daß der Änderungsvorgang abgebrochen wird. Stimmt er dagegen zu, ist gar nichts zu tun. Der Listener terminiert einfach und die Bean fährt mit der Befragung beim nächsten Listener fort. Nur wenn alle Listener zugestimmt haben (also niemand eine `PropertyVetoException` ausgelöst hat), wird die Änderung tatsächlich durchgeführt.

Die Methode `setLightOn` in Listing 44.6 macht "lightOn" also sowohl zu einer gebundenen als auch unterbindbaren Eigenschaft. Zunächst prüft sie, ob alle registrierten `VetoableChangeListener` eine mögliche Zustandsänderung nicht ablehnen. Erst wenn das der Fall ist, wird die Änderung tatsächlich durchgeführt, und die registrierten `PropertyChangeListener` werden darüber informiert.

Das Registrieren und Deregistrieren erfolgt mit den Methoden `addVetoableChangeListener` und `removeVetoableChangeListener`. Ebenso wie die korrespondierenden Methoden zur Registrierung der `PropertyChangeListener` müssen ihre Namen und Parameter genau der hier angegebenen Form entsprechen, damit sie vom GUI-Designer gefunden werden.

Zur Unterstützung bei der Implementierung kann sich die Bean der Klasse `VetoableChangeSupport` bedienen, die ähnliche Methoden wie `PropertyChangeSupport` zur Verfügung stellt:

    public void addVetoableChangeListener(
      VetoableChangeListener listener
    )
    public void removeVetoableChangeListener(
      VetoableChangeListener listener
    )

    public void fireVetoableChange(PropertyChangeEvent evt)
      throws PropertyVetoException

    public void fireVetoableChange(
      String propertyName,
      Object oldValue,
      Object newValue
    )
      throws PropertyVetoException

    public void fireVetoableChange(
      String propertyName,
      int oldValue,
      int newValue
    )
      throws PropertyVetoException

    public void fireVetoableChange(
      String propertyName,
      boolean oldValue,
      boolean newValue
    )
      throws PropertyVetoException
| java.beans.VetoableChangeSupport

Um im nächsten Abschnitt ein Beispiel für die Anwendung von Constrained Properties geben zu können, wollen wir eine Klasse vorstellen, die das Interface `VetoableChangeListener` implementiert. Sie wird durch eine Bean repräsentiert, die einen einfachen Umschalter darstellt, der durch Mausklick geöffnet oder geschlossen werden kann. Ist der Schalter geschlossen, sind Eigenschaftsänderungen erlaubt. Ist er dagegen geöffnet, wird bei jedem Aufruf von `vetoableChange` eine `PropertyVetoException` ausgelöst. Auf diese Weise werden bei geöffnetem Schalter alle Eigenschaftsänderungen verhindert:

    001 /* VetoSwitch.java */
    002
    003 import java.awt.*;
    004 import java.awt.event.*;
    005 import java.io.*;
    006 import java.beans.*;
    007
    008 public class VetoSwitch
    009 extends Canvas
    010 implements Serializable, VetoableChangeListener
    011 {
    012   //---Instanzvariablen----------------------------------------
    013   protected Color   linecolor;
    014   protected boolean vetoallchanges;
    015
    016   //---Methoden------------------------------------------------
    017   public VetoSwitch()
    018   {
    019     this.linecolor = Color.black;
    020     this.vetoallchanges = false;
    021     initTransientState();
    022   }
    023
    024   //---Konturenfarbe---
    025   public void setLineColor(Color color)
    026   {
    027     this.linecolor = color;
    028   }
    029
    030   public Color getLineColor()
    031   {
    032     return this.linecolor;
    033   }
    034
    035   //---Zustandsumschaltung Licht an/aus---
    036   public void setVetoAllChanges(boolean b)
    037   {
    038     if (this.vetoallchanges != b) {
    039       this.vetoallchanges = b;
    040       repaint();
    041     }
    042   }
    043
    044   public boolean getVetoAllChanges()
    045   {
    046     return this.vetoallchanges;
    047   }
    048
    049   //---Veto---
    050   public void vetoableChange(PropertyChangeEvent e)
    051   throws PropertyVetoException
    052   {
    053     if (this.vetoallchanges) {
    054       throw new PropertyVetoException("!!!VETO!!!", e);
    055     }
    056   }
    057
    058   //---Implementierung der Oberfläche---
    059   public void paint(Graphics g)
    060   {
    061     int width = getSize().width;
    062     int height = getSize().height;
    063     g.setColor(linecolor);
    064     g.drawRect(0, 0, width - 1, height - 1);
    065     g.drawLine(width * 1 / 8, height / 2, width * 3 / 8, height / 2);
    066     g.drawLine(width * 5 / 8, height / 2, width * 7 / 8, height / 2);
    067     g.fillRect(width * 3 / 8 - 1, height / 2 - 1, 3, 3);
    068     g.fillRect(width * 5 / 8 - 1, height / 2 - 1, 3, 3);
    069     if (this.vetoallchanges) {
    070       //draw open connection
    071       g.drawLine(width * 3 / 8, height / 2, width * 5 / 8, height / 4);
    072     } else {
    073       //draw short-cutted connection
    074       g.drawLine(width * 3 / 8, height / 2, width * 5 / 8, height / 2);
    075     }
    076   }
    077
    078   public Dimension getPreferredSize()
    079   {
    080     return new Dimension(60, 20);
    081   }
    082
    083   public Dimension getMinimumSize()
    084   {
    085     return new Dimension(28, 10);
    086   }
    087
    088   //---Private Klassen----------------------------------------
    089   private void initTransientState()
    090   {
    091     addMouseListener(new MouseClickAdapter());
    092   }
    093
    094   /**
    095    * Wird überlagert, um nach dem Deserialisieren den transienten
    096    * Zustand zu initialisieren.
    097    */
    098   private void readObject(ObjectInputStream stream)
    099   throws IOException, ClassNotFoundException
    100   {
    101     stream.defaultReadObject();
    102     initTransientState();
    103   }
    104
    105   //---Lokale Klassen----------------------------------------
    106   class MouseClickAdapter
    107   extends MouseAdapter
    108   {
    109     public void mouseClicked(MouseEvent event)
    110     {
    111       setVetoAllChanges(!getVetoAllChanges());
    112     }
    113   }
    114 }
| VetoSwitch.java

Listing 44.7: Ein Veto-Schalter

Abbildung 44.5 zeigt die Klasse `VetoSwitch` in ihren beiden möglichen Zuständen in der Beanbox:

Abbildung 44.5: LightedPushButton und VetoSwitch in der Beanbox

### 44.4.3 Anwendungsbeispiel

Das folgende Listing zeigt die drei bisher definierten Beans im Zusammenspiel. Zunächst werden alle drei Elemente instanziert und auf einem Frame plaziert. Da der `LightedPushButton` bei jedem Tastendruck den Zustand der `LightBulb` umschalten soll, bekommt er einen `PropertyChangeListener` zugeordnet, der bei Zustandsänderungen der Eigenschaft "lighton" die Lampe umschaltet.

Der `VetoSwitch` wird als `VetoableChangeListener` ebenfalls beim `LightedPushButton` registriert. Da er in geöffnetem Zustand alle unterbindbaren Eigenschaftenänderungen verhindert, dient er dazu, die Funktion des Buttons zu deaktivieren. Nur, wenn der `VetoSwitch` geschlossen ist, hat das Drücken des `LightedPushButton` eine Änderung des Lampenzustands zur Folge.

    001 /* Listing4408.java */
    002
    003 import java.awt.*;
    004 import java.awt.event.*;
    005 import java.beans.*;
    006
    007 public class Listing4408
    008 extends Frame
    009 {
    010   public Listing4408()
    011   {
    012     //Initialisierung
    013     super("BeanPropertiesTest");
    014     setLayout(new FlowLayout());
    015     setBackground(Color.lightGray);
    016     addWindowListener(
    017       new WindowAdapter() {
    018         public void windowClosing(WindowEvent event)
    019         {
    020           System.exit(0);
    021         }
    022       }
    023     );
    024     //Dialogelemente hinzufügen
    025     LightedPushButton button1 = new LightedPushButton();
    026     VetoSwitch veto1 = new VetoSwitch();
    027     final LightBulb bulb1 = new LightBulb();
    028     add(button1);
    029     add(veto1);
    030     add(bulb1);
    031     button1.addPropertyChangeListener(
    032       new PropertyChangeListener() {
    033         public void propertyChange(PropertyChangeEvent e)
    034         {
    035           if (e.getPropertyName().equals("lighton")) {
    036             Boolean on = (Boolean)e.getNewValue();
    037             bulb1.setLightOn(on.booleanValue());
    038           }
    039         }
    040       }
    041     );
    042     button1.addVetoableChangeListener(veto1);
    043   }
    044
    045   //---main-------------------------------------------------
    046   public static void main(String[] args)
    047   {
    048     Listing4408 frm = new Listing4408();
    049     frm.setLocation(100, 100);
    050     frm.pack();
    051     frm.setVisible(true);
    052   }
    053 }
| Listing4408.java

Listing 44.8: Eigenschaftenänderungen von Beans

Abbildung 44.6 zeigt das Programm mit angeschalteter Lampe und anschließend geöffnetem Schalter. Das Drücken des Tasters bleibt nun so lange erfolglos, bis der Schalter wieder geschlossen wird.

Abbildung 44.6: Testprogramm für Eigenschaftenänderungen

Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de