Titel | Inhalt | Suchen | Index | API | Go To Java 2, Zweite Auflage, Handbuch der Java-Programmierung |
<< | < | > | >> | Kapitel 44 - Beans |
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.
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. |
|
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 |
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.
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 |
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
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 |
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 | API | Go To Java 2, Zweite Auflage, Addison Wesley, Version 2.0 |
<< | < | > | >> | © 2000 Guido Krüger, http://www.gkrueger.com |