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

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