Titel   Inhalt   Suchen   Index   DOC  Handbuch der Java-Programmierung, 7. Auflage
 <<    <     >    >>   API  Kapitel 46 - Beans

46.6 BeanInfo-Klassen und Property-Editoren



46.6.1 BeanInfo-Klassen

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.

 Warnung 

getIcon

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.

 Tip 

getPropertyDescriptors

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.

 Hinweis 

getMethodDescriptors

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

Die Klasse LightBulbBeanInfo

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
Listing 46.10: Die Klasse LightBulbBeanInfo

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.

46.6.2 Property-Editoren

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.

 Hinweis 

LightBulbLightOnEditor1

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
Listing 46.11: Die Klasse LightBulbLightOnEditor1

Der erste Editor sieht in der Beanbox so aus:

Abbildung 46.8: LightBulbLightOnEditor1 in der Beanbox

LightBulbLightOnEditor2

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
Listing 46.12: Die Klasse LightBulbLightOnEditor2

Der zweite Editor sieht in der Beanbox so aus:

Abbildung 46.9: LightBulbLightOnEditor2 in der Beanbox

LightBulbLightOnEditor3

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
Listing 46.13: Die Klasse LightBulbLightOnEditor3

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