Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Kompendium der Informationstechnik
 von Sascha Kersken
EDV-Grundlagen, Programmierung, Mediengestaltung
Buch: Kompendium der Informationstechnik
gp Kapitel 6 Konzepte der Programmierung
  gp 6.1 Algorithmen und Datenstrukturen
    gp 6.1.1 Ein einfaches Praxisbeispiel
    gp 6.1.2 Sortier-Algorithmen
    gp 6.1.3 Such-Algorithmen
    gp 6.1.4 Ausgewählte Datenstrukturen
  gp 6.2 Reguläre Ausdrücke
    gp 6.2.1 Muster für reguläre Ausdrücke
    gp 6.2.2 Programmierung mit regulären Ausdrücken
  gp 6.3 Systemnahe Programmierung
    gp 6.3.1 Prozesse und Pipes
    gp 6.3.2 Threads
  gp 6.4 GUI- und Grafikprogrammierung
    gp 6.4.1 Zeichnungen und Grafiken erstellen
    gp 6.4.2 Animation
    gp 6.4.3 Programmierung fensterbasierter Anwendungen
    gp 6.4.4 Java-Applets
  gp 6.5 Zusammenfassung

gp

Prüfungsfragen zu diesem Kapitel (extern)


Galileo Computing

6.4 GUI- und Grafikprogrammierung  downtop

Anwendungen für grafische Benutzeroberflächen unterscheiden sich von Konsolenanwendungen dadurch, dass kein geordnetes Nacheinander von Ein- und Ausgabe stattfindet, sondern dass mehrere Bedienelemente parallel zur Verfügung stehen. Ein GUI-Programm muss also flexibel auf Befehle reagieren können, die in nicht vorhersagbarer Reihenfolge gegeben werden.

Grafische Oberflächen und objektorientierte Programmierung passen vorzüglich zueinander: Die Bedienelemente der GUI (in den gängigen Grafikbibliotheken meist Widgets genannt) lassen sich sehr bequem als autonome Objekte realisieren, die wie in einem Baukastensystem zusammengefügt werden können. Die Benutzerbefehle werden den einzelnen Komponenten als Ereignisse (Events) mitgeteilt, auf die jedes Widget individuell reagieren oder über die es Nachrichten (Messages) an andere Elemente versenden kann. Eine der ersten objektorientierten Programmiersprachen, SmallTalk, wurde sogar eigens zur Implementierung der ersten GUI bei Xerox PARC entwickelt.

Trotz der unbestreitbaren Vorteile der OOP existieren einige GUI-Bibliotheken für imperative Programmiersprachen, vor allem für C. Sie verwenden verschiedene Tricks, um objektorientierte Verfahren wie die Nachrichtenübermittlung (Message-Passing) oder die Ereignisbehandlung (Event-Handling) adäquat nachzubilden. Allerdings sind die weitaus meisten GUI-Toolkits objektorientiert.

Die JFC-Bibliothek

In diesem Abschnitt wird eine besonders nützliche Bibliothek zur Programmierung grafischer Anwendungen vorgestellt: die plattformunabhängigen Java Foundation Classes (JFC). Mit Hilfe dieser Sammlung von Klassen zur Programmierung grafischer Oberflächen und Zeichnungen lassen sich zwar nicht alle Raffinessen der plattformspezifischen Desktops nutzen, aber dafür laufen JFC-Anwendungen unter den verschiedensten konkreten GUIs.

Die JFC bestehen im Wesentlichen aus den folgenden Packages und Bestandteilen:

gp  java.awt. Das Abstract Windowing Toolkit (AWT) ist der älteste Bestandteil der JFC, der bereits in der allerersten Java-Version enthalten war. Das AWT stellt sowohl grundlegende Zeichenfunktionen als auch einfache Widgets für grafische Benutzeroberflächen bereit. Der GUI-Teil wird inzwischen weitgehend durch das Swing-Toolkit ersetzt, die Zeichenfunktionalität durch die Java2D-API.
gp  java.awt.event. Dieses Unter-Package des AWT stellt grundlegende Interfaces zur Ereignisbehandlung für AWT-Komponenten bereit.
gp  javax.swing. Das umfangreiche Swing-Toolkit und seine Unter-Packages (zum Beispiel javax.swing.event zur erweiterten Ereignisbehandlung oder javax.swing.table mit mächtigen Funktionen zur Tabellendarstellung) bilden den Rahmen für die zeitgemäße Programmierung grafischer Oberflächen in Java. Alle Swing-Klassen sind von entsprechenden AWT-Vorfahren abgeleitet, aber erheblich flexibler und leistungsfähiger.
gp  Java2D. Die zentrale Java2D-Klasse Graphics2D wurde irritierenderweise im Package java.awt untergebracht. Dennoch handelt es sich um eine völlige Neuentwicklung, die erheblich mehr Funktionen zu bieten hat als die Vorgängerklasse java.awt.Graphics.
gp  java.applet. Formal gesehen gehören die Java-Applets zwar nicht zu den JFC, machen aber regen Gebrauch von deren Bestandteilen. Ein Java-Applet ist ein kleines Java-Programm, das in einem Webbrowser ausgeführt werden kann und auf jeden Fall aus einer grafischen Zeichenfläche besteht.

In der Praxis werden die alten AWT-Klassen oft mit den neuen Swing- und Java2D-Klassen gemischt angewandt, weil nicht alle Aspekte in den aktuelleren Modellen völlig neu entworfen wurden. Beispielsweise sind die Klassen, die in java.awt.event enthalten sind, in den meisten Fällen völlig ausreichend für das Event-Handling.

Dieser Abschnitt konzentriert sich im Wesentlichen auf das AWT und die klassische Zeichen-API; diese älteren Klassen sind zwar nicht so leistungsfähig wie Swing und Java2D, aber dafür zu allen Java-Versionen kompatibel und einfacher zu verstehen.


Galileo Computing

6.4.1 Zeichnungen und Grafiken erstellen  downtop

Das Zeichnen von Linien, Füllungen und geometrischen Figuren ist eine recht einfache Angelegenheit. Die Zeichenfunktionen sind Methoden einer Instanz der Klasse java.awt.Graphics (beziehungsweise der abgeleiteten Klasse javax.swing.Graphics2D für erweiterte Funktionen). Um diese Methoden zu verwenden, benötigen Sie allerdings zunächst ein Container-Objekt, auf dessen Untergrund gezeichnet werden kann. Sämtliche Flächenobjekte wie java.awt.Frame (einfaches Fenster) oder java.awt.Panel (eine rechteckige Fläche) sind von der grundlegenden AWT-Container-Klasse java.awt.Canvas abgeleitet und besitzen deshalb ein integriertes Graphics-Objekt, auf das Sie mittels container.getGraphics() zugreifen können. Ebenso ist jede Swing-Komponente mit einer Graphics2D-Instanz ausgestattet.

Die einfachste Art und Weise, eine Zeichenfläche zu erstellen und statische Zeichnungen hineinzumalen, besteht in der Erweiterung der Klasse Frame beziehungsweise JFrame (Swing-Fenster mit erweiterter Funktionalität). Wenn Sie in Ihrem eigenen Programm die Methode paint() der Elternklasse überschreiben, werden die darin enthaltenen Zeichenanweisungen automatisch ausgeführt, sobald ein Objekt Ihrer Klasse erstellt und seine Methode show() aufgerufen wird. Diese Instanziierung erledigt man am sinnvollsten in der Methode main().

In Listing 6.13 sehen Sie ein Beispiel, das ein Fenster auf den Bildschirm zeichnet und ein rotes Rechteck, ein grünes Dreieck und einen blauen Kreis hineinzeichnet. Die einzelnen Graphics-Methoden, die zum Zeichnen verwendet wurden, werden weiter unten erläutert.

Listing 6.13   Eine einfache Zeichnung in einem GUI-Fenster

import java.awt.*;
 
public class EinfacheZeichnung extends Frame
{
   public static void main (String args[])
   {
      // Eine Instanz von EinfacheZeichnung erstellen
      EinfacheZeichnung z = new EinfacheZeichnung();
      // Größe und Titel des Fensters festlegen
      z.setSize (480, 360);
      z.setTitle ("Die erste Java-Zeichnung");
      // Das Fenster anzeigen
      z.show();
   }
 
   public void paint (Graphics g)
   {
      // Rotes Rechteck zeichnen
      g.setColor (Color.RED);
      g.fillRect (10, 10, 400, 150);
      // Grünes Dreieck zeichnen
      int[] xcoords = {150, 270, 30};
      int[] ycoords = {180, 350, 350};
      g.setColor (Color.GREEN);
      g.fillPolygon (xcoords, ycoords, xcoords.length);
      // Blauen Kreis zeichnen
      g.setColor (Color.BLUE);
      g.fillOval (250, 220, 120, 120);
   }
}

Abbildung 6.2 zeigt zunächst das Ergebnis. Wie Ihnen beim Testen des Programms wahrscheinlich aufgefallen ist, lässt sich das erzeugte GUI-Fenster nicht durch Klick auf seinen Schließen-Button entfernen. Erst, wenn Sie das zugehörige Konsolenprogramm per (STRG) + (C) beenden, verschwindet auch das Fenster. Das liegt daran, dass AWT-Objekte nur dann Nachrichten wie Mausklicks oder Tastaturcodes verarbeiten, wenn Sie explizit das entsprechende Event-Handling einrichten. Wie das funktioniert, wird weiter unten erläutert.


Abbildung 6.2   Eine einfache Zeichnung in einem AWT-Fenster

Abbildung
Hier klicken, um das Bild zu Vergrößern


Die Methode main() erzeugt zunächst eine neue Instanz der Klasse, in der sie definiert wurde. Diese Vorgehensweise braucht Sie nicht zu beunruhigen, sie wurde auch schon weiter oben bei der Hintergrundsuche eingesetzt. Sie dient vor allem dazu, ein großes Problem zu lösen: Die Methode main() muss static deklariert werden, weil das laufende Programm selbst keine Instanz ist. Andererseits benötigen Sie eine Instanz, um mit den nichtstatischen Eigenschaften und Methoden der Klasse arbeiten zu können. Wenn es Ihnen lieber ist, können Sie diese Funktionalität auch auf zwei Klassen in separaten Dateien verteilen, also eine separate »Programm«-Klasse mit einer main()-Methode schreiben, die eine Instanz der anderen Klasse erzeugt.

Fenster-Methoden

Da die Klasse EinfacheZeichnung von Frame abgeleitet wurde, erbt sie die Methoden die anschließend für die Instanz z aufgerufen werden: setSize(int breite, int hoehe) stellt die Fenstergröße in Pixeln ein, setTitle(String fenstertitel) bestimmt den Text, der in der Titelleiste angezeigt wird, und show() zeigt das fertige Objekt auf dem Bildschirm an.

Interessanter sind die verschiedenen Methoden der Klasse Graphics, die in der Zeichenmethode paint() aufgerufen werden. Die wichtigsten von ihnen werden in Tabelle 6.1 erläutert. Es handelt sich hier lediglich um die grundlegenden Methoden der AWT-Klasse Graphics, nicht um die erweiterten aus der Swing-Klasse Graphics2D.


Tabelle 6.1   Die wichtigsten Zeichenmethoden der Klasse »Graphics«

Graphics-Methode Erläuterung
drawLine (int x1, int y1,
int x2, int y2)
Zeichnet eine Linie vom angegebenen Anfangspunkt (x1|y1) bis zum Endpunkt (x2|y2).
drawRect (int x, int y,
int breite, int hoehe)
Zeichnet die Kontur eines Rechtecks mit der angegebenen Breite und Höhe, dessen linke obere Ecke sich auf (x|y) befindet.
fillRect (int x, int y,
int breite, int hoehe)
Wie drawRect(), zeichnet aber ein gefülltes Rechteck.
drawOval (int x, int y,
int breite, int hoehe)
Zeichnet die Kontur eines Ovals. (x|y) geben die Koordinaten der linken oberen Ecke des Begrenzungsrechtecks an, breite und hoehe seine Ausdehnung.
fillOval (int x, int y,
int breite, int hoehe)
Wie drawOval(), zeichnet aber ein gefülltes Oval.
drawPolygon (int[] x,
int[] y, int anzahl)
Zeichnet die Kontur eines beliebigen Vielecks. Je zwei Elemente der beiden Arrays bilden zusammen die Koordinaten eines Punktes; die Anzahl bestimmt die Zahl der Punkte und kann in der Regel durch x.length angegeben werden.
fillPolygon (int[] x,
int[] y, int anzahl)
Wie drawPolygon(), zeichnet aber ein gefülltes Vieleck.
drawArc (int x, int y,
int breite, int hoehe,
int winkel1, int winkel2)
Zeichnet einen Kreis- beziehungsweise Ovalbogen von winkel1 bis winkel2. Die beiden Winkel werden im Winkelmaß (0-360°) angegeben und verlaufen von rechts gegen den Uhrzeigersinn.
fillArc (int x, int y,
int breite, int hoehe,
int winkel1, int winkel2)
Wie drawArc(), zeichnet aber einen gefüllten Bogen.
setColor (int farbe)
setColor (Color farbe)
Stellt die Zeichenfarbe auf den angegebenen Wert (siehe unten).

Farben einstellen

Die Methode setColor() zum Einstellen der Zeichenfarbe existiert in zwei verschiedenen Varianten: Die Version, die einen int-Wert entgegennimmt, wird üblicherweise mit den symbolischen Konstanten der Grundfarben aufgerufen, die in der Klasse Color definiert sind, beispielsweise Color.RED, Color.WHITE oder Color.MAGENTA. Die andere Variante erwartet eine Instanz der Klasse Color als Argument; häufig wird mittels new() eine anonyme Instanz erzeugt und übergeben. Der wichtigste Konstruktor von Color erwartet drei int-Werte, die den Rot-, den Grün- und den Blau-Anteil der gewünschten Farbe zwischen 0 und 255 angeben:

g.setColor (new Color (255, 153, 0));   // orange

Falls Sie nicht wissen sollten, wie die RGB-Farbmischung funktioniert, lesen Sie Kapitel 8, Bildbearbeitung und Grafik.

In Listing 6.14 sehen Sie ein komplexeres Beispiel: Zunächst wird ein Koordinatensystem erstellt, anschließend wird eine Sinus- und eine Cosinuskurve hineingezeichnet. In Abbildung 6.3 sehen Sie anschließend die Ausgabe des Programms.

Listing 6.14   Eine Sinus- und eine Cosinuskurve

import java.awt.*;
 
public class SinusCosinus extends Panel
{
 
    public static void main (String args[])
    {
       SinusCosinus sc = new SinusCosinus();
       Frame f = new Frame ("Sinus und Cosinus");
       f.setSize (420, 420);
       f.add (sc);
       f.show();
    }
        public void paint (Graphics g)
    {
        // Grundlinien des Koordinatensystems zeichnen
        g.setColor (Color.black);
        g.drawLine (200, 0, 200, 400);
        g.drawLine (0, 200, 400, 200);
        // Unterteilungen der Achsen zeichnen
        for (int i = 20; i < 400; i += 20) {
            g.drawLine (i, 198, i, 202);
            g.drawLine (198, i, 202, i);
        }
        // Pfeilspitzen der Achsen zeichnen
        g.drawLine (400, 200, 395, 195);
        g.drawLine (400, 200, 395, 205);
        g.drawLine (200, 0, 195, 5);
        g.drawLine (200, 0, 205, 5);
        g.setColor (Color.BLUE);
        g.drawString ("y = sin (x)", 10, 30);
        g.setColor (Color.RED);
        g.drawString ("y = cos (x)", 10, 50);
        // die eigentlichen Kurven zeichnen
        double oldSinus = Math.sin (-10) * 20;
        double oldCosinus = Math.cos (-10) * 20;
        for (int i = 1; i < 400; i++) {
            g.setColor (Color.blue);
            double sinus = Math.sin ((double)
                 (i - 200) / 20);
            sinus *= 20;
            g.drawLine (i-1, 200 - (int)oldSinus, i, 200 - (int)sinus);
            oldSinus = sinus;
            g.setColor (Color.red);
            double cosinus = Math.cos ((double)
                 (i - 200) / 20);
            cosinus *= 20;
            g.drawLine (i-1, 200 - (int)oldCosinus, i, 200 - (int)cosinus);
            oldCosinus = cosinus;
        }
    }
}

Abbildung 6.3   Die Ausgabe des Sinus- und Cosinus-Programms

Abbildung
Hier klicken, um das Bild zu Vergrößern


Beachten Sie zunächst, dass die Klasse SinusCosinus nicht von Frame abgeleitet ist, sondern von java.awt.Panel, einer rechteckigen Zeichenfläche. Das benötigte Fenster-Objekt wird separat als Instanz von Frame erstellt; der hier verwendete Konstruktor new Frame (String titel) setzt automatisch den Fenstertitel. Anschließend wird die SinusCosinus-Instanz mit Hilfe der Frame-Methode add() in das Fenster gesetzt. Diese Vorgehensweise garantiert, dass die linke obere Ecke des Zeichenbereichs auf jeden Fall innerhalb des Fensters zu sehen ist, bei einer direkten Instanziierung von Frame wäre dies womöglich anders.

Die paint()-Methode benutzt nur wenige neue Anweisungen: Die statischen Methoden sin() und cos() der Klasse Math berechnen den Sinus beziehungsweise Cosinus eines Wertes. Die Argumente müssen Winkel im Bogenmaß sein (360° = 2p). Die Graphics-Methode drawString() schreibt den angegebenen Text auf eine Grafikfläche.

Verwirrend ist dagegen die erforderliche Umrechnung des Maßstabs: Der Wert 1 wird durch 20 Pixel dargestellt. Aus diesem Grund werden die Einteilungen des Koordinatensystems in Zwanzigerschritten gezeichnet; außerdem werden sämtliche Berechnungen mit 20 multipliziert. Ein weiteres Problem ergibt sich dadurch, dass die y–Werte in der Grafikprogrammierung Kopf stehen: Während sie beim cartesischen Koordinatensystem nach oben wachsen, liegt der Nullpunkt beim Computer in der linken oberen Ecke und wächst nach unten. Dass der Ursprung des Koordinatensystems auf (200|200) platziert wurde, ergibt weiteren Umrechnungsbedarf.

Das Zeichnen der Kurven selbst funktioniert folgendermaßen: In jedem Schleifendurchlauf wird mittels drawLine() eine Linie vom Punkt des vorigen Durchlaufs (oldSinus beziehungsweise oldCosinus) zum aktuellen Punkt gezeichnet. Da die trigonometrischen Funktionen ziemlich exakt ausgerechnet werden müssen, um sie im Maßstab möglichst korrekt darstellen zu können, akzeptieren die AWT-Methoden nur ganzzahlige Werte. Deshalb finden einige Typecasting-Operationen von int nach double und umgekehrt statt.


Galileo Computing

6.4.2 Animation  downtop

Ein weiterer Nutzen der AWT- und Swing-Zeichenklassen besteht in der Erstellung von Animationen. Das Grundprinzip ist leicht zu verstehen: Im zeitlichen Verlauf wird die Zeichenfläche erst mit einer Zeichnung versehen, die anschließend wieder von einer Füllung in Hintergrundfarbe verdeckt wird. Dies geschieht immer wieder.

Double Buffering

Dieser theoretische Ansatz wird in der Praxis noch verbessert. Würden die verdeckende Füllung und die neue Zeichnung nacheinander auf eine sichtbare Fläche gezeichnet, dann würde die Animation flackern. Aus diesem Grund wird eine Technik namens Double Buffering eingesetzt. Die nächste Phase der Animation, bestehend aus Lösch- und Neuzeichnungsvorgang, wird zunächst auf eine nicht sichtbare Zeichenfläche aufgetragen, den Buffer (eine Instanz der Klasse Image). Erst nach Fertigstellung wird das gesamte neue Bild auf die eigentliche Fläche gesetzt.

Die Klasse Image können Sie übrigens auch noch zu einem anderen Zweck einsetzen. In eine Image-Instanz kann eine Bilddatei hineingeladen werden, die sich auf diese Weise in einer AWT-Anwendung anzeigen lässt. Unterstützt werden die klassischen Internet-Bilddateitypen GIF und JPEG. Beispielsweise sehen Sie hier, wie das Bild test.gif geladen und mit Hilfe der Graphics-Instanz g gezeichnet wird:

Toolkit tk = Toolkit.getDefaultToolkit();
Image test = tk.getImage ("test.gif");
g.drawImage (test, 0, 0, this);

Da der Umgang mit Bilddateien plattformspezifisch ist, wird zunächst eine Toolkit-Instanz erzeugt, die eine Referenz auf die konkreten Grafikfunktionen des verwendeten Betriebssystems bildet.

Im Übrigen wird für gewöhnlich ein eigener Thread mit der Ausführung der Animation beauftragt, damit sie möglichst gleichmäßig und unbeeindruckt von anderen Ereignissen ablaufen kann.

Listing 6.15 zeigt ein kleines Beispielprogramm, bei dem ein blauer »Ball« jeweils an den Fensterrändern abprallt, das heißt, die Richtung wird bei jedem Aufprall umgekehrt.

Listing 6.15   Eine einfache Animation

import java.awt.*;
 
public class BouncingBall extends Panel      implements Runnable
{
    // Thread- und Buffer-Variablen:     private Image buffer;
    private Graphics bg;
    private Thread animation;
    // die Eigenschaften der "Kugel":
    private int x;
    private int y;
    private int xdir;
    private int ydir;
    private int sfaktor;
        public static void main (String args[])
    {
       BouncingBall b = new BouncingBall();
       b.init();
       b.start();
       Frame f = new Frame ("Bouncing Ball");
       f.setSize (420, 420);
       f.add (b);
       f.show();
    }
 
    private int getSFaktor ()
    {
        int xvm = Math.abs (x - 200);
        int yvm = Math.abs (y - 200);
        int f = (int)(xvm + yvm) / 2;
        int sf = f / 20 + 1;
        return sf;
    }
        public void init ()
    {
        // Werte initialisieren
        /* Anmerkung: x und y dürfen nicht gleich sein, sonst bewegt sich 
die "Kugel" nur zwischen den Diagonalen, was langweilig wäre!     */
        x = 71;
        y = 191;
        xdir = 1;
        ydir = 1;
    }
        public void start ()
    {
        animation = new Thread (this);
        animation.start ();
    }
        public void run ()
    {
        while (true) {
            try {
                animation.sleep (30);
            }
            catch (InterruptedException e) {
            }
            repaint ();
        }
    }
        public void update (Graphics g)
    {
        // Buffer bei Bedarf erzeugen
        if (buffer == null) {
           buffer = createImage (400, 400);
           bg = buffer.getGraphics ();
        }
        // Auf den Buffer zeichnen
        paint (bg);
        // Den Buffer einblenden
        g.drawImage (buffer, 0, 0, this);
    }
        public void paint (Graphics g)
    {
        g.setColor (Color.YELLOW);
        g.fillRect (0, 0, 400, 400);
        g.setColor (Color.BLUE);
        // die "Kugel" zeichnen
        g.fillOval (x, y, 20, 20);
        // Werte verändern
        sfaktor = getSFaktor ();
        g.setColor (Color.BLACK);
        g.drawString ("" + sfaktor, 20, 20);
        x += (xdir * sfaktor);
        y += (ydir * sfaktor);
        // Ränder prüfen und ggfls. Richtungen umkehren
        if (x <= 0 || x >= 380)
            xdir = -xdir;
        if (y <= 0 || y >= 380)
            ydir = -ydir;
    }
}

Der Schlüssel zur Double-Buffering-Technik ist die Methode update(): Sie wird automatisch von der Methode repaint() aufgerufen. Als Erstes erfolgt hier ein Aufruf der Methode paint(), und zwar mit dem Graphics-Objekt des Buffers. Anschließend wird der Buffer mit Hilfe der Graphics-Methode drawImage() auf der eigentlichen Zeichenfläche platziert.

Damit die Animation nicht zu schnell abläuft, wird innerhalb der Methode run() zunächst sleep() aufgerufen. Das Argument ist eine Wartezeit in Millisekunden, die der Thread »schlafen« soll. Der Aufruf muss stets, wie gezeigt, in einem try/catch-Block stehen, weil der Thread unter Umständen vorzeitig unterbrochen werden kann, was eine InterruptedException auslöst.

Die gezeigte Animation selbst dürfte im Grunde selbsterklärend sein. Interessant ist allenfalls die Methode getSFaktor(), die den Geschwindigkeitsfaktor in Abhängigkeit vom Abstand zu den Wänden berechnet, sodass der Ball zu den Rändern hin schneller zu werden scheint: Die Wände wirken ein wenig »magnetisch«. Zur Kontrolle wird der aktuelle Geschwindigkeitsfaktor jeweils angezeigt.


Galileo Computing

6.4.3 Programmierung fensterbasierter Anwendungen  downtop

Die Hauptaufgabe von Fenstern besteht natürlich nicht darin, Zeichnungen oder Animationen anzuzeigen. Fenster stellen vielmehr den Rahmen zur Platzierung verschiedener Widgets dar, die mit Funktionalität versehen werden müssen.

Im AWT und in Swing stehen die einzelnen Arten von GUI-Komponenten als Klassen zur Verfügung. In der Regel werden entsprechende Instanzen erzeugt, anschließend wird die Methode add() der jeweils übergeordneten Komponente aufgerufen, um sie zu dieser hinzuzufügen. Auf diese Weise entsteht ein ineinander verschachteltes Gefüge von GUI-Bestandteilen.

Die wichtigsten AWT-Klassen werden in Tabelle 6.2 dargestellt. Zu vielen von ihnen gibt es abgeleitete Swing-Klassen, die durch ein vorangestelltes J gekennzeichnet werden (zum Beispiel JFrame zu Frame oder JButton zu Button).


Tabelle 6.2   Wichtige AWT-Klassen und ihre gängigen Konstruktoren

Klasse Wichtige Konstruktoren Erläuterung
Frame Frame()
Frame (String titel)
Fenster
Button Button()
Buton (String label)
Schaltfläche
TextField TextField()
TextField (String txt)
einzeiliges Textfeld
TextArea TextArea()
TextField (String txt)
mehrzeiliger Textbereich
MenuBar MenuBar() Menüleiste
Menu Menu()
Menu (String label)
einzelnes Menü
MenuItem MenuItem()
MenuItem (String txt)
Menüelement
Panel Panel() Anzeigefläche
Label Label()
Label (String txt)
Beschriftungsfeld

Am Beispiel eines Fensters mit Menüleiste und zwei Buttons sehen Sie hier, wie eine solche Objekthierarchie aufgebaut wird:

import java.awt.*;
 
public class Test2
{
   public static void main(String args[])
   {
      // Objekte erzeugen
      Frame f = new Frame ("Menü und Buttons");
      MenuBar mb = new MenuBar();
      Menu m = new Menu ("Test");
      MenuItem mi1 = new MenuItem ("Probe 1");
      MenuItem mi2 = new MenuItem ("Probe 2");
      Button b1 = new Button ("Info 1");
      Button b2 = new Button ("Info 2");
      // Objekte verknüpfen
      // Menüpunkte zum Menü hinzufügen
      m.add (mi1);
      m.add (mi2);
      // Menü zur Menüleiste hinzufügen
      mb.add (m);
      // Menüleiste und Buttons zum Frame hinzufügen
      f.setMenuBar (mb);
      f.add (b1);
      f.add (b2);
      f.setSize (400, 400);
      f.show();
   }
}

LayoutManager

Wenn Sie diesen Code ausführen, werden Sie feststellen, dass nicht beide Buttons zuverlässig angezeigt werden (wahrscheinlich wird sogar nur einer angezeigt). Das liegt daran, dass nicht festgelegt wurde, wie die beiden Buttons im Fenster angeordnet werden sollen. Für diese Anordnung sind spezielle Klassen zuständig, die als LayoutManager bezeichnet werden. Um die beiden Buttons beispielsweise nebeneinander zu setzen, müssen Sie über der Zeile f.add(b1); folgende Ergänzung durchführen:

f.setLayout (new GridLayout (1, 2));

Um die Buttons dagegen untereinander zu stellen, lautet die Layout-Anweisung so:

f.setLayout (new GridLayout (2, 1));

Das erste Argument des GridLayout-Konstruktors gibt also die Anzahl der Zeilen an, das zweite die Anzahl der Spalten. Nach der Einrichtung des LayoutManagers können Sie einfach nacheinander Elemente hinzufügen, die von links nach rechts und von oben nach unten nacheinander in das GridLayout eingesetzt werden.

Ein anderer bedeutender LayoutManager ist das BorderLayout. Es besteht aus fünf verschiedenen Anzeigebereichen, die durch die statischen symbolischen Konstanten BorderLayout.NORTH (oben), BorderLayout.SOUTH (unten), BorderLayout.WEST (links), BorderLayout.EAST (rechts) und BorderLayout.CENTER (Mitte) angegeben werden. Die Elemente, die sich oben und unten befinden, werden bis zum linken und rechten Rand durchgezogen, das linke, mittlere und rechte Element teilen sich den Platz dazwischen. Zum Beispiel:

Button b1 = new Button ("oben");
Button b2 = new Button ("unten");
Button b3 = new Button ("links");
Button b4 = new Button ("rechts");
Button b5 = new Button ("Mitte");
f.setLayout (new BorderLayout());
f.add (b1, BorderLayout.NORTH);
f.add (b2, BorderLayout.SOUTH);
f.add (b3, BorderLayout.WEST);
f.add (b4, BorderLayout.EAST);
f.add (b5, BorderLayout.CENTER);

Dies sind natürlich nur wenige Beispiele für die Verwendung von LayoutManagern; es gibt zahlreiche weitere Varianten, deren Behandlung hier zu weit führen würde.

LayoutManager können übrigens leicht ineinander verschachtelt werden: Erzeugen Sie eine Panel-Instanz und weisen Sie ihr den zu verschachtelnden LayoutManager zu. Anschließend können Sie Elemente zu diesem Panel hinzufügen. Zu guter Letzt lässt sich das Panel auf einer übergeordneten Anzeigefläche platzieren, das sein eigenes Layout besitzt. Die Beispiele im nächsten Unterabschnitt verwenden solche verschachtelten Layouts.

Event-Handling

Nun wissen Sie zwar bereits, wie sich Buttons hinzufügen lassen, aber noch nicht, wie Sie Code schreiben können, der darauf reagiert. Sämtliches Event-Handling wird im AWT durch so genannte Listener erledigt, die im Hintergrund auf bestimmte Ereignisse »lauschen« und diese verarbeiten.

Konkret bedeutet das für die Programmierung, die passenden Listener-Interfaces zu implementieren und die in diesen Interfaces deklarierten Callback-Methoden bereitzustellen, die aufgerufen werden, wenn das entsprechende Ereignis eintritt. Einige gängige Listener-Interfaces sind:

gp  java.awt.ActionListener verarbeitet Button- und Menübefehle sowie Tastatur-Ereignisse. Das Interface deklariert nur eine einzige Methode: void actionPerformed (ActionEvent e). Das übermittelte ActionEvent können Sie mittels e.getActionCommand() untersuchen – Sie erhalten den String zurück, mit dem der Button oder das Menüelement beschriftet ist.
gp  java.awt.MouseListener ist für die Verarbeitung von Mausklicks und Rollover-Operationen zuständig. Leider müssen Sie eine ganze Reihe von Methoden implementieren, um die Anforderungen dieses Interfaces zu erfüllen. Wenn Sie die Funktionalität einer dieser Methoden nicht benötigen, kann der Methodenrumpf allerdings leer bleiben. Die einzelnen Methoden sind folgende:
void mousePressed (MouseEvent e) – die Maustaste wurde gedrückt. void mouseReleased (MouseEvent e) – die Maustaste wurde wieder losgelassen. void mouseClicked (MouseEvent e) – ein vollständiger Mausklick aus Drücken und Loslassen hat stattgefunden. void mouseEntered (MouseEvent e) – der Mauszeiger hat den Bereich der Komponente berührt. void mouseExited (MouseEvent e) – der Mauszeiger hat den Bereich der Komponente wieder verlassen.
    Der konkrete Umgang mit diesen Methoden wird weiter unten an Beispielen erläutert.
       
gp  MouseMotionListener registriert einfache Mausbewegungen in einer Komponente. Es müssen zwei Methoden implementiert werden:
void mouseMoved (MouseEvent e) – die Maus wurde bewegt. void mouseDragged (MouseEvent e) – die Maus wurde mit gedrückter Maustaste bewegt, also gezogen.
    Die wichtigsten Methoden der erhaltenen MouseEvent-Instanz sind getX() und getY(); sie liefern die Koordinaten des Mauszeigers.
       
gp  WindowListener enthält eine Reihe von Methoden für die Behandlung wichtiger Fenster-Ereignisse:
void windowOpened (WindowEvent e) – wird unmittelbar nach der Erzeugung des Fensters aufgerufen. void windowActivated (WindowEvent e) – wird aufgerufen, wenn das Fenster durch Anklicken oder eine andere Methode zum aktiven Fenster wird. void windowDeactivated (WindowEvent e) – wird aufgerufen, sobald ein anderes Fenster aktiv wird. void windowIconified (WindowEvent e) – wird beim Minimieren des Fensters (eine plattformabhängige Funktion) aktiviert. void windowDeiconified (WindowEvent e) – wird beim Wiederherstellen des Fensters nach der Minimierung aufgerufen. void windowClosing (WindowEvent e) – wird aufgerufen, wenn der Schließ-Button des Fensters gedrückt wird. Meist erfolgt an dieser Stelle ein Aufruf der Methode dispose() des entsprechenden Fensters, um es tatsächlich zu schließen. void windowClosed (WindowEvent e) – wird automatisch nach einem Aufruf von dispose() aufgerufen, also nachdem das Fenster bereits geschlossen wurde. Eine gute Gelegenheit, um »aufzuräumen« und – falls dies das eigentliche Programmfenster war – das Programm durch Aufruf von System.exit(0) zu beenden.

Nachdem Sie einen Listener implementiert haben, müssen Sie ihn zu den Komponenten hinzufügen, deren Ereignisse er verarbeiten soll. Dafür gibt es eine Reihe von Methoden, etwa addActionListener(), addMouseListener, addMouseMotionListener() und addWindowListener() . Das Argument dieser Methoden ist die jeweilige Instanz, die den Listener implementiert – sehr häufig this, wenn die aktuelle Klasse selbst den Listener bildet.

Das Beispiel in Listing 6.16 zeigt einen einfachen Rechner: In zwei Textfelder können Sie die beiden Zahlen eingeben, mit denen gerechnet werden soll. Ein Klick auf einen von vier Operations-Buttons führt die entsprechende Grundrechenart mit den beiden Zahlen aus und schreibt das Ergebnis in ein drittes Textfeld. Dank der Implementierung von WindowListener lässt sich das Programm einfach durch Schließen des Fensters beenden.

Listing 6.16   Ein einfacher AWT-Taschenrechner

import java.awt.*;
import java.awt.event.*;
 
public class Rechner extends Frame      implements ActionListener, WindowListener
{
   // GUI-Komponenten
   private Panel eingabe1;
   private Panel eingabe2;
   private Panel ausgabe;
   private TextField einTF1;
   private TextField einTF2;
   private TextField ausTF;
   private Label ein1;
   private Label ein2;
   private Label aus;
   private Panel buttonLeiste;
   private Button plus;
   private Button minus;
   private Button mal;
   private Button durch;
 
   public static void main (String args[])
   {
      Rechner r = new Rechner();
      r.setTitle ("AWT-Taschenrechner");
      r.setSize (320, 240);
      r.show();
   }
 
   // Konstruktor
   public Rechner()
   {
      // Komponenten erzeugen
      eingabe1 = new Panel();
      eingabe2 = new Panel();
      ausgabe = new Panel();
      einTF1 = new TextField();
      einTF2 = new TextField();
      ausTF = new TextField();
      ein1 = new Label ("Zahl 1:");
      ein2 = new Label ("Zahl 2:");
      aus = new Label ("Ergebnis:");
      buttonLeiste = new Panel();
      plus = new Button ("+");
      minus = new Button ("-");
      mal = new Button ("*");
      durch = new Button ("/");
      // Komponenten platzieren
      eingabe1.setLayout (new GridLayout (1, 2));
      eingabe1.add (ein1);
      eingabe1.add (einTF1);
      eingabe2.setLayout (new GridLayout (1, 2));
      eingabe2.add (ein2);
      eingabe2.add (einTF2);
      ausgabe.setLayout (new GridLayout (1, 2));
      ausgabe.add (aus);
      ausgabe.add (ausTF);
      buttonLeiste.setLayout (new GridLayout (1, 4));
      buttonLeiste.add (plus);
      buttonLeiste.add (minus);
      buttonLeiste.add (mal);
      buttonLeiste.add (durch);
      this.setLayout (new GridLayout (4, 1));
      this.add (eingabe1);
      this.add (eingabe2);
      this.add (buttonLeiste);
      this.add (ausgabe);
      // Event-Handler registrieren
      plus.addActionListener (this);
      minus.addActionListener (this);
      mal.addActionListener (this);
      durch.addActionListener (this);
      this.addWindowListener (this);
   }
 
   // ActionListener-Implementierung
   public void actionPerformed (ActionEvent e)
   {
      // Eingabewerte lesen
      int z1 = Integer.parseInt (einTF1.getText());
      int z2 = Integer.parseInt (einTF2.getText());
      int erg = 0;
      // Berechnung je nach Button
      String cmd = e.getActionCommand();
      if (cmd.equals ("+"))
         erg = z1 + z2;
      if (cmd.equals ("-"))
         erg = z1 - z2;
      if (cmd.equals ("*"))
         erg = z1 * z2;
      if (cmd.equals ("/"))
         erg = z1 / z2;
      // Ergebnis anzeigen
      ausTF.setText ("" + erg);
   }
      // WindowListener-Implementierung
   public void windowOpened (WindowEvent e)
   {  // Wird hier nicht benötigt
   }
      public void windowActivated (WindowEvent e)
   {  // Wird hier nicht benötigt
   }
 
   public void windowDeactivated (WindowEvent e)
   {  // Wird hier nicht benötigt
   }
      public void windowIconified (WindowEvent e)
   {  // Wird hier nicht benötigt
   }
 
   public void windowDeiconified (WindowEvent e)
   {  // Wird hier nicht benötigt
   }
      public void windowClosing (WindowEvent e)
   {
      // Fenster schließen
      this.dispose();
   }
 
   public void windowClosed (WindowEvent e)
   {
      // Programm beenden
      System.exit (0);
   }
}

Wenn Sie den Code lesen, werden Sie feststellen, dass die aktuelle Instanz der Klasse (this) im Konstruktor jeweils als Event-Handler registriert wird: Als ActionListener für die vier Buttons und als WindowListener für sich selbst. Der Rest des Programms sollte sich nach den obigen Ausführungen und mit Hilfe der Kommentare von selbst erklären. In Abbildung 6.4 sehen Sie den fertigen Rechner.


Abbildung 6.4   Der AWT-Rechner bei der Division von 65.536 durch 2

Abbildung
Hier klicken, um das Bild zu Vergrößern


Zur Verdeutlichung der Mausabfrage sehen Sie in Listing 6.17 ein weiteres kleines Programm. Es ermöglicht Ihnen das Zeichnen mit der Maus, wobei Sie über ein Menü die Farbe wechseln, die Zeichenfläche löschen und das Programm beenden können.

Listing 6.17   Ein kleines Malprogramm

import java.awt.*;
import java.awt.event.*;
 
public class Malen extends Frame      
implements Runnable, MouseMotionListener, ActionListener, WindowListener
{
   // GUI-Komponenten
   private MenuBar mbar;
   private Menu bild;
   private MenuItem loeschen;
   private MenuItem beenden;
   private Menu farben;
   private MenuItem rot;
   private MenuItem gruen;
   private MenuItem blau;
   private MenuItem cyan;
   private MenuItem magenta;
   private MenuItem gelb;
   private MenuItem schwarz;
     // Thread, Buffer
   private Thread mal;
   private Image buffer;
   private Graphics bg;
      // Globale Variablen
   private int xnew, ynew;  // Neue Koordinaten
   private int xold, yold;  // Alte Koordinaten
   private boolean malt;    // Maustaste oben/unten?
   private Color farbe;     // Aktuelle Malfarbe
      public static void main (String args[])
   {
      Malen m = new Malen();
      m.start();            // Thread starten
      m.setTitle ("Kleines Malprogramm");
      m.setSize (500, 500);
      m.show();
   }
      // Konstruktor
   public Malen()
   {
      // Komponenten erzeugen
      mbar = new MenuBar();
      bild = new Menu ("Bild");
      loeschen = new MenuItem ("Löschen");
      beenden = new MenuItem ("Beenden");
      farben = new Menu ("Farbe");
      rot = new MenuItem ("Rot");
      gruen = new MenuItem ("Grün");
      blau = new MenuItem ("Blau");
      cyan = new MenuItem ("Cyan");
      magenta = new MenuItem ("Magenta");
      gelb = new MenuItem ("Gelb");
      schwarz = new MenuItem ("Schwarz");
                  // Komponenten montieren
      bild.add (loeschen);
      bild.add (beenden);
      mbar.add (bild);
      farben.add (rot);
      farben.add (gruen);
      farben.add (blau);
      farben.add (cyan);
      farben.add (magenta);
      farben.add (gelb);
      farben.add (schwarz);
      mbar.add (farben);
      this.setMenuBar (mbar);
            // Event-Handler registrieren
      this.addMouseMotionListener (this);
      loeschen.addActionListener (this);
      beenden.addActionListener (this);
      rot.addActionListener (this);
      gruen.addActionListener (this);
      blau.addActionListener (this);
      cyan.addActionListener (this);
      magenta.addActionListener (this);
      gelb.addActionListener (this);
      schwarz.addActionListener (this);
      this.addWindowListener (this);
            // Werte initialisieren
      xnew = 0;
      ynew = 0;
      xold = 0;
      yold = 0;
      malt = false;
      farbe = Color.BLACK;
   }
      public void start()
   {
      mal = new Thread (this);
      mal.start();
   }
      public void run()
   {
      while (true) {
         try {
            mal.sleep (3);
         }
         catch (InterruptedException e) {
         }
         repaint();
      }
   }
      public void update (Graphics g)
   {
      // Buffer bei Bedarf erzeugen
      if (buffer == null) {
         buffer = createImage (500, 500);
         bg = buffer.getGraphics();
      }
      // Auf den Buffer zeichnen
      paint (bg);
      // Den Buffer einblenden
      g.drawImage (buffer, 0, 0, this);
   }
      public void paint (Graphics g)
   {
      if (malt) {
         g.setColor (farbe);
         g.drawLine (xold, yold, xnew, ynew);
         g.drawLine (xold + 1, yold, xnew + 1, ynew);
         g.drawLine (xold, yold + 1, xnew, ynew + 1);
         g.drawLine (xold + 1, yold + 1, xnew + 1, ynew + 1);
      }
   }
      // MouseMotionListener-Implementierung
   public void mouseMoved (MouseEvent e)    {
      malt = false;
      // "Alten" X- und Y-Wert setzen
      xold = e.getX();
      yold = e.getY();
   }
      public void mouseDragged (MouseEvent e)    {
      if (malt) {
         // Alten X- und Y-Wert merken
     xold = xnew;
     yold = ynew;
  } else {
     // Ab hier neu malen
     xold = e.getX();
     yold = e.getY();
  }
  malt = true;
      // Neuen X- und Y-Wert setzen
      xnew = e.getX();
      ynew = e.getY();
   }
      // ActionListener-Implementierung
   public void actionPerformed (ActionEvent e)
   {
      String cmd = e.getActionCommand();
      if (cmd.equals ("Löschen")) {
         bg.setColor (Color.WHITE);
         bg.fillRect (0, 0, 500, 500);
      }
      if (cmd.equals ("Beenden")) {
         this.dispose();
      }
      if (cmd.equals ("Rot")) {
         farbe = Color.RED;
      }
      if (cmd.equals ("Grün")) {
         farbe = Color.GREEN;
      }
      if (cmd.equals ("Blau")) {
         farbe = Color.BLUE;
      }
      if (cmd.equals ("Cyan")) {
         farbe = Color.CYAN;
      }
      if (cmd.equals ("Magenta")) {
         farbe = Color.MAGENTA;
      }
      if (cmd.equals ("Gelb")) {
         farbe = Color.YELLOW;
      }
      if (cmd.equals ("Schwarz")) {
         farbe = Color.BLACK;
      }
   }
      // WindowListener-Implementierung
   public void windowOpened (WindowEvent e)
   {  // Wird hier nicht benötigt
   }
      public void windowActivated (WindowEvent e)
   {  // Wird hier nicht benötigt
   }
 
   public void windowDeactivated (WindowEvent e)
   {  // Wird hier nicht benötigt
   }
      public void windowIconified (WindowEvent e)
   {  // Wird hier nicht benötigt
   }
 
   public void windowDeiconified (WindowEvent e)
   {  // Wird hier nicht benötigt
   }
      public void windowClosing (WindowEvent e)
   {
      // Fenster schließen
      this.dispose();
   }
 
   public void windowClosed (WindowEvent e)
   {
      // Programm beenden
      System.exit (0);
   }
}

Das eigentliche Malen findet wie bei der Animation auf einem Buffer statt, der jeweils eingeblendet wird. Die Wartezeit des Mal-Threads ist mit drei Millisekunden sehr kurz gewählt, damit auch relativ schnelle Mausbewegungen registriert werden. Andernfalls könnten in den gezeichneten Linien auffällige Lücken entstehen. Damit die Linien nicht zu dünn aussehen, werden übrigens vier im Quadrat nebeneinander liegende Linien gezeichnet. Abbildung 6.5 zeigt das Malprogramm in Aktion.


Abbildung 6.5   Das Malprogramm mit aufgeklapptem Farbmenü

Abbildung
Hier klicken, um das Bild zu Vergrößern



Galileo Computing

6.4.4 Java-Applets  toptop

Ein Java-Applet ist ein spezielles kleines Java-Programm, das in einem Webbrowser ausgeführt werden kann. Gegenüber einer »normalen« Java-Anwendung besitzt es einige Besonderheiten:

gp  Jedes Applet muss von der Klasse java.applet.Applet abgeleitet werden. Da diese wiederum eine abgeleitete Klasse von java.awt.Panel ist, können Sie darin beliebige grafische Komponenten verwenden, die weiter oben beschrieben werden.
gp  Ein Applet benötigt keine main()-Methode. Falls Sie eine schreiben sollten, wird sie ohnehin nicht aufgerufen. Stattdessen kann ein Applet die besondere Methode init() besitzen, die unmittelbar nach dem Laden des Applets ausgeführt wird. Anders als main() in Java-Anwendungen ist init() keine statische Methode, weil ein Applet im Webbrowser gewissermaßen eine Instanz ist.
gp  Die Anzeigegröße des Applets wird von der Webbrowser-Umgebung festgelegt: Es wird in eine Webseite eingebettet, in der Höhe und Breite angegeben werden.

Wenn Sie sich an diese einfachen Regeln halten, lassen sich sämtliche AWT-Anwendungen aus diesem Abschnitt leicht in Applets umwandeln.

Das kleine Applet in Listing 6.18 besitzt lediglich eine paint()-Methode, die einen linearen Farbverlauf von Rot nach Grün berechnet und zeichnet.

Listing 6.18   Ein einfaches Java-Applet

import java.applet.*;
import java.awt.*;
 
public class GradientApplet extends Applet
{
   public void paint (Graphics g)
   {
      int rot1 = 255;
      int gruen1 = 0;
      int blau1 = 0;
      int rot2 = 0;
      int gruen2 = 255;
      int blau2 = 0;
      int steps = 400;
      double rotStep = (rot2 - rot1) / (double)steps;
      double gruenStep = (gruen2 - gruen1)            / (double)steps;
      double blauStep = (blau2 - blau1)            / (double)steps;
      double ra = (double)rot1;
      double ga = (double)gruen1;
      double ba = (double)blau1;
      for (int i = 1; i <= steps; i++) {
          g.setColor (new Color ((int)ra, (int)ga,                (int)ba));
          g.drawLine (0, i, 400, i);
          ra += rotStep;
          ga += gruenStep;
          ba += blauStep;
      }
   }
}

Applets in Webseiten einbetten

Nach dem Kompilieren müssen Sie das Applet in ein HTML-Dokument einbetten. Die minimale Fassung eines solchen Dokuments sieht so aus:

<html>
  <head>
    <title>Farbverlaufs-Applet</title>
  </head>
  <body>
    <applet code="GradientApplet.class" 
width="400" height="400"></applet>
  </body>
</html>

Wie das Einbetten von Applets detailliert funktioniert, wird in Kapitel 16, HTML und XHTML, beschrieben.






  

Einstieg in PHP 5

Einstieg in Java

C von A bis Z

Einstieg in C++

Einstieg in Linux

Einstieg in XML

Apache 2




Copyright © Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de