Java-Pattern Tutorial

Kapitel
Grundlagen Beispielcode UML-Darstellung Übung
Home

Einführung

GUI programmieren

Patterns

   MVC

Verzeichnisse

GUI programmieren

Grundlage für die Programmierung einer grafischen Benutzeroberfläche ist die Kenntnis des Abstract Windowing Toolkit (kurz: AWT), Ereignissen, AWT-Komponenten und Layout-Managern.

AWT - Abstract Windowing Toolkit

Das AWT ist neben SWING und SWT (nur in der Entwicklungsumgebung "Eclipse" enthalten) eine Klassenbibliothek, um grafische Anwendungen zu programmieren. AWT benutzt entgegen den anderen beiden Klassenbibliotheken das darunter liegende Betriebssystem um seine grafischen Elemente anzuzeigen.

AWT stellt folgende Funktionalität bereit:

  • Anzeige von Bitmaps
  • Abspielen von Sound
  • Zeichnen geometrischer Objekte
  • Schriften
  • Komponenten (Schaltflächen, Menüs,...)
  • Ereignisbehandlung

Die Klassenhierarchie sieht folgendermaßen aus:

Abb. 2.1: Klassenhierarchie AWT

Unterhalb der Klasse java.util.EventObject befindet sich die Klasse java.awt.EventObject, die für die Ereignisbehandlung zuständig ist. Der Zweig java.awt.Component enthält alle Komponenten, die zum Bau einer grafischen Benutzeroberfläche benötigt werden.
Klassenname Aufgabe
java.awt.Component
  • Größe und Position einer Komponente
  • Grafische Eigenschaften
  • Ereignisse empfangen
java.awt.Container
  • Aufnahme von Komponenten
  • Entfernen von Komponenten
  • Anordnen von Komponenten
java.awt.Window
  • Erzeugt Top-Level-Fenster ohne Rahmen, Menü oder Titelleiste
java.awt.Frame
  • Erzeugt Fenster, wie es uns von Windows oder KDE/Gnome bekannt ist

 

Aufbau eines Fensters 

Quelle: [Moser, 2004]

Abb. 2.2: Elemente eines Fensters der Klasse java.awt.Frame 

Über die Klassendokumentation von java.awt.Frame erhält man Zugriff auf die Konstruktoren und Methoden, die diese Klasse zur Verfügung stellt. Mit diesem Link kann auf die Onlinedokumentation von Sun zugegriffen werden. Unter Package "java.awt" --> Classes "Frame" findet man diese Angaben. 

Folgender Beispielcode, der nur ein Label mit Text anzeigt. Außerdem das Schließfeld implementiert.

import java.awt.*;
import java.awt.event.*;


public class LabelFenster extends Frame {

  Label lbl; // Fensterkomponenten deklarieren

  // Konstruktor
  public LabelFenster () {

  // Fenster
  super("Label Fenster Beispiel"); // Titel des Fensters
  this.setLocation(50,50);         // Position des Fensters
  this.setSize(200, 100);          // Groesse des Fensters
  this.setBackground(Color.lightGray); // Hintergr.Farbe
  this.setLayout(null);            // Layoutmanager ausschalten

  // Label
  this.lbl = new Label("Hallo liebe Benutzer!"); //Nachricht
  this.lbl.setLocation(30,40);     // Position Label
  this.lbl.setSize(150, 30);       // Groesse Label
  this.add(this.lbl);              // Label Fenster hinzufuegen

  // Fenster "vollenden"
  this.addWindowListener(new WindowAdapter() {

         public void windowClosing(WindowEvent we) {

              System.exit(0);
         }
      });

  // Fenster anzeigen
  this.setVisible(true);

  }

  public static void main(String [] args) {

   LabelFenster fenster = new LabelFenster();
  }

}

Tab. 2.1: Beispieldatei FensterLabel.java

In diesem Programm werden ganz grundlegende Schritte angewandt, um eine Fenster zu programmieren.
  1. Es müssen die richtigen Klassen importiert werden (java.awt.* und java.awt.event.*)
  2. Die zu erstellende Klasse "LabelFenster" muss von Klasse java.awt.Frame abgeleitet werden.
  3. Die Fensterkomponenten müssen deklariert werden.
  4. Anschließend wird im Konstruktor der Klasse "LabelFenster" das Fenster aufgebaut.
  5. In der main-Methode wird ein Objekt vom Typ LabelFenster erstellt.
  6. Fertig!

zu 4.: Fenster aufbauen:

  • Fenster programmieren
  • Komponenten (hier: lbl) programmieren
  • WindowAdapter, um die Fensterfelder Minimieren, Maximieren und Schließen zu  programmieren
  • Fenster anzeigen.

Die gebräuchlichsten Methoden um ein Fenster zu programmieren sind in diesem Beispiel aufgeführt. Die Aufgaben sind in der Zeile als Kommentare enthalten. Nähere Informationen finden Sie in der Java-Dokumentation in der entsprechenden Klasse.

Ereignisse

Ereignisse ermöglichen erst die Kommunikation des Benutzers mit der GUI.

Ereignisse bestehen aus einer Ereignisquelle und einem Ereignisziel. Die Ereignisquelle stellen Komponenten dar, zum Beispiel Schaltflächen und Textfelder. Ereignisziele sind so genannte Listener, die auf ein Ereignisauslösung warten.

Das Zusammenspiel zwischen Ereignisquelle und Ereignisziel geschieht über das Delegation Event Modell.

Delegation Event Modell

Um ein Ereignis behandeln zu können muss ein Ereignisziel sich bei einer Ereignisquelle registrieren. Danach kann eine Ereignisquelle das Ereignis an das Ereignisziel zur Verarbeitung weitergeben.

Abb. 2.3: Delegation Event Modell

Ereignisklassen

Es gibt Low-Level-Ereignisse und sematische Ereignisse. Low-Level-Ereignisse können den grafischen Komponenten des Fensters zugeordnet werden. Semantische Ereignisse beziehen sich auf "höherwertigere" Ereignisse wie Zustandsänderungen oder Aktionen.

Abb. 2.4: Klassenhierarchie Ereignisklassen

Listener-Interfaces

Diese Listener-Interfaces sind dafür zuständig, dass Ereignisse entgegengenommen werden können.

Abb. 2.5: Listener Klassenhierarchie

Bringt man alles zusammen, so erhält man zum Beispiel folgenden Quelltext:
import java.awt.*;
import java.awt.event.*;

public class EingabeFenster extends Frame implements MouseListener {

 Label lbl, lblAusgabe;
 TextField txtEingabe;
 Button btn;

 public EingabeFenster() {

  // Fenster
  super("Eingabe Fenster Beispiel");
  this.setLocation(50,50);
  this.setSize(300, 200);
  this.setBackground(Color.lightGray);
  this.setLayout(null);

  // Textfield txtEingabe
  txtEingabe = new TextField();
  txtEingabe.setLocation(20,30);
  txtEingabe.setSize(250, 20);
  this.add(txtEingabe);

  // Label lbl
  lbl = new Label("Eingegebener Text:");
  lbl.setLocation(20,60);
  lbl.setSize(250,30);
  this.add(lbl);

  // Label lblAusgabe
  lblAusgabe = new Label("");
  lblAusgabe.setLocation(20,90);
  lblAusgabe.setSize(250,30);
  this.add(lblAusgabe);

  // Button btn
  btn = new Button("Text anzeigen!");
  btn.setLocation(20,150);
  btn.setSize(100,30);
  btn.addMouseListener(this);

  this.add(btn);

  // Ereignisbehandlung ueber Adapterklasse
  this.addWindowListener(new WindowAdapter() {

   public void windowClosing(WindowEvent we) {

    System.exit(0);
   }
  });


  this.setVisible(true);
 }
 // Ereignisbehandlung ueber Interface
 public void mouseClicked(MouseEvent me) {

  lblAusgabe.setText(txtEingabe.getText());
  txtEingabe.setText("");
  txtEingabe.requestFocus();
 }

 public void mouseReleased(MouseEvent me) {}

 public void mousePressed(MouseEvent me) {}

 public void mouseEntered(MouseEvent me) {}

 public void mouseExited(MouseEvent me) {}


 public static void main(String[] args) {

  EingabeFenster fenster = new EingabeFenster();
 }
}

Tab. 2.2: Beispieldatei "EingabeFenster.java

Wichtig ist an dieser Stelle, dass verstanden wird, welche Komponente des Fensters zu einer Ereignisquelle wird und welche nicht. In diesem Fall ist es ausschließlich der Button btn, da die anderen Komponenten nur passiv sind. Das Textfeld ist ebenfalls nur passiv, da die Texteingabe nicht durch das Textfeld gesteuert wird, sondern vom Button. Passive Fensterkomponenten können aber dennoch mit Daten gefüttert werden. Sie lösen nur keine Ereignisse aus.

Abb. 2.6: Delegation Event Modell zu Beispiel EingabeFenster.java

Registrieren

Das Registrieren erfolgt beim definieren des Buttons. Hier wird dem Ereignisziel mitgeteilt, dass der Button ein Ereignis liefert. Dieses Ereignis ist ein Mausklick auf den Button, der vom MouseListener übernommen wird.

Delegieren

Geschieht durch das ausprogrammieren der Methoden, die das MouseListener-Interface bereitstellt. Falls dem MouseListener mehrere Ereignisquellen zugeordnet sind, muss durch eine if-Abfrage die Ereignisquelle bestimmt werden. Das ist hier aber nicht der Fall. Darum wird die Methode mouseClicked(MouseEvent me) mit der konkreten Aktion, die beim Klick auf den Button ausgelöst wird, ausprogrammiert. Also wird der Text des Textfeldes direkt im Label ausgegeben. Anschließend wird das Textfeld gelöscht und angewählt

Ereignisbehandlung implementieren

Im oberen Beispiel sind zwei Arten der Benutzung von Ereignisbehandlung vorgegeben.

Implementierungsart Anwendung
Adapterklasse Interfaceklassen mit vielen Methoden
Interface implementieren Interfaceklassen mit wenigen Methoden

Adapterklasse

// Ereignisbehandlung ueber Adapterklasse
this.addWindowListener(new WindowAdapter() {

 public void windowClosing(WindowEvent we) {

 
System.exit(0);
 }
});

Wie oben erwähnt müssen bei einer Adapterklasse nur die Methoden implementiert werden, die tatsächlich benötigt werden. In diesem Fall wird das Schließenfeld eines Fensters mit dem Beenden des Programms belegt. Würde man diese Funktion über ein Interface implementieren, dann müssten alle Methoden aufgeführt werden. Egal ob mit oder ohne Funktion.

Interface implementieren

public class EingabeFenster extends Frame implements MouseListener {

 ...
 
// Ereignisbehandlung ueber Interface
  public void mouseClicked(MouseEvent me) {

  lblAusgabe.setText(txtEingabe.getText());
  txtEingabe.setText("");
  txtEingabe.requestFocus();
  }

  public void mouseReleased(MouseEvent me) {}

  public void mousePressed(MouseEvent me) {}

  public void mouseEntered(MouseEvent me) {}

  public void mouseExited(MouseEvent me) {}
 
...
}

Hier wird die Ereignisbehandlung über das Implementieren eines Interface erreicht. Das Gute daran ist, dass die potentiellen Möglichkeiten dieses Listeners dargestellt sind. Der Nachteil ist, dass leere Methoden übernommen werden müssen.

Darum wird je nach Umfang des Interface zwischen den beiden Implementierungsarten (Adapterklasse oder Interface) gewechselt.

Layoutmanager

Ein Layoutmanager übernimmt die Anordnung der Fensterkomponenten automatisch. In den bisherigen Beispielen wird kein Layoutmanager verwendet.

this.setLayout(null);

Folgende Layoutmanager stehen zur Auswahl:

Abb. 2.7: Layoutmanager

Unabhängig davon ob ein Layoutmanager eingesetzt wird oder nicht werden die Elemente genau in der Reihenfolge ins Fenster eingefügt in der sie durch den Befehl

this.add(Komponente);

tatsächlich dem Fenster zugeordnet werden. Das ist besonders zu beachten, da Fenster auch mit der Tastatur steuerbar sind (Tabulator-Taste). Falls die Reihenfolge eine Rolle spielen soll, müssen die Komponenten in der selben Reihenfolge dem Fenster zugeordnet werden, wie die logische Anordnung auf dem Bildschirm sein soll.

Trennen von Applikationslogik und grafischer Benutzeroberfläche

Wie dem Codeumfang im letzten Beispiel entnommen werden kann, so wird ein ausgewachsenes GUI-Programm sehr umfangreich. Dadurch ist es wichtig eine Dreiteilung des Codes vorzunehmen:

  1. Hauptprogramm
  2. GUI
  3. Anwendungslogik

Dazu das obere Beispiel darauf abgebildet:

import java.awt.*;
import java.awt.event.*;

public class EventDelegationFenster {

 // Part 1: Hauptprogramm
 public static void main(String[] args) {

  EreignisBehandlung ereignis = new EreignisBehandlung();
  EDFenster fenster = new EDFenster(ereignis);
 }
}

// Part 2: GUI
class EDFenster extends Frame {

 Label lbl, lblAusgabe;
 TextField txtEingabe;
 Button btn;

 public EDFenster(EreignisBehandlung ereignis) {

  // Fenster
  super("Eingabe Fenster Beispiel");
  this.setLocation(50,50);
  this.setSize(300, 200);
  this.setBackground(Color.lightGray);
  this.setLayout(null);

  // Textfield txtEingabe
  txtEingabe = new TextField();
  txtEingabe.setLocation(20,30);
  txtEingabe.setSize(250, 20);
  this.add(txtEingabe);

  // Label lbl
  lbl = new Label("Eingegebener Text:");
  lbl.setLocation(20,60);
  lbl.setSize(250,30);
  this.add(lbl);

  // Label lblAusgabe
  lblAusgabe = new Label("");
  lblAusgabe.setLocation(20,90);
  lblAusgabe.setSize(250,30);
  this.add(lblAusgabe);

  // Button btn
  btn = new Button("Text anzeigen!");
  btn.setLocation(20,150);
  btn.setSize(100,30);
  this.add(btn);

  this.setVisible(true);

  ereignis.setFenster(this);

  // Ereignisbehandlung
  btn.addMouseListener(ereignis);
  this.addWindowListener(ereignis);

 }
}

// Part 3: Anwendungslogik
class EreignisBehandlung implements WindowListener, MouseListener {

 // GUI
 private EDFenster fenster;

 public void setFenster(EDFenster fenster) {

  this.fenster = fenster;
 }

 // Alle Methoden des Interface WindowListener
 public void windowClosing(WindowEvent we) {

  System.exit(0);
 }


 public void windowOpened(WindowEvent we) {}
 public void windowActivated(WindowEvent we) {}
 public void windowClosed(WindowEvent we) {}
 public void windowDeactivated(WindowEvent we) {}
 public void windowDeiconified(WindowEvent we) {}
 public void windowIconified(WindowEvent we) {}

 // Alle Methoden des Interface MouseListener
 public void mouseClicked(MouseEvent me) {

  fenster.lblAusgabe.setText(fenster.txtEingabe.getText());
  fenster.txtEingabe.setText("");
  fenster.txtEingabe.requestFocus();
 }


 public void mouseReleased(MouseEvent me) {}
 public void mousePressed(MouseEvent me) {}
 public void mouseEntered(MouseEvent me) {}
 public void mouseExited(MouseEvent me) {}
}

Tab. 2.3: Beispieldatei EventDelegationFenster.java

Hier hat eine Trennung stattgefunden. Im Gegensatz zu dem ursprünglichen Beispiel nimmt der Umfang an geschriebenem Code zu. Bei steigender Komplexität bleibt der Code jedoch lesbar.

Die wichtigsten Elemente des oberen Codes werden kurz erläutert

:public class EventDelegationFenster {

  // Part 1: Hauptprogramm
  public static void main(String[] args) {

  EreignisBehandlung ereignis = new EreignisBehandlung();
  EDFenster fenster = new EDFenster(ereignis);
  }
}

Im Hauptprogramm werden die beiden Klassen instanziiert. Zuerst das Objekt ereignis vom Typ EreignisBehandlung. Anschließend wird dieses Objekt ereignis an das  Objekt fenster vom Typ EDFenster übergeben.

// Part 2: GUI
class EDFenster extends Frame {

  Label lbl, lblAusgabe;
  TextField txtEingabe;
  Button btn;

  public EDFenster(EreignisBehandlung ereignis) { // 1.

  // Fenster
  super("Eingabe Fenster Beispiel");
  ...

  // Textfield txtEingabe
  txtEingabe = new TextField();
  txtEingabe.setLocation(20,30);
  txtEingabe.setSize(250, 20);
  this.add(txtEingabe);

  // Label lbl
  ...

  // Label lblAusgabe
  lblAusgabe = new Label("");
  lblAusgabe.setLocation(20,90);
  lblAusgabe.setSize(250,30);
  this.add(lblAusgabe);

  // Button btn
  ...

  this.setVisible(true);

  ereignis.setFenster(this); // 2.

  // Ereignisbehandlung
  btn.addMouseListener(ereignis); // 3.
  this.addWindowListener(ereignis); // 3.

  }
}

Die Klasse EDFenster dient dem Aufbau der GUI. Somit unterscheidet sie sich nur an manchen Stellen vom ursprünglichen Beispiel aus Tab. 2.2. Diese Stellen sind fett hervorgehoben:

  1. Konstruktor übernimmt Objekt vom Typ Ereignisbehandlung.
  2. Bei den Abschnitt ereignis.setFenster(this) wird die GUI beim Objekt ereignis registriert.
  3. Den Ereignis-Listenern wird am Ende das Ereignis übergeben.

// Part 3: Anwendungslogik
class EreignisBehandlung implements WindowListener, MouseListener {

 // GUI
 EDFenster fenster;

 public void setFenster(EDFenster fenster) {

  this.fenster = fenster;
 }

 // Alle Methoden des Interface WindowListener
 public void windowClosing(WindowEvent we) {

 
System.exit(0);
 }


 ...

 // Alle Methoden des Interface MouseListener
 public void mouseClicked(MouseEvent me) {

 
fenster.lblAusgabe.setText(fenster.txtEingabe.getText());
  fenster.txtEingabe.setText("");
  fenster.txtEingabe.requestFocus();
 }


 ...
}

Im letzten Abschnitt wird die Anwendungslogik programmiert. Damit die grafische Benutzeroberfläche GUI angesprochen werden kann, muss sie über die set-Methode dem Objekt ereignis bekannt gemacht werden.

Somit können alle Fensterkomponenten problemlos in den Methoden angesprochen werden.

Fazit

In diesem Kapitel haben wir die Programmierung von grafischen Benutzeroberflächen erlernt.

Dazu gehört das Verständnis des Abstract Windowing Toolkit (AWT), Ereignisse und die Ereignisbehandlung, Layoutmanger und schließlich die Trennung von Anwendungslogik und GUI.

Mit diesen Grundlagen sind wir für die weiteren Kapitel über Patterns - Entwurfmuster - bestens gerüstet.