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

## 44.6 BeanInfo-Klassen und Property-Editoren

- 44.6 BeanInfo-Klassen und Property-Editoren
  - 44.6.1 BeanInfo-Klassen
    - getIcon
    - getPropertyDescriptors
    - getMethodDescriptors
    - Die Klasse LightBulbBeanInfo
  - 44.6.2 Property-Editoren
    - LightBulbLightOnEditor1
    - LightBulbLightOnEditor2
    - LightBulbLightOnEditor3

### 44.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 muß sie im selben Paket liegen wie die Bean-Klasse. Die BeanInfo-Klasse muß das Interface `BeanInfo` implementieren oder aus `SimpleBeanInfo` abgeleitet sein (letzteres bietet den Vorteil, daß 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 wichigsten 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, daß 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, muß 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, muß 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 44.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 muß. |  |
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 43.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 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 44.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 `LighBulb`-Bean in der Beanbox:

- In der Toolbox wird neben dem Namen das 16x16-Icon angezeigt.
- Der Eigenschafteneditor kennt nur noch die Eigenschaft "lightOn".
- Der GUI-Designer hat registriert, daß es in der Methodenliste von `LightBulb` zusätzlich eine parameterlose Methode `toggleLigth` gibt.

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 plazieren 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 nun `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.

### 44.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 muß die Beanbox aber passen. Auch für Objekttypen kann ein GUI-Designer keinen Standard-Editor 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 44.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, daß 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 Customizers. Dieser erhält volle Kontrolle über die Konfiguration der Bean. Er kann insbesondere auch mehrere Eigenschaften gleichzeitig verändern und auf diese Weise aufwendige 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.awt.*;
    004 import java.beans.*;
    005
    006 public class LightBulbLightOnEditor1
    007 extends PropertyEditorSupport
    008 {
    009   boolean currentvalue;
    010
    011   public void setValue(Object value)
    012   {
    013     currentvalue = ((Boolean)value).booleanValue();
    014   }
    015
    016   public Object getValue()
    017   {
    018     return new Boolean(currentvalue);
    019   }
    020
    021   public String getAsText()
    022   {
    023     return "" + (currentvalue ? "an" : "aus");
    024   }
    025
    026   public void setAsText(String text)
    027   throws java.lang.IllegalArgumentException
    028   {
    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 }
| LightBulbLightOnEditor1.java

Listing 44.11: Die Klasse LightBulbLightOnEditor1

Der erste Editor sieht in der Beanbox so aus:

Abbildung 44.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 muß 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.awt.*;
    004 import java.beans.*;
    005
    006 public class LightBulbLightOnEditor2
    007 extends PropertyEditorSupport
    008 {
    009   boolean currentvalue;
    010
    011   public void setValue(Object value)
    012   {
    013     currentvalue = ((Boolean)value).booleanValue();
    014   }
    015
    016   public Object getValue()
    017   {
    018     return new Boolean(currentvalue);
    019   }
    020
    021   public String getAsText()
    022   {
    023     return "" + (currentvalue ? "an" : "aus");
    024   }
    025
    026   public void setAsText(String text)
    027   throws java.lang.IllegalArgumentException
    028   {
    029     System.out.println("setAsText(" + text + ")");
    030     if (text.equalsIgnoreCase("an")) {
    031       currentvalue = true;
    032     } else if (text.equalsIgnoreCase("aus")) {
    033       currentvalue = false;
    034     } else {
    035       throw new IllegalArgumentException(text);
    036     }
    037     firePropertyChange();
    038   }
    039
    040   public String[] getTags()
    041   {
    042     return new String[]{"aus", "an"};
    043   }
    044 }
| LightBulbLightOnEditor2.java

Listing 44.12: Die Klasse LightBulbLightOnEditor2

Der zweite Editor sieht in der Beanbox so aus:

Abbildung 44.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 Wertes mit Hilfe einer eigenen Komponente verantwortlich. Dazu werden weitere Methoden überlagert:

- `isPaintable` gibt `true` zurück, um anzuzeigen, daß der Editor selbst für die Darstellung in der Eigenschaftenliste verantwortlich ist und dafür die Methode `paintValue` überlagert.
- `supportsCustomEditor` gibt ebenfalls `true` zurück, um anzuzeigen, daß ein eigener Eigenschafteneditor zur Verfügung steht und durch Aufruf von `getCustomEditor` beschafft werden kann. Der eigene Editor muß aus der Klasse `Component` abgeleitet sein; er wird vom GUI-Designer automatisch in einem Editorfenster plaziert.

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 44.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 44.10: LightBulbLightOnEditor3 in der Beanbox

Wird er angeklickt, erzeugt die Beanbox eine vergrößerte Variante, in der der Wert verändert werden kann:

Abbildung 44.11: LightBulbLightOnEditor3 in der Eigenschaftenliste

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