[Teil 6: Praktische Standardklassen]

Einstieg in Java
Teil 7: Applet und Applikation

Von Enno Runne und Hendrik C.R. Lock

Der AWT (Abstract Window Toolkit) von Java bietet viele Möglichkeiten, um graphische Benutzeroberflächen zu bauen. Die vielleicht interessanteste Komponenente, die Java bei vielen Hackern erst beliebt gemacht hat, ist das Applet. Ein Applet ist eine relativ autarke Komponente, die über das Netz verschickt und in Browsern am anderen Ende angezeigt werden kann. In diesem Teil legen wir die Grundlagen, um mit Applets zu programmieren.


Einführung

Mit AWT stellt Java eine Standardbibliothek zur Verfügung, mit deren Hilfe graphische Oberflächen realisert werden können. Ein wichtiges Merkmal von AWT ist seine Plattformunabhängigkeit. Wenn ich also ein Fenster (Klasse Frame) aus AWT verwende, so wird es auf jeder unterstützten Plattform als ein Fenster mit Menuleiste dargestellt. AWT erreicht diese Plattformunabhängikeit, indem es auf dem jeweiligen graphischen Subsystem aufsetzt. Dadurch sieht ein Fenster auf einem Mac wie ein typisches Mac Fenster aus, unter Windows NT entspricht es dem typischen Microsoft Look&Feel, und unter UNIX ist es oft ein X-Fenster. Die Einhaltung der jeweiligen Look&Feel-Standards setzt der Plattformunabhängigkeit allerdings auch ihre Grenzen: Das Layout läßt sich nämlich nicht immer exakt nach den eigenen Wünschen fixieren, weil z.B. die Randstärken von Fenstern oder Schattierungen vom jeweiligen Zielsystem abhängen können.

In AWT programmierte Oberflächen können nicht nur als eigenständige Anwendungen ablaufen, sondern auch als sogenannte Applets verpackt in HTML-Seiten (Hyper Text Markup Language) eingebunden und über das Netz (WWW) verschickt werden. Applets sind Objekte einer AWT-Klasse, die von Java-fähigen Browsern geladen und ausgeführt werden können (z.B. vom Netscape-Browser oder dem Internet Explorer von Microsoft). Enthält eine HTML-Seite ein Applet, so lädt der Browser die Klassendatei mit dem ausführbaren Bytecode von der URL der HTML-Seite. Die Ausführung erfolgt dann interpretiert (und damit langsam), oder wird mit Hilfe eine JIT-Übersetzers beschleunigt (Just-in-Time z.B. beim Internet Explorer).

Der eigentliche Spaß an AWT und Applets besteht darin, dass sich viele Benutzer des WWW ihre HTML-Seiten mit Animationen und graphischem Zauber aufpeppen. Wie man das macht, besprechen wir in dieser und kommenden Ausgaben. Aber auch bei kommerziellen Anwendungen haben AWT und das Applet ihren besonderen Platz: Eingabeformulare in HTML-Seiten und interaktive Dialoge können so realisiert werden, dass Daten bereits vor ihrer Versendung übers Netz auf Konsistenz überprüft und verschlüsselt werden.


Fenster

Eine graphischen Oberfläche wird immer in einem Fenster dargestellt, egal ob es sich um ein Applet oder eine eigenständige Anwendung handelt. Im Falle eines Applets stellt bereits der Browser das Fenster zur Verfügung, und das Applet selbst ist eine Komponente, die innerhalb eines Fensters offen und ohne eigenen Rahmen dargestellt wird.

Im AWT wird ein Fenster durch ein Objekt der Klasse Window oder Frame erzeugt. Ein Window ist ein rahmenloses Fenster ohne Menuleisten, während Frame ein Fenster mit Rahmen erzeugt, in das eine Menuleiste eingehängt werden kann. Rahmenlose Fenster werden vorwiegend für Fehlermeldungen verwendet. Folgende Anweisung erzeugt ein Fenster der Größe 300x200 und zeigt es an:

   Frame f = new Frame(" Titeltext des Fensters ");
   f.resize(300, 200);
   f.show();

Bild 1 zeigt die Darstellung unter Solaris.


Die Struktur von AWT

Bevor wir uns an die Programmierung mit AWT wagen, schauen wir uns die Klassenstruktur, und damit den Zusammenhang zwischen den wichtigsten Komponenten des AWT an.

Die Klasse Component bildet die Wurzel der Klassenhierchie und legt damit die allgemeinen Methoden aller Komponenten fest. Beispiele für solche Methoden sind show() und resize(), mit denen eine Komponente angezeigt b.z.w. in ihren Ausmaßen verändert wird. Bild 2 gibt die Komponentenhierarchie wieder. Direkt unterhalb von Component stehen die Grundkomponenten wie Button, Label, Canvas und Scrollbar. Direkt von Component erbt die abstrakte Klasse Container, die einen Behältertyp einführt. Ein Behälter kann viele weitere Komponenten enthalten, z.B. können wir in ein Fenster mehrere Knöpfe einfügen. Die Methode zum Einfügen heißt add().

Die wichtigsten Behälterklassen sind Frame und Panel. Wie bereits erwähnt, sind Frames eigenständige Fenster, denen man auch Menuleisten anheften kann. Panels hingegen sind reine Behälter, die nahtlos in der Komponenente aufgehen, in die sie eingefügt werden. Damit sind Panels hervorragend für die Strukturierung graphischer Oberflächen geeignet: zusammenhängende Knopfleisten und Texte werden geschickterweise in ein gemeinsames Panel eingehängt, und aus solchen Aggregaten lassen sich dann komplexere Einheiten wie ein Mosaik aufbauen.

Applet ist eine spezielle Unterklasse von Panel, die nicht nur als Behälter dient, sondern die enthaltenen Komponenten auch in einem Browser anzeigbar macht. Dazu werden zusätzliche Methoden benötigt, die in einem späteren Teil ausführlicher behandelt werden sollen.


Der erste Schritt: ein Applet

Nach diesen trockenen Vorbereitungen können wir uns nun auf unser erstes Applet stürzen. Wir verwenden als oberste Komponente ein Applet, denn dadurch gewinnen wir die Freiheit, die Komponente als Applet in einen Browser zu laden oder als Panel in ein Fenster einzuhängen.

Unser erstes Applet setzt mit Hilfe eines Labels den Text "Hallo Welt" auf einer HTML-Seite ab. Durch die Methode setBackground() wählen wir die Hintergrundfarbe Weiss, so dass wir die vom Applet belegt Fläche sehen können (vorausgesetzt, der Browser benutzt Grau als Standardhintergrund). Ausserdem wechseln wir die Schriftart und ihre Größe.

Applet Code, Applet und HTML Code fürs Applet
import java.applet.Applet;
import java.awt.*;

public class AppletBeispiel1
  extends Applet
{
  public AppletBeispiel1() {
  Label l1 = new Label( "Hallo Welt" );
  add(l1);  	// ins Applet einhängen
  setBackground( Color.white );
  resize( 200, 200 );
  show();	// anzeigen
  }
}
<applet Codebase="code7"
  Code="AppletBeispiel1"
  Width=200 Height=50>
</applet>
Das Applet wird durch die Marke <applet> in eine HTML-Seite eingebunden. Ihre Attribute Code und Codebase geben dabei den Namen und den Pfad (die URL) der class-Datei an. In unserem Beipiel liegt AppletBeispiel1 im Unterverzeichnis code2 relativ zur URL-Adresse der HTML-Seite. Die Attribute Width und Height geben dabei den Ausschnitt des Applets an, der auf der Seite angezeigt wird.


Ein Skizzenblock

Als nächtes werden wir mit kleinem Aufwand einen elektronischen Skizzenblock mit farbigen Stiften bauen. Dabei lernen wir aktive Schalterelemente, die Behandlung von Ereignissen in solchen Elementen (wie z.B. ein Mausklick), und Möglichkeiten zum Zeichnen graphischer Elemente kennen.

Als erstes definieren wir ein Applet, auf dessen Fläche Striche gezeichnet werden. Ein Strich wird gezeichnet, solange die aktivierte Maus über die Fläche gezogen wird. Die Aktivierung der Maus wird durch die Methode mouseDown() erkannt. Wird die Maus aktiviert, so sendet der AWT diese Methode an das Objekt desjenigen AWT-Elementes, in dem die Maus gerade steht. Genauso verhält es sich mit der Methode mouseDrag(). Sie wird von AWT aufgerufen, wenn sich die aktivierte Maus in der Fläche unseres Applet bewegt. In diesem Fall zeichnen also wir eine Linie zwischen der vorangehenden und der aktuellen Mauskoordinate.

Um Linien graphisch zeichnen zu können, benötigen wir in AWT ein Objekt der Klasse Graphics. Nun wird aber jede AWT-Komponente letztendlich mittels eines Objektes der Klasse Graphics dargestellt. Deshalb können wir uns das zum Applet gehörende Graphikobjekt mit der Methode getGraphics() besorgen. Der Methodenaufruf drawLine(x1,y1, x2, y2) malt dann eine Linie im Graphikobjekt zwischen den beiden Koordinaten. Die Farbe der Linie ist durch die Farbe des Vordergrundes bestimmt; sie wird mit setForeground() gesetzt. Ebenso wird die Hintergrundfarbe durch setBackground() bestimmt.

import java.applet.Applet;
import java.awt.*;

public class Skizze extends Applet
{
  private int letztesX, letztesY;

  public Skizze() {
    setForeground( Color.blue);
    setBackground( Color.white );
    resize( 600,350);
  }

  public boolean mouseDown(Event e, int x, int y) {
    letztesX = x; letztesY = y;
    return true;  // Mausereignis wurde behandelt
  }
  
  /** zeichne Linienzüge bei gedrückter Maus */
  public boolean mouseDrag(Event e, int x, int y) {
    Graphics g = getGraphics();
    g.drawLine( letztesX, letztesY, x, y);
    letztesX = x; letztesY = y;
    return true;
   }
}

Um dickere Linien zu erhalten, könnten wir auch Kreise am jeweiligen Ort der aktiven Maus malen:

    g.fillOval(x,y, 8, 8 ); 

Nun stellt sich allerdings ein unbeabsichtigter Effekt ein: je schneller wir die Maus ziehen, desto ausgedünnter werden die Linien, d.h. wir erhalten keine durchgehenden Linienzüge. Dies liegt daran, dass die Geschwindigkeit, mit der mouseDrag aufgerufen wird, so gering ist, dass nicht jede Koordinate erfasst wird.

Damit haben wir bereits zwei Ereignisse und ihre Behandlung kennengelernt: mouseDown und mouseDrag. Im nächsten Abschnitt erweitern wir die Ereignisbehandlung auf Schalter.


Schalten und Walten

Wir erweitern nun die Funktionalität des Skizzenblocks um Löschen der Fläche und Wechsel des Farbstiftes. Dazu führen wir Schalter der Klassen Button und Checkbox ein.

Der Code new Button("Beschriftung") erzeugt einen beschrifteten Knopf, der per Mausklick aktiviert werden kann. Bei seiner Aktivierung wird eine Ereignisbehandlung ausgelöst. Sie besteht darin, dass die Methode action() derjenigen innersten Komponente aufgerufen wird, in die der Knopf eingehängt worden ist.

Weil die Methode action() allerdings auch bei vielen anderen Ereignissen aufgerufen wird, müssen wir den Knopfdruck identifizieren. Nichts ist einfacher als das: wir merken uns den Verweis auf unseren Schalter und vergleichen ihn in action() mit dem Verweis des verursachenden Objektes. Das verursachende Objekt wird durch den Code

  event.target()  

identifiziert, wobei event ein Objekt der Klasse Event ist, das alle Informationen über das Ereignis transportiert.

Konnte das Ereignis behandelt werden, so geben wir in action() den Wert true an den Aufrufer zurück. Im anderen Fall geben wir false zurück, womit wir der umgebenden Komponente signalisieren, dass sie das Ereignis behandeln soll. Falls keine Komponente in dieser aufsteigenden Kette das Ereignis behandelt (es ist die Kette, in der Komponenten ineinander eingehängt worden sind), so wird es einfach ignoriert.

Für die Auswahl der Farbstifte brauchen wir nun Schalter, die sich "erinnern" können, dass sie bereits gedrückt worden sind. Der Schalter vom Typ Button kann das allerdings nicht. Außerdem soll immer nur einer aus vielen solcher Schalter gedrückt sein. Diese häufig anzutreffende Funktionalität bieten im AWT die Klassen Checkbox und CheckboxGroup. Der folgende Code

  CheckBox rot = new Checkbox( "rot", gg, false );  

erzeugt einen Schalter mit der Beschriftung "rot". Das Argument gg ist dabei ein Objekt der Klasse CheckboxGroup. Durch Bindung einer Reihe von Schaltern an eine solche Gruppe wird erreicht, dass immer nur ein einziger Schalter der Gruppe gedrückt sein kann. Das letzte Argument bestimmt, ob der Schalter initial gedrückt ist, oder nicht.

import java.applet.Applet;
import java.awt.*;

public class Skizze3
  extends Skizze
{
  private Button clear, red;
  private Checkbox rot, blau, sch;

  public Skizze3() {
    super();
	Panel p = new Panel();
	p.setBackground(Color.pink);
 	add(p);
	CheckboxGroup g = new CheckboxGroup();
	rot = new Checkbox( "rot", g, false );
	blau = new Checkbox( "blau", g, true );
	sch = new Checkbox( "sch", g, false );
	p.add( blau ); p.add( rot ); p.add(sch);
	clear = new Button("Löschen");
	Panel p2 = new Panel();
	p2.setBackground(Color.red);  // rot unterlegen
	p2.add(clear); p.add(p2);
  }

  public boolean action( Event e, Object o ) {
    if (e.target == clear ) {
	  Graphics g = getGraphics();
	  update(g);
	  return true;
	}
	else if (e.target == rot ) {
	  setForeground( Color.red );
	  return true;
	}
	else if (e.target == blau ) {
	  setForeground( Color.blue );
	  return true;
	}
 	else if (e.target == sch ) {
	  setForeground( Color.black );
	  return true;
	}
    return false;
  }
}

Ach ja, nun bleibt nur noch zu erwähnen, dass die neue Klasse ihre Funktionalität zum Malen von der Klasse Skizze erbt. Eine Demo wird im Bild 4 wiedergegeben.

Der elektronische Skizzenblock


[Teil 6: Praktische Standardklassen]