Titel   Inhalt   Suchen   Index   API  Go To Java 2, Zweite Auflage, Handbuch der Java-Programmierung
 <<    <     >    >>  Kapitel 44 - Beans

44.6 BeanInfo-Klassen und Property-Editoren



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:

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:

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   API  Go To Java 2, Zweite Auflage, Addison Wesley, Version 2.0
 <<    <     >    >>  © 2000 Guido Krüger, http://www.gkrueger.com