From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001
From: Sven Eisenhauer
+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:
+
+
+
+
+
+
+ Titel
+ Inhalt
+ Suchen
+ Index
+ DOC
+ Handbuch der Java-Programmierung, 5. Auflage
+
+ <<
+ <
+ >
+ >>
+ API
+ Kapitel 44 - Beans
+
+
+
+
+
+44.2 Entwurf einer einfachen Bean
+
+
+
+
+44.2.1 Grundsätzliche Architektur
+
+
+
+
+Listing 44.1: Deklaration der Klasse LightBulb
+
+
+
+
+
+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, dass 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, dass 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, dass 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, dass 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 muss 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, dass 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 Abschluss 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 + | DOC + | Handbuch der Java-Programmierung, 5. Auflage, Addison +Wesley, Version 5.0.1 + |
| << + | < + | > + | >> + | API + | © 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de + |