Du bist hier: Themen / OOP - Advanced / GUI's
Freitag, 19.04.2019

Grafische Benutzerschnittstellen

Grafische Benutzerschnittstellen (engl.: Graphical User Interface, GUI) werden immer nach einem relativ starren Schema programmiert. Die wichtigste Klasse ist dabei JFrame, die ein Programmfenster repräsentiert und sich im Paket javax.swing befindet. Eine mögliche Vorgehensweise lässt sich folgendem Schema entnehmen:

  1. Erstellen einer neuen Klasse, die Unterklasse von JFrame ist.
  2. Importieren der Pakete javax.swing, java.awt, java.awt.event
  3. Allgemeine Einstellungen und Initialisierungen im Konstruktor vornehmen
    • Verhalten beim Schließen setzen, meist: setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    • Den Layout-Manager festlegen
    • Attribute für Komponenten definieren, Kompenenten erzeugen und zum JFrame hinzufügen.
    • Die Anordnung der Komponenten anpassen: pack()
    • Das Fenster sichtbar machen: setVisible(true);
  4. Ereignisverwaltung organisieren.
  5. Optional: public static void main(String[] args) Methode implementieren. Diese erzeugt ein Objekt der eigenen Klasse.

Das Projekt

Ziel dieses Kapitels ist es eine grafische Benutzerschnittstelle für ein Programm zu schreiben, das Wetterdaten verschiedener Städte anzeigen kann. Die Klasse Weatherstation stellt eine virtuelle Wetterstation dar und ist schon fertig implementiert, bietet aber keine GUI.

Aufgabe 1: Erstelle ein neues BlueJ-Projekt, importiere die Klasse WeatherStation (add Class from file) und teste das Verhalten einer Wetterstation, indem Du die Schnittstellendokumentation liest, ein Objekt erzeugst und Methoden aufrufst. Ein fertiges Programm könnte dann z.B. so aussehen.

Hinweise:

Grundgerüst einer GUI

Der Quellcode für eine sehr einfache GUI sieht demnach folgendermaßen aus:

// Importiert alle Klassen aus den wichtigsten Paketen zur GUI-Entwicklung
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

// Definiert eine neue Klasse als Unterklasse von JFrame
public class MeineGUI extends JFrame
{
    public MeineGUI()
    {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        pack();
        setVisible(true);
    }
}

Aufgabe 2: Erstelle ein neues BlueJ-Projekt und schreibe eine einfache GUI. Diese soll später erweitert werden, so dass daraus ein Programm wird, das die Wetterdaten verschiedener Städte anzeigen kann.

Komponenten hinzufügen

Alle Elemente, die man auf dem Fenster sieht, nennt man Komponenten, da sie durch Unterklassen von Component realisiert werden. Es gibt eine Vielzahl von Komponenten wie Buttons (JButton), Menüleisten(JMenuBar), Eingabefelder(JTextField) etc.

Um eine Komponente hinzuzufügen, definiert man üblicherweise ein Attribut pro Komponente, um auch nach dem Erzeugen Zugriff auf diese zu haben. Im Konstruktor der Fensterklasse wird die Komponente dann erzeugt und dafür gesorgt, dass sie dem Fenster auch optisch hinzugefügt wird:

    private JLabel halloLabel;

    public MeineGUI()
    {
        // ... weitere Anweisungen hier ausgelassen
        halloLabel = new JLabel("Hallo Welt");
        add(halloLabel);
    }

Aufgabe 3: Füge ein JLabel hinzu und sorge dafür, dass es angezeigt wird. Beachte was passiert, wenn man eine zweite Komponente hinzufügen möchte.

Layout-Manager

Um die verschiedenen Komponenten entsprechend zu plazieren, benutzt ein JFrame einen LayoutManager. Es gibt verschiedene Typen von Layout-Managern, die dann dafür sorgen, dass die Komponenten z.B. in einer Zeile (FlowLayout), in einem Gitter (GridLayout) oder an den verschiedenen Seiten eines Fensters (BorderLayout) erscheinen.

Um den LayoutManager zu setzen, besitzt ein JFrame die Methode void setLayout(LayoutManager manager).

FlowLayout

Bei einem FlowLayout werden Komponenten horizontal nebeneinander angeordnet, so lange Platz ist. Danach werden weitere Komponenten umgebrochen:

// im Konstruktor des JFrames
setLayout(new FlowLayout());
add(new JButton("Ein Button"));
add(new JButton("Noch einer"));
add(new JButton("Der dritte Button"));

bzw. bei weniger horizontalem Platz

GridLayout

Sollen die Elemente gitterförmig angeordnet werden, ist ein GridLayout das Mittel der Wahl:

// Setze ein GridLayout mit drei Zeilen und zwei Spalten.
setLayout(new GridLayout(3, 2));
// Füge zeilenweise Komponenten hinzu.
add(new JTextField("Ein Text"));
add(new JButton("Ein Button"));
add(new JTextField("Noch ein Text"));
add(new JButton("Noch einer"));
add(new JTextField("Der dritte Text"));
add(new JButton("Der dritte Button"));


BorderLayout

Viele Anwendungen besitzen einen zentralen Arbeitsbereich mit am Rand befindlichen Steuer- oder Anzeigeelementen. Dafür eignet sich ein BorderLayout gut. Beim Hinzufügen von Komponenten muss eine der überladenen add-Methoden benutzt werden, um die Position im Layout anzugeben:

setLayout(new BorderLayout());
// Der zweite Parameter ist vom Typ int und als Konstante definiert.
add(new JButton("Brrrrr..."), BorderLayout.NORTH);
add(new JButton("Die goldene Mitte"), BorderLayout.CENTER);
add(new JButton("Im Osten geht die Sonne auf"), BorderLayout.EAST);
add(new JButton("Im Westen nichts Neues"), BorderLayout.WEST);
add(new JButton("Der Sonne hinterher"), BorderLayout.SOUTH);


Aufgabe 4: Experimentiere mit den verschiedenen Layout-Managern und füge weitere Komponenten in Dein Programm hinzu.

Weitere LayoutManager

Es gibt noch einige andere LayoutManager, die für Spezialfälle oder komplexere Layouts gedacht sind. Außderdem kann man mehrere LayoutManager kombinieren, um z.B. im nördlichen Bereich eines BorderLayouts ein FlowLayout einzusetzen. Für uns ist hier allerdings nur das Konzept wichtig. Die Dateilarbeit werden wir später mit entsprechenden Softwaretools erledigen.

Ereignisbehandlung

Unsere GUI bietet bis jetzt noch keine Möglichkeit auf irgendwelche Ereignisse wie z.B. Benutzereingaben zu reagieren. Wir brauchen also eine Möglichkeit zur Ereignisbehandlung. Dazu muss (mindestens) ein Objekt dafür zuständig sein auf Ereignisse zu reagieren. Am einfachsten lässt sich dies realisieren, wenn dieses Objekt das Hauptfenster der Anwendung ist.

Sobald der Benutzer z.B. auf einen Button klickt, soll das Hauptfenster davon in Kenntnis gesetzt werden und entsprechend reagieren.

 

Der Button weiß im Moment allerdings noch nicht, dass das GUI-Fenster benachrichtigt werden möchte, wenn er geklickt wurde. Deshalb stellt ein Button - und viele andere Komponenten auch - eine Methode zur Verfügung, die es einem Objekt ermöglichen, sich als ActionListener anzumelden: void addActionListener(ActionListener listener).

Das anzumeldende Objekt muss vom Typ ActionListener sein, damit der Button auch sicher sein kann, dass das Objekt entsprechend auf eine Nachricht reagieren kann. ActionListener ist ein Interface, das die Implementierung der Methode public void actionPerformed(ActionEvent evt) verlangt. In unserem Fall bedeutet das, dass unsere GUI das Interface ActionListener aus dem Paket java.awt.event implementieren muss.

Innerhalb dieser Methode muss dann dafür gesorgt werden, dass auf den Mausklick entsprechend reagiert wird.

Der Code der GUI muss also folgendermaßen ergänzt werden:

public class MeineGUI extends JFrame implements ActionListener
{
    private JButton button;

    public MeineGUI()
    {
        button = new JButton("Klick mich");
        button.addActionListener(this);
        add(button);
    }

    public void actionPerformed(ActionEvent e)
    {
        System.out.println("Der Button wurde geklickt!!!");
    }

Aufgabe 5: Erweitere Dein Programm um Ereignisbehandlung.

Mehrere Ereignisquellen

Im Normalfall hat man mehrere Komponenten, die Ereignisse auslösen können.  Da das ActionEvent Objekt Informationen über den Auslöser enthält, kann so unterschieden werden, wer das Ereignis ausgelöst hat:

    public void actionPerformed(ActionEvent e)
    {
        // Überprüfe, ob das button-Objekt die Quelle ist.
        if(e.getSource() == button)
        {
            System.out.println("Der Button wurde geklickt!!!");
        }
    }

Aufgabe 6: Füge mindestens einen Button zum Beenden der Anwendung hinzu. (siehe Abschnitt Verschiedenes -> Java Codeschnipsel)

Speziellere Ereignisse

Ein ActionEvent ist ein sehr einfaches Ereignis, das allerdings voll und ganz ausreicht, um festzustellen, dass z.B. ein Button geklickt oder Menüeintrag ausgewählt wurde. Es gibt noch eine Reihe anderer Ereignisse wie z.B. MouseEvent oder WindowEvent, die z.B. ausgelöst werden, wenn die Maus über einer Komponente ist oder das Fenster minimiert wird.

Deren Benutzung verläuft analog und soll hier nicht näher erläutert werden, da es uns auch hier um die Prinzipien geht, wir die Detailarbeit aber später von entsprechenden Softwaretools erledigen lassen.

Anonyme innere Klassen

Die oben dargestellte Vorgehensweise besitzt ein paar kleinere Nachteile. Ein Nachteil ist z.B., dass beim Umbenennen des Buttons dessen Name auch in der actionPerformed-Methode geändert werden muss. Somit können hier leicht Flüchtigkeitsfehler entstehen.

Außerdem wäre es schöner, wenn man beim Erzeugen des Buttons auch direkt festlegen könnte, wie bei einen Klick darauf reagiert werden soll anstatt dies über den Umweg der zentralen actionPerformed-Methode zu tun.

Java bietet mit dem Konzept von anonymen inneren Klassen eine Möglichkeit dies zu tun. Allerdings hat das den Nachteil, dass der Code dadurch konzeptionell schwieriger wird. Da man anonyme innere Klassen außer bei der Erstellung von GUIs nur selten braucht, sollen diese hier nicht näher erläutert werden. Bei Interesse lässt sich diese Vorgehensweise im Netz z.B. unter http://java.sun.com/docs/books/tutorial/uiswing/events/generalrules.html#innerClasses nachlesen.

Programmstart

Beim Starten eines Programms außerhalb von BlueJ, muss ein Punkt definiert werden, bei dem die Anwendung startet. Da es zu Beginn noch kein Objekt gibt, muss es sich dabei um eine Klassenmethode handeln. Diese muss per Konvention die Signatur public static void main(String[] args) besitzen. Soll eine Anwendung mit dem Erzeugen eines Fensters starten lässt sich dies somit folgendermaßen erreichen:

public static void main(String[] args)
{
    MeineGUI gui;
    gui = new MeineGUI();
}

In BlueJ lässt sich dann das ganze Programm über "Project->Create Jar File..." in eine einzelne ausführbare Datei exportieren. Dabei muss die Klasse angegeben werden, welche die main-Methode enthält. Die erstellte jar-Datei lässt sich dann auf jedem Rechner, auf dem java installiert ist, ausführen. Sie enthält alle compilierten Java-Dateien und evtl. zusätzliche Dateien in gepackter Form.

Aufgabe 7: Exportiere Dein Programm in eine jar-Datei und teste diese.