Titel | Inhalt | Suchen | Index | API | Go To Java 2, Zweite Auflage, Handbuch der Java-Programmierung |
<< | < | > | >> | Kapitel 44 - Beans |
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.
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 } |
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 } |
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. |
|
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. |
|
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.
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.
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. |
|
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 |
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. |
|
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 |
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 |