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

## 44.2 Entwurf einer einfachen Bean

- 44.2 Entwurf einer einfachen Bean
  - 44.2.1 Grundsätzliche Architektur
  - 44.2.2 Grafische Darstellung
  - 44.2.3 Eigenschaften
    - Objekte als Eigenschaften
    - Indizierte Eigenschaften
  - 44.2.4 Implementierung
  - 44.2.5 Verwendung der Bean

Wir wollen uns in diesem Abschnitt mit dem Entwurf einer einfachen Bean beschäftigen. Dazu werden wir eine Klasse `LightBulb` entwerfen, die eine kleine Glühlampe grafisch darstellt. Sie kann wahlweise an- oder ausgeschaltet werden. Diese Klasse wird alle notwendigen Eigenschaften einer Bean aufweisen und kann im GUI-Designer und im laufenden Programm verwendet werden.

### 44.2.1 Grundsätzliche Architektur

Da wir eine GUI-Komponente realisieren wollen, folgen wir analog der in Kapitel 33 beschriebenen Vorgehensweise und leiten unsere Klasse `LightBulb` aus der Klasse `Canvas` des Pakets `java.awt` ab. Um die Eigenschaft der Serialisierbarkeit zu erfüllen, implementiert die Klasse das Interface `Serializable`. Der Anschaltzustand wird in der Instanzvariablen `lighton` festgehalten:

    001 import java.awt.*;
    002 import java.awt.event.*;
    003 import java.io.*;
    004 import java.beans.*;
    005
    006 public class LightBulb
    007 extends Canvas
    008 implements Serializable
    009 {
    010   protected boolean lighton;
    011   transient protected Image   offimage;
    012   transient protected Image   onimage;
    013   ...
    014 }

Listing 44.1: Deklaration der Klasse LightBulb

### 44.2.2 Grafische Darstellung

Die grafische Darstellung der Glühbirne soll durch das Anzeigen von Bitmaps erfolgen, die bei der Instanzierung aus zwei gif-Dateien bulb1.gif und bulb2.gif geladen werden. Abhängig vom Zustand der Variable `lighton` wird in der überlagerten `paint`-Methode jeweils eine der beiden Bitmaps angezeigt. Das Verfahren entspricht im wesentlichen dem in Abschnitt 34.1.2 beschriebenen. Auch die Methoden `getPreferredSize` und `getMinimumSize` werden überlagert, damit die Komponente einem eventuell vorhandenen Layoutmanager die gewünschte Größe (in diesem Fall 40 mal 40 Pixel) mitteilen kann.

Etwas anders als bisher beschrieben arbeitet die Routine zum Laden der Bilddateien. Damit die Bilddatei auch gefunden wird, wenn die Klasse aus einer .jar-Datei geladen wurde (das ist beispielsweise beim Laden von serialisierten Beans oder beim Import in einen GUI-Designer der Fall), kann nicht einfach der Dateiname an `createImage` bzw. `getImage` übergeben werden. Statt dessen konstruieren wir mit Hilfe des Klassenobjekts unserer Bean und dessen Methode `getResource` ein `URL`-Objekt, das wir an `createImage` übergeben können:

    001 private Image getImageResource(String name)
    002 {
    003   Image img = null;
    004   try {
    005     java.net.URL url = getClass().getResource(name);
    006     img = getToolkit().createImage(url);
    007     MediaTracker mt = new MediaTracker(this);
    008     mt.addImage(img, 0);
    009     try {
    010       //Warten, bis das Image vollständig geladen ist,
    011       mt.waitForAll();
    012     } catch (InterruptedException e) {
    013       //nothing
    014     }
    015   } catch (Exception e) {
    016     System.err.println(e.toString());
    017   }
    018   return img;
    019 }

Listing 44.2: Laden einer Image-Ressource

Diese Vorgehensweise basiert darauf, daß jede geladene Klasse ihren Classloader (also das Objekt, das für das Laden der Klasse verantwortlich war) kennt und an diesen Aufrufe zum Laden von Ressourcen delegieren kann. Der beim Laden eines Objekts aus einer .jar-Datei verwendete Classloader unterscheidet sich dabei sehr wohl von dem Bootstrap Loader, der System- und Anwendungsklassen aus .class-Dateien lädt. Diese Unterscheidung wird in dem von `getResource` gelieferten URL gekapselt und vom AWT-Toolkit beim Aufruf von `createImage` aufgelöst.

|  | Nach dem Aufruf von `createImage` sorgt ein `MediaTracker` dafür, das die Bilddateien erst vollständig geladen werden, bevor die Methode terminiert. Diese Technik verhindert, daß `paint` aufgerufen wird, während die `Image`-Objekte noch nicht vollständig initialisiert sind. Details dazu wurden in Abschnitt 34.1.1 vorgestellt. |  |
Hinweis

### 44.2.3 Eigenschaften

Wie im einleitenden Abschnitt dargelegt, sind Eigenschaften ein wesentliches Designmerkmal von Beans. Eine Eigenschaft ist eigentlich nur eine Membervariable, die über öffentliche Methoden gelesen und geschrieben werden kann. Eine Bean kann beliebig viele Eigenschaften haben, jede von ihnen besitzt einen Namen und einen Datentyp. Die Bean-Designkonventionen schreiben vor, daß auf eine Eigenschaft mit dem Namen name und dem Datentyp typ über folgende Methoden zugegriffen werden soll:

    public typ getName();

    public void setName(typ newValue);

|  | Diese beiden Methoden bezeichnet man auch als getter- und setter-Methoden. |  |
Hinweis

Unsere Beispiel-Bean hat eine einzige Eigenschaft lightOn vom Typ `boolean`. Ihre getter-/setter-Methoden haben demnach folgende Signatur (der erste Buchstabe des Eigenschaftsnamens wird großgeschrieben, da er hinter dem "get" bzw. "set" steht):

    public boolean getLightOn();

    public void setLightOn(boolean newvalue);

Auf diese Weise können getter- und setter-Methoden für alle primitiven Datentypen geschrieben werden. Der GUI-Designer erkennt Eigenschaftennamen und -typen anhand der Signaturen und stellt automatisch einen passenden Editor dafür zur Verfügung.

#### Objekte als Eigenschaften

Neben primitiven Typen ist auch die Übergabe von Objekttypen erlaubt. Die Signaturkonventionen entsprechen genau denen von primitiven Typen. Bei Objekteigenschaften kann allerdings nicht unbedingt davon ausgegangen werden, daß der GUI-Designer einen geeigneten Editor zur Verfügung stellen kann. Zwar besitzt der GUI-Designer für die häufig benötigten Objekttypen `Color` und `Font` standardmäßig geeignete Editoren. Bei einem selbstdefinierten Objekttyp ist das natürlich nicht der Fall. Hier muß der Entwickler nötigenfalls selbst einen Editor entwickeln und dem Designer zur Verfügung stellen. Wir werden in Abschnitt 44.6.2 zeigen, wie das gemacht wird.

#### Indizierte Eigenschaften

Anstelle eines Einzelwertes kann eine Eigenschaft auch durch ein Array von Werten repräsentiert werden. Sie wird in diesem Fall als indizierte Eigenschaft bezeichnet. Die getter-/setter-Methoden sehen dann so aus:

    public typ getName(int index);

    public void setName(int index, typ newValue);

Es werden also keine Arrays übergeben, sondern die Methoden erwarten jeweils den Index der gewünschten Eigenschaft als zusätzliches Argument. Für das Einhalten der Arraygrenzen ist der Aufrufer selbst verantwortlich. Ist die Arraygröße variabel, könnte die Bean sie in einer zweiten Eigenschaft festhalten und über eigene getter-/setter-Methoden verfügbar machen.

|  | Auch indizierte Eigenschaften werden vom GUI-Designer automatisch erkannt und sollten zum Aufruf eines geeigneten Editors führen. Die Beanbox selbst (also die von SUN zur Verfügung gestellte Referenzimplementierung eines GUI-Designers), auf die wir in Abschnitt 44.3 eingehen werden, kann allerdings nicht mit indizierten Eigenschaften umgehen. |  |
Hinweis

### 44.2.4 Implementierung

Nach diesen Vorbemerkungen ist die Implementierung der Bean zur Darstellung der Glühbirne keine große Hürde mehr:

    001 /* LightBulb.java */
    002
    003 import java.awt.*;
    004 import java.awt.event.*;
    005 import java.io.*;
    006 import java.beans.*;
    007
    008 public class LightBulb
    009 extends Canvas
    010 implements Serializable
    011 {
    012   //Instanzvariablen
    013   protected boolean lighton;
    014   transient protected Image offimage;
    015   transient protected Image onimage;
    016
    017   //Methoden
    018   public LightBulb()
    019   {
    020     lighton = false;
    021     initTransientState();
    022   }
    023
    024   //Getter/Setter Licht an/aus
    025   public void setLightOn(boolean on)
    026   {
    027     if (on != this.lighton) {
    028       this.lighton = on;
    029       repaint();
    030     }
    031   }
    032
    033   public boolean getLightOn()
    034   {
    035     return this.lighton;
    036   }
    037
    038   public void toggleLight()
    039   {
    040     setLightOn(!getLightOn());
    041   }
    042
    043   //Implementierung der Oberfläche
    044   public void paint(Graphics g)
    045   {
    046     int width = getSize().width;
    047     int height = getSize().height;
    048     int xpos = 0;
    049     if (width > 40) {
    050       xpos = (width - 40) / 2;
    051     }
    052     int ypos = 0;
    053     if (height > 40) {
    054       ypos = (height - 40) / 2;
    055     }
    056     g.drawImage(
    057       (this.lighton ? onimage : offimage),
    058       xpos,
    059       ypos,
    060       this
    061     );
    062   }
    063
    064   public Dimension getPreferredSize()
    065   {
    066     return new Dimension(40, 40);
    067   }
    068
    069   public Dimension getMinimumSize()
    070   {
    071     return new Dimension(40, 40);
    072   }
    073
    074   //Private Methoden
    075   private void initTransientState()
    076   {
    077     offimage = getImageResource("bulb1.gif");
    078     onimage  = getImageResource("bulb2.gif");
    079   }
    080
    081   private void readObject(ObjectInputStream stream)
    082   throws IOException, ClassNotFoundException
    083   {
    084     stream.defaultReadObject();
    085     initTransientState();
    086   }
    087
    088   private Image getImageResource(String name)
    089   {
    090     Image img = null;
    091     try {
    092       java.net.URL url = getClass().getResource(name);
    093       img = getToolkit().createImage(url);
    094     } catch (Exception e) {
    095       System.err.println(e.toString());
    096     }
    097     return img;
    098   }
    099 }
| LightBulb.java

Listing 44.3: Die Bean zur Anzeige einer Glühbirne

Der Konstruktor initialisiert zunächst die Zustandsvariable `lighton` und ruft dann die Methode `initTransientState` auf, um die beiden gif-Dateien zu laden. Durch Aufruf von `setLightOn` kann die Beleuchtung wahlweise an- oder ausgeschaltet werden; `getLightOn` liefert den aktuellen Zustand. In `paint` wird - abhängig vom aktuellen Zustand - jeweils eines der beiden Images ausgegeben. Die Umrechnungsroutinen dienen dazu, die Images zentriert auszugeben, wenn mehr Platz als nötig zur Verfügung steht.

|  | Bemerkenswert ist, daß die Methode `readObject` überlagert wurde. Sie wird immer dann aufgerufen, wenn die zuvor serialisierte Bean per Deserialisierung instanziert wird. In diesem Fall würde nämlich der Konstruktor des Objekts gar nicht aufgerufen werden (siehe Abschnitt 41.1.3) und die `Image`-Variablen blieben uninitialisiert. Ihre Initialisierung haben wir deshalb in die Methode `initTransientState` verlagert, die sowohl aus dem Konstruktor als auch aus `readObject` aufgerufen wird. Damit wird die Bean in beiden Fällen (Instanzierung per `new` und Deserialisierung) vollständig initialisiert. In seiner eigentlichen Funktion ruft `readObject` lediglich `defaultReadObject` auf, um die Standard-Deserialisierung auszuführen. |  |
Hinweis

### 44.2.5 Verwendung der Bean

Zum Abschluß wollen wir uns ansehen, wie die erstellte Bean in ein einfaches Programm eingebunden werden kann. Dazu bedienen wir uns exakt der Techniken, die bereits in den Kapiteln 31 bis 34 beschrieben wurden. Tatsächlich unterscheidet sich die Verwendung einer selbstentwickelten Bean nicht vom Einbinden einer vordefinierten Komponente.

    001 /* Listing4404.java */
    002
    003 import java.awt.*;
    004 import java.awt.event.*;
    005
    006 public class Listing4404
    007 extends Frame
    008 {
    009   public Listing4404()
    010   {
    011     super("Bean einbinden");
    012     setLayout(new FlowLayout());
    013     setBackground(Color.lightGray);
    014     LightBulb bulb1 = new LightBulb();
    015     bulb1.setLightOn(false);
    016     add(bulb1);
    017     LightBulb bulb2 = new LightBulb();
    018     bulb2.setLightOn(true);
    019     add(bulb2);
    020     addWindowListener(
    021       new WindowAdapter() {
    022         public void windowClosing(WindowEvent event)
    023         {
    024           System.exit(0);
    025         }
    026       }
    027     );
    028   }
    029
    030   public static void main(String[] args)
    031   {
    032     Listing4404 frm = new Listing4404();
    033     frm.setLocation(100, 100);
    034     frm.pack();
    035     frm.setVisible(true);
    036   }
    037 }
| Listing4404.java

Listing 44.4: Einbinden einer einfachen Bean

Die Ausgabe des Programms sieht wie folgt aus:

Abbildung 44.1: Die Glühlampen-Bean

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