Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 7. Auflage |
<< | < | > | >> | API | Kapitel 46 - Beans |
Die bisher beschriebenen Fähigkeiten und Eigenschaften einer Bean wurden vom GUI-Designer automatisch erkannt, weil die Beans bestimmte Design-Konventionen eingehalten haben. Der GUI-Designer verwendet eine Instanz der Klasse Introspector aus dem Paket java.beans, um diese Fähigkeiten nach dem Laden der Bean zu bestimmen.
Neben getter-/setter-Methoden und Registrierungsmethoden für Events können Beans aber noch weitere Eigenschaften besitzen. So kann beispielsweise in der Toolbox ein Icon angezeigt werden. Auch Methoden, die nicht den getter-/setter-Konventionen entsprechen, können bekanntgemacht werden. Zudem ist es möglich, für Nicht-Standard-Eigenschaften spezielle Eigenschafteneditoren zur Verfügung zu stellen.
Diese Möglichkeiten stehen erst zur Verfügung, wenn man die Bean mit einer expliziten BeanInfo-Klasse ausstattet. Sie hat denselben Namen wie die Bean, trägt aber zusätzlich den Suffix »BeanInfo« am Ende ihres Namens. Zudem muss sie im selben Paket liegen wie die Bean-Klasse. Die BeanInfo-Klasse muss das Interface BeanInfo implementieren oder aus SimpleBeanInfo abgeleitet sein (Letzteres bietet den Vorteil, dass bereits alle erforderlichen Methoden vorhanden sind und bei Bedarf nur noch überlagert werden müssen).
BeanInfo definiert eine Reihe von Methoden, die der GUI-Designer abfragt. Sie liefern jeweils ein Array von Descriptor-Objekten, von denen jedes Informationen über eine zu veröffentlichende Methode, Eigenschaft oder ein Icon zur Verfügung stellt. Wir wollen uns die drei wichtigsten von ihnen in den nachfolgenden Abschnitten ansehen.
Während der GUI-Designer die ihm bekannten Beans lädt, sucht er für jede von ihnen zunächst nach einer passenden BeanInfo-Klasse. Findet er eine solche, wird sie verwendet und der Low-Level-Introspectionsvorgang wird gar nicht erst angestoßen. Daraus folgt insbesondere, dass in diesem Fall nur die in der BeanInfo-Klasse angegebenen Merkmale zur Verfügung stehen. Ist es beispielsweise nötig, eine BeanInfo-Klasse zur Verfügung zu stellen, weil dem Designer eine besondere Methode bekanntgemacht werden soll, die nicht den getter-/setter-Konventionen entspricht, muss die BeanInfo-Klasse auch alle anderen Eigenschaften der Bean explizit bekanntmachen. Andernfalls würde der GUI-Designer nur die spezielle Methode sehen, alle anderen Merkmale aber ignorieren. |
|
public Image getIcon(int iconKind) |
java.beans.BeanInfo |
Der GUI-Designer ruft getIcon auf, um herauszufinden, ob die Bean ein Icon zur Darstellung in der Toolbox zur Verfügung stellen kann. Als Parameter wird dabei eine der symbolischen Konstanten ICON_COLOR_16x16, ICON_MONO_16x16, ICON_COLOR_32x32 oder ICON_MONO_32x32 übergeben. Kann die BeanInfo-Klasse ein Icon in der gewünschten Form zur Verfügung stellen, muss sie ein passendes Image-Objekt erzeugen und an den Aufrufer zurückgeben. Ist das nicht der Fall, sollte null zurückgegeben werden.
Ist die BeanInfo-Klasse aus SimpleBeanInfo abgeleitet, ist das Erzeugen eines Image-Objekts einfach. SimpleBeanInfo besitzt eine Methode loadImage, die bei Übergabe eines Dateinamens die ensprechende Bilddatei lädt und daraus ein Image erzeugt. |
|
public PropertyDescriptor[] getPropertyDescriptors() |
java.beans.BeanInfo |
Die Methode getPropertyDescriptors wird aufgerufen, um ein Array von PropertyDescriptor-Objekten zur Verfügung zu stellen. Es enthält für jede öffentliche Eigenschaft der Bean ein Element, das dessen Merkmale beschreibt. Ein PropertyDescriptor kann recht einfach instanziert werden:
public PropertyDescriptor(String propertyName, Class<?> beanClass) throws IntrospectionException |
java.beans.PropertyDescriptor |
Lediglich der Name der Eigenschaft und das Klassenobjekt der zugehörigen Bean sind anzugeben. Weitere Merkmale können durch Methodenaufrufe hinzugefügt werden. Uns interessiert an dieser Stelle lediglich die Methode setPropertyEditorClass, auf die wir in Abschnitt 46.6.2 zurückkommen werden.
Für jedes von getPropertyDescriptors zurückgegebene Element erwartet der GUI-Designer eine setter- und eine getter-Methode, deren Signatur dem bekannten Schema entsprechen muss. |
|
public MethodDescriptor[] getMethodDescriptors() |
java.beans.BeanInfo |
Zusätzliche Methoden können dem GUI-Designer durch Überlagern von getMethodDescriptors bekanntgemacht werden. Zurückgegeben wird ein Array, das für jede Methode ein Objekt des Typs MethodDescriptor enthält. Darin wird im Wesentlichen ein Method-Objekt gekapselt (siehe Abschnitt 45.3):
public MethodDescriptor(Method method) |
java.beans.MethodDescriptor |
Nach diesen Vorbemerkungen können wir eine geeignete BeanInfo-Klasse für die LightBulb-Komponente entwickeln:
001 /* LightBulbBeanInfo.java */ 002 003 import java.awt.*; 004 import java.beans.*; 005 import java.lang.reflect.*; 006 007 public class LightBulbBeanInfo 008 extends SimpleBeanInfo 009 { 010 public Image getIcon(int iconKind) 011 { 012 String imgname = "bulbico16.gif"; 013 if (iconKind == BeanInfo.ICON_MONO_32x32 || 014 iconKind == BeanInfo.ICON_COLOR_32x32) { 015 imgname = "bulbico32.gif"; 016 } 017 return loadImage(imgname); 018 } 019 020 public PropertyDescriptor[] getPropertyDescriptors() 021 { 022 try { 023 PropertyDescriptor pd1 = new PropertyDescriptor( 024 "lightOn", 025 LightBulb.class 026 ); 027 //pd1.setPropertyEditorClass(LightBulbLightOnEditor1.class); 028 PropertyDescriptor[] ret = {pd1}; 029 return ret; 030 } catch (IntrospectionException e) { 031 System.err.println(e.toString()); 032 return null; 033 } 034 } 035 036 public MethodDescriptor[] getMethodDescriptors() 037 { 038 MethodDescriptor[] ret = null; 039 try { 040 Class<LightBulb> bulbclass = LightBulb.class; 041 Method meth1 = bulbclass.getMethod("toggleLight", null); 042 ret = new MethodDescriptor[1]; 043 ret[0] = new MethodDescriptor(meth1); 044 } catch (NoSuchMethodException e) { 045 //ret bleibt null 046 } 047 return ret; 048 } 049 } |
LightBulbBeanInfo.java |
Wird die Klasse übersetzt und in die jar-Datei für die Beanbox aufgenommen, verändert sich die Präsentation der LightBulb-Bean in der Beanbox:
Während die ersten beiden Veränderungen nach dem Starten der Beanbox offensichtlich sind, können wir die letzte überprüfen, indem wir den Aufruf von toggleLight auf das Ereignis einer anderen Bean legen. Dazu platzieren wir im GUI-Designer zunächst eine LightBulb- und eine LightedPushButton-Bean. Anschließend markieren wir die LightedPushButton-Bean und rufen den Menüpunkt »Edit.Events.PropertyChange.propertyChange« auf. Die rote Verbindungslinie ziehen wir auf die LightBulb und fixieren sie dort mit einem Mausklick.
Aus der Auswahlliste der verfügbaren parameterlosen Methoden können wir toggleLight auswählen und »OK« drücken. Die Beanbox generiert und übersetzt nun eine Adapterklasse, die bei jedem PropertyChangeEvent des LightedPushButton die Methode toggleLight der LightBulb aufruft. Nach jedem Drücken des Buttons verändert die Lampe also ihren Zustand. Die Lampe kann natürlich nach wie vor auch im Eigenschaftenfenster an- und ausgeschaltet werden.
Die Beanbox und andere GUI-Designer stellen für einfache Eigenschaften vordefinierte Editoren zur Verfügung, mit denen ihr Wert verändert werden kann. Bereits bei indizierten Eigenschaften muss die Beanbox aber passen. Auch für Objekttypen kann ein GUI-Designer keinen Standardeditor zur Verfügung stellen, weil er die Konfigurationsschnittstelle des Objekts nicht kennt. Für diesen Zweck bietet die Beans-Architektur die Möglichkeit, eigene Editoren zu definieren und bestimmten Eigenschaften von Beans zuzuordnen.
In Zeile 027 von Listing 46.10 ist der Aufruf von setPropertyEditorClass auskommentiert, damit die Beanbox den eingebauten Editor für boolesche Werte verwendet. Durch Entfernen des Kommentars erhält der PropertyDescriptor der Eigenschaft »lightOn« die Information, dass zum Editieren dieser Eigenschaft ein benutzerdefinierter Editor verwendet werden soll.
Benutzerdefinierte Eigenschafteneditoren werden üblicherweise aus der Klasse PropertyEditorSupport des Pakets java.beans abgeleitet. Sie besitzt eine Reihe von Methoden, die in eigenen Editoren überlagert werden:
public void setValue(Object value) public Object getValue() public String getAsText() public void setAsText(String text) throws java.lang.IllegalArgumentException public String[] getTags() public boolean isPaintable() public boolean supportsCustomEditor() public void paintValue(Graphics g, Rectangle box) public Component getCustomEditor() |
java.beans.PropertyEditorSupport |
In den nächsten Abschnitten werden wir drei Editoren für die Eigenschaft »lightOn« vorstellen. Sie machen in unterschiedlicher Weise von diesen Methoden Gebrauch.
Eine weitere Variante zur Konfiguration von Beans (auf die wir hier nicht näher eingehen wollen) ist die Implementierung eines Customizer. Dieser erhält volle Kontrolle über die Konfiguration der Bean. Er kann insbesondere auch mehrere Eigenschaften gleichzeitig verändern und auf diese Weise aufwändige Editoren zur Verfügung stellen, die den bekannten Wizards oder Assistenten moderner Entwicklungssysteme ähneln. |
|
Der erste Editor ist sehr einfach aufgebaut. Er stellt ein Textfeld zur Verfügung, in dem die Werte »an« und »aus« eingegeben werden können, um den Zustand der Lampe umzuschalten.
Jeder der nachfolgend vorgestellten Editoren kapselt eine boolesche Membervariable currentvalue, die den aktuellen Zustand der Lampe festhält. Sie wird vom GUI-Designer durch Aufruf von getValue abgefragt und durch setValue gesetzt. Beide Methoden operieren mit Objekttypen, d.h., es ist jeweils eine Konvertierung zwischen boolean und Boolean erforderlich. In unserem Beispiel werden zusätzlich die Methoden getAsText und setAsText überlagert, um auf die textuelle Repräsentation (»an« und »aus«) des booleschen Werts zuzugreifen.
001 /* LightBulbLightOnEditor1.java */ 002 003 import java.beans.*; 004 005 public class LightBulbLightOnEditor1 006 extends PropertyEditorSupport 007 { 008 boolean currentvalue; 009 010 public void setValue(Object value) 011 { 012 currentvalue = ((Boolean)value).booleanValue(); 013 } 014 015 public Object getValue() 016 { 017 return new Boolean(currentvalue); 018 } 019 020 public String getAsText() 021 { 022 return "" + (currentvalue ? "an" : "aus"); 023 } 024 025 public void setAsText(String text) 026 throws java.lang.IllegalArgumentException 027 { 028 if (text.equalsIgnoreCase("an")) { 029 currentvalue = true; 030 } else if (text.equalsIgnoreCase("aus")) { 031 currentvalue = false; 032 } else { 033 throw new IllegalArgumentException(text); 034 } 035 firePropertyChange(); 036 } 037 } |
LightBulbLightOnEditor1.java |
Der erste Editor sieht in der Beanbox so aus:
Abbildung 46.8: LightBulbLightOnEditor1 in der Beanbox
Ist der Wertevorrat für die zu editierende Eigenschaft endlich, kann es bequemer sein, dem Anwender alle möglichen Varianten in einer Combox anzubieten. Dazu muss lediglich die Methode getTags überlagert und ein Array von Strings mit den möglichen Werten zurückgegeben werden:
001 /* LightBulbLightOnEditor2.java */ 002 003 import java.beans.*; 004 005 public class LightBulbLightOnEditor2 006 extends PropertyEditorSupport 007 { 008 boolean currentvalue; 009 010 public void setValue(Object value) 011 { 012 currentvalue = ((Boolean)value).booleanValue(); 013 } 014 015 public Object getValue() 016 { 017 return new Boolean(currentvalue); 018 } 019 020 public String getAsText() 021 { 022 return "" + (currentvalue ? "an" : "aus"); 023 } 024 025 public void setAsText(String text) 026 throws java.lang.IllegalArgumentException 027 { 028 System.out.println("setAsText(" + text + ")"); 029 if (text.equalsIgnoreCase("an")) { 030 currentvalue = true; 031 } else if (text.equalsIgnoreCase("aus")) { 032 currentvalue = false; 033 } else { 034 throw new IllegalArgumentException(text); 035 } 036 firePropertyChange(); 037 } 038 039 public String[] getTags() 040 { 041 return new String[]{"aus", "an"}; 042 } 043 } |
LightBulbLightOnEditor2.java |
Der zweite Editor sieht in der Beanbox so aus:
Abbildung 46.9: LightBulbLightOnEditor2 in der Beanbox
Zusätzlich zu den beiden einfachen Varianten kann die Bean aber auch einen vollkommen frei definierten Editor zur Verfügung stellen. Sie ist in diesem Fall sowohl für die Darstellung der Eigenschaft in der Eigenschaftenliste als auch für das Verändern des aktuellen Werts mit Hilfe einer eigenen Komponente verantwortlich. Dazu werden weitere Methoden überlagert:
Die folgende Klasse LightBulbLightOnEditor3 stellt einen sehr einfachen Editor zur Verfügung. Er besteht lediglich aus zwei nebeneinanderliegenden Rechtecken in den Farben Blau und Gelb. Die blaue Farbe symbolisiert den ausgeschalteten Zustand, die gelbe den eingeschalteten. Der aktuelle Zustand der Lampe wird durch einen schwarzen Rahmen um eines der beiden Rechtecke angezeigt. Durch einfaches Anklicken eines der beiden Farbfelder kann der Zustand umgeschaltet werden.
001 /* LightBulbLightOnEditor3.java */ 002 003 import java.awt.*; 004 import java.awt.event.*; 005 import java.beans.*; 006 007 public class LightBulbLightOnEditor3 008 extends PropertyEditorSupport 009 { 010 boolean currentvalue; 011 012 public void setValue(Object value) 013 { 014 currentvalue = ((Boolean)value).booleanValue(); 015 } 016 017 public Object getValue() 018 { 019 return new Boolean(currentvalue); 020 } 021 022 public boolean isPaintable() 023 { 024 return true; 025 } 026 027 public boolean supportsCustomEditor() 028 { 029 return true; 030 } 031 032 public Component getCustomEditor() 033 { 034 return new LightOnCustomEditor(); 035 } 036 037 public void paintValue(Graphics g, Rectangle box) 038 { 039 //Linke Box: blau, Lampe ausgeschaltet 040 g.setColor(Color.blue); 041 g.fillRect(box.x, box.y, box.width / 2, box.height); 042 //Rechte Box: blau, Lampe angeschaltet 043 g.setColor(Color.yellow); 044 g.fillRect(box.x + box.width / 2, box.y, box.width / 2, box.height); 045 //Rahmen 046 g.setColor(Color.black); 047 for (int i = 0; i < 2; ++i) { 048 g.drawRect( 049 box.x + (currentvalue ? box.width / 2 : 0) + i, 050 box.y + i, 051 box.width / 2 - 1 - (2 * i), 052 box.height - 1 - (2 * i) 053 ); 054 } 055 } 056 057 //---Private Klassen---------------------------------------- 058 class LightOnCustomEditor 059 extends Canvas 060 { 061 public LightOnCustomEditor() 062 { 063 addMouseListener( 064 new MouseAdapter() { 065 public void mouseClicked(MouseEvent event) 066 { 067 currentvalue = (event.getX() > getSize().width / 2); 068 LightBulbLightOnEditor3.this.firePropertyChange(); 069 repaint(); 070 } 071 } 072 ); 073 } 074 075 public void paint(Graphics g) 076 { 077 paintValue(g, new Rectangle(0, 0, getSize().width, getSize().height)); 078 } 079 080 public Dimension getPreferredSize() 081 { 082 return new Dimension(120, 60); 083 } 084 } 085 } |
LightBulbLightOnEditor3.java |
Der Editor wird durch die aus Canvas abgeleitete lokale Klasse LightOnCustomEditor implementiert. Ihre bevorzugte Größe ist fest eingestellt und sie verwendet die paintValue-Methode der umgebenden Klasse zur Bildschirmdarstellung. Bei einem Mausklick verändert sie deren Membervariable currentvalue und teilt dies dem GUI-Designer durch Aufruf von firePropertyChange mit. Anschließend ruft sie repaint auf, um den Rahmen neu zu zeichnen.
Der dritte Editor sieht in der Eigenschaftenliste so aus:
Abbildung 46.10: LightBulbLightOnEditor3 in der Beanbox
Wird er angeklickt, erzeugt die Beanbox eine vergrößerte Variante, in der der Wert verändert werden kann:
Abbildung 46.11: LightBulbLightOnEditor3 in der Eigenschaftenliste
Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 7. Auflage, Addison Wesley, Version 7.0 |
<< | < | > | >> | API | © 1998, 2011 Guido Krüger & Heiko Hansen, http://www.javabuch.de |