Events

Inhalt

 

 

Event-Listener

 

Action Events

 

Mouse Events

 

Key Events

 

Andere Events

 

Adapter-Klassen

 

Anonyme Klassen

 

Übungsaufgaben

Event-Listener

Wie wir schon im Kapitel über Fenster gesehen haben, beruht die Verwaltung einer Benutzeroberfläche auf dem Konzept des Ereignisses. Ein Ereignis ist normalerweise eine von Benutzer angestoßene Aktion, wie ein Tastendruck oder ein Mausklick. Andere Systeme stellen aber auch andersartige Ereignisse zur Verfügung, wie etwa einen Timer, der in gewissen Intervallen ein Ereignis auslöst. Eine andere Möglichkeit wäre die Ankunft eines Datenpaketes aus dem Internet.

Es stellt sich die Frage, wann Events verarbeitet werden. Es gibt dabei drei Möglichkeiten

  1. Möglichkeit: Events werden sequentiell verarbeitet. In diesem Fall werden die Events in eine Warteschlange (event queue) eingereiht, und die nächste Verarbeitung wird erst angestoßen, wenn die vorherige beendet ist. So funktioniert die Verarbeitung in Java. Der Nachteil ist, das eine lange Verarbeitung die Benutzerinteraktion blockiert.
  2. Möglichkeit: Events werden in separaten Threads behandelt. Dann muss der Programmier für eine korrekte Synchronisation dieser Threads sorgen. Dies ist keine leichte Aufgabe. Bei den meisten Benutzereingaben würde das ohnehin nur darauf herauslaufen, dass das Programm warten müsste, bis die Verarbeitung des letzten Ereignisses komplett ist.
  3. Möglichkeit: Es gibt mehrere Warteschlangen. Das ist ein Zwischending zwischen den beiden Extremen. In Java gibt es für Benutzereingaben nur eine Warteschlange. Andere Ereignisbehandler werden vom Programmierer separat implementiert, was einer anderen Warteschlange entspricht.

Wir haben bisher nur eine Event-Verarbeitung kennen gelernt, nämlich die Verarbeitung des Repaint-Ereignisses in einer graphischen Komponenten. Dies geschah dadurch, dass die paint-Methode überlagert wurde. Diese Art der Verarbeitung durch Überlagerung wird in Java nur noch sehr restriktiv eingesetzt.

Die Ereignisbehandlung von Java beruht auf Objekten, die die Ereignisse entgegennehmen können. Diese Objekte heißen Event-Listener. Sie implementieren gewisse Interfaces und damit gewisse Methoden, die die Ereignisse behandeln. Außerdem müssen sich die Objekte bei den Event-Erzeugern anmelden. Es können sich dabei aber mehrere Listener bei einem Erzeuger anmelden.

Als Nebeneffekt des Listener-Modells wird die Implementation der Oberflächenelemente und ihrer Anordnung von der Programmlogik, die ja in der Verarbeitung der Ereignisse liegt, getrennt. Man kann damit das Aussehen des Programms und seine Funktionalität getrennt betrachten.

Action Events

Als Beispiel behandeln wir das Ereignis, dass ein Knopf gedrückt wird. Dazu wird ein JButton in ein Fenster eingefügt. Als Event-Listener fungiert eine Methode, die das Programm beendet.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

class CloseButton extends JButton implements ActionListener
{
    public CloseButton ()
    {
        super("Close");
        addActionListener(this);
    }

    @Override
    public void actionPerformed (ActionEvent e)
    {
        System.exit(0);
    }
}

public class ButtonTest
{
    public static void main (String args[])
    {
        JFrame F = new JFrame();
        F.setTitle("Test");
        F.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Als Action Listener registrieren:
        F.add(new CloseButton());

        // Anzeigen:
        F.pack();
        F.setLocationRelativeTo(null);
        F.setVisible(true);
    }
}

Wie man sieht, schafft dieses Beispiel eine neue Instanz eines ActionListener, um auf das Drücken des Button zu reagieren. Dazu muss diese Instanz als ActionListener beim Button registriert werden. Falls nun der Knopf gedrückt wird, so benachrichtigt er alle registrierten ActionListener davon, indem er deren Methode actionPerformed() aufruft. Als Parameter wird eine Instanz von ActionEvent übergeben, die nähere Informationen zum Ereignis enthält.

Ein Knopf kann mehrere ActionListener bedienen. Ein ActionListener kann sich mit Hilfe der Funktion removeActionListener() wieder aus der Liste der Ereignisempfänger entfernen.

Man sieht an diesem Beispiel auch, wie der Aufbau der Oberfläche von der Ereignisbehandlung getrennt wird. Schließlich kann jede Klasse als Listener fungieren, der das Ereignis abarbeitet.

Das Beispiel lässt sich auch anders organisieren, indem man eine eigene JFrame-Klasse schreibt. Diese kann sich dann selber als EventListener eintragen, und in einer Routine die Quelle des Events abfragen. Das sieht so aus.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

class TestFrame extends JFrame
        implements ActionListener
{
    JButton Close; // Der Schließen-Knopf

    /**
     * Initialisiere das Fenster. Es enthält nur einen Schließen-Knopf.
     */
    public TestFrame ()
    {
        setTitle("Test");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Knopf einfügen:
        Close = new JButton("Close");
        Close.addActionListener(this);
        add(Close);

        pack();
    }

    /**
     * Der ActionListener, derzeit nur für den Knopf
     */
    @Override
    public void actionPerformed (ActionEvent e)
    {
        if (e.getSource() == Close)
        {
            dispose();
        }
    }
}

public class Test
{
    public static void main (String args[])
    {
        JFrame F = new TestFrame();
        F.setLocationRelativeTo(null);
        F.setVisible(true);
    }
}

Das Hauptprogramm erzeugt nun lediglich eine Instanz von TestFrame. Diese Klasse übernimmt alles selbst und verwaltet auch die Ereignisse ihrer Knöpfe. Da wir eventuell mehrere Knöpfe einfügen möchten, testen wir, woher das Ereignis kommt.

Mouse Events

Als Beispiel für Mausereignisse reagieren wir auf das Anklicken im Innern eines TestCanvas. Es soll nur einfach die Koordinate des Klicks ausgegeben werden.

Das Interface heißt hier MouseListener und implementiert fünf Methoden. Wir registrieren das Canvas selber als MouseListener. Das Event heißt MouseEvent und hat z.B. die Methoden getX() und getY(), womit die Koordinaten des Klicks abgefragt werden können.

In diesem Beispiel registriert sich wieder die Komponente selbst als Event-Listener.

import java.awt.Canvas;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.JFrame;

class TestCanvas extends Canvas implements MouseListener
{
    public TestCanvas ()
    {
        addMouseListener(this);
    }

    // Methoden von MouseListener

    @Override
    public void mousePressed (MouseEvent e)
    {
        System.out.println(e);
    }

    @Override
    public void mouseReleased (MouseEvent e)
    {
        System.out.println(e);
    }

    @Override
    public void mouseClicked (MouseEvent e)
    {
        System.out.println(e);
        System.out.println(
                "Mausklick bei " + e.getX() + "," + e.getY());
    }

    @Override
    public void mouseEntered (MouseEvent e)
    {
        System.out.println(e);
    }

    @Override
    public void mouseExited (MouseEvent e)
    {
        System.out.println(e);
    }
}

public class Test
{
    public static void main (String args[])
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(new TestCanvas());
        f.setSize(300, 300);
        f.setVisible(true);
    }
}

Man sieht, wenn man dieses Beispiel laufen lässt, dass mouseClicked() nur aufgerufen wird, wenn die Maus an derselben Stelle gedrückt und losgelassen wurde. Außerdem sieht man, dass es schwer ist, Doppelklicks zu entdecken. Die dazu nötigen Techniken müssen wir auf später verschieben.

Will man die Bewegung der Maus verfolgen, so benötigt man einen MouseMotionListener. Die Technik ist ziemlich ähnlich. Entnehmen Sie daher die entsprechenden Klassen der Dokumentation zu Java. Will man Zeichenroutinen mit diesen Events verbinden, so kann man repaint() verwenden. Es ist allerdings unter Umständen nötig, einen Grafikpuffer einzurichten und die gezeichnete Grafik als Ganzes zu übertragen. Diese Techniken werden wir erst später behandeln.

Key Events

Tastaturereignisse werden von dem KeyListener-Interface behandelt. Man hat drei Methoden zu implementieren. Hier wieder ein Beispiel-Code.

import java.awt.Canvas;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

class TestCanvas extends Canvas implements KeyListener
{
    public TestCanvas ()
    {
        addKeyListener(this);
    }

    // Methoden von KeyListener:

    public void keyPressed (KeyEvent e)
    {
        if (e.isActionKey())
            System.out.println("Action-Key : " + e.getKeyCode());
        else if (e.getKeyCode() == KeyEvent.VK_DELETE) // Bug?
            System.out.println("Action-Key : " + e.getKeyCode());
    }

    public void keyReleased (KeyEvent e)
    {
    }

    public void keyTyped (KeyEvent e)
    {
        System.out.println("Character : " + e.getKeyChar());
    }
}

Wie man sieht, erzeugen normale Buchstabentasten keyTyped()-Aufrufe und Sondertasten nicht. Diese fängt man mit keyPressed() ab. Durch einen Fehler wird die Delete-Taste nicht als Action-Taste erkannt.

Die Codes der Action-Tasten sind alle in KeyEvent dokumentiert. Beipiele sind VK_F1 usw.

Andere Events

Als weiteres Beispiel für einen Event-Listener behandeln wir WindowListener. Dieses Interface dient dazu, auf Ereignisse zu reagieren, die den Status des Fensters betreffen. Es implementiert eine ganze Reihe von Methoden. Die Klasse TestFrame muss alle diese Methoden implementieren, obwohl nur eine benötigt wird. Diese eine Methode ist windowClosing() und wird aufgerufen, wenn der Benutzer den Schließknopf im Fensterrahmen betätigt, oder das Betriebssystem das Fenster schließen will.

import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import javax.swing.JFrame;

class Test extends JFrame
        implements WindowListener
{
    public Test ()
    {
        super("Test Frame");
        setSize(300, 300);
        addWindowListener(this);
    }

    // Methoden von WindowListener:

    @Override
    public void windowActivated (WindowEvent e)
    {
    }

    @Override
    public void windowClosed (WindowEvent e)
    {
    }

    @Override
    public void windowClosing (WindowEvent e)
    {
        // Schließe das Fenster und beende das Programm:
        dispose();
        System.exit(0);
    }

    @Override
    public void windowDeactivated (WindowEvent e)
    {
    }

    @Override
    public void windowDeiconified (WindowEvent e)
    {
    }

    @Override
    public void windowIconified (WindowEvent e)
    {
    }

    @Override
    public void windowOpened (WindowEvent e)
    {
    }

    public static void main (String args[])
    {
        JFrame F = new Test();
        F.setVisible(true);
    }
}

Wir haben hier eine weitere Methode gewählt, um unser Programm zu testen. Die Fensterklasse ist hier Test selber, und sie enthält auch das Hauptprogramm main. Das ist eine gute Art, Testroutinen in Klassen, einzubauen.

Es gibt eine ganze Reihe weiterer Event-Listener-Interfaces, die alle auf Ereignisse spezieller Art warten (Mausereignisse, Tastatur-Eingaben usw.). Hier ist eine Liste.

Event-Klasse

Interface

Methoden

Quellen

ActionEvent

ActionListener

actionPerformed()

Button
MenuItem
TextField

AdjustmentEvent

AdjustementListener

adjustmentValueChanged()

Scrollbar

ComponentEvent

ComponentListener

componentHidden()
componentMoved()
componentResized()
componentShown()

Component

ContainerEvent

ContainerListener

componentAdded()
componentRemoved()

Container

FocusEvent

FocusListener

focusGained()
focuseLost()

Component

ItemEvent

ItemListener

itemStateChanged()

Checkbox
CheckboxMenuItem
Choice
List

KeyEvent

KeyListener

keyPressed()
keyReleased()
keyTyped()

Component

MouseEvent

MouseListener

mouseClicked()
mouseEntered()
mouseExited()
mousePressed()
mouseReleased()

Component

 

MouseMotionListener

mouseDragged()
mouseMoved()

Component

TextEvent

TextListener

textValueChanged()

TextComponent

WindowEvent

WindowListener

windowActivated()
windowClosed()
windowClosing()
windowDeactivated()
windowDeiconified()
windowIconified()
windowOpened()

Window

Viele Event-Quellen, die hier angegeben sind, sagen uns noch nichts. Man beachte aber, dass Frame von Window abstammt und damit auch als Quelle eines WindowEvent in Frage kommt. Genauso stammen alle Komponenten, die wir bisher besprochen haben (Canvas, Panel etc.), von Component ab. Entsprechendes gilt für die Swing-Klassen mit vorangestelltem J, die meist dieselben Ereignisse erzeugen können.

Adapter-Klassen

Damit eine Klasse nicht durch zu viele Methoden eines Interface aufgebläht wird, gibt es Adapter-Klassen. Diese Klassen implementieren das Interface mit leeren Methoden. Man braucht nur noch die entsprechende Methode zu überschreiben.

Im folgenden Beispiel verwenden wir den WindowAdapter, der das WindowListener-Interface implementiert. Wir überschreiben lediglich die Methode windowClosing().

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;

class Closer extends WindowAdapter
{
    @Override
    public void windowClosing (WindowEvent e)
    {
        System.exit(0);
    }
}

class Test extends JFrame
{
    String Close = "Close";

    public Test ()
    {
        super("Test Frame");
        setSize(300, 300);
        addWindowListener(new Closer());
    }

    public static void main (String args[])
    {
        JFrame F = new Test();
        F.setVisible(true);
    }
}

Noch einfacher wird dies, wenn man Closer als innere Klasse deklariert. Dazu muss man einfach die Deklaration von Closer in die Klasse TestFrame kopieren. Der innere Zusammenhang wird dann klar. Außerdem kann man die Variablen von TestFrame verwenden.

class TestFrame extends Frame implements ActionListener
{   
    ...
    class Closer extends WindowAdapter
    {   
        public void windowClosing (WindowEvent e)
        {   
            dispose(); System.exit(0);
        }
    }
    ...
}

Alles andere bleibt gleich. Man beachte, dass die innere Klasse auf die Variablen der umgebenden Klasse (in diesem Fall auf Close) zugreifen kann.

Die Adapter für die anderen Events heißen entsprechend MouseAdapter, KeyAdapter etc.

Anonyme Klassen

Es geht aber noch prägnanter. Dazu braucht man überhaupt keine Klasse mehr zu deklarieren, sondern verwendet anonyme Klassen. Die Deklaration erfolgt gleich im new-Statement. Dies ist eine sehr kompakte Möglichkeit, Kinderklassen zu deklarieren und gleich eine Instanz anzulegen. Es ist klar, dass man diese Klassendefinition nicht nochmals verwenden kann.

Die Instanz der anyonymen Klasse kann auf die Variablen der Klasse zugreifen, in der sie deklariert wurde, genau wie bei einer lokal definierten innderen Klasse.

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;

class Test extends JFrame
{
    String Close = "Close";

    public Test ()
    {
        super("Test Frame");
        setSize(300, 300);

        addWindowListener(
                new WindowAdapter()
                {
                    @Override
                    public void windowClosing (WindowEvent e)
                    {
                        dispose();
                        System.exit(0);
                    }
                });
    }

    public static void main (String args[])
    {
        JFrame F = new Test();
        F.setVisible(true);
    }
}

Dieses Beispiel zeigt eine anonyme innere Klasse, nämlich ein Kind von WindowAdapter. Die Syntax ist

new VaterKlasse (KonstruktorParameter)
Klassendefinition

Übungsaufgaben

  1. Schreiben Sie eine Komponente TestCanvas, die auf Mausklicks reagiert. Dazu implementieren Sie das Interface MouseListener, das einen Mausklick bearbeitet, und initiieren dort das Neuzeichen mit repaint (nachdem Sie sich noch die Koordinaten des Klicks gemerkt haben). Es soll an der Stelle, an der geklickt wurde, ein Rechteck gezeichnet werden. Alternativ können Sie auch den MouseAdapter verwenden. 
  2. Informieren Sie sich mit der Dokumentation über Keyboard-Events. Schreiben Sie ein Frame, das sich mit der Taste Escape schließen lässt. Tun Sie das mit KeyAdapter.
  3. Tun Sie nun ein Canvas in das Fenster, und überprüfen Sie, wer das Tastaturereignis erhält. Was passiert, wenn man zwei Canvas-Komponenten mit einem GridLayout in das Fenster legt? Wie kann man erreichen, dass die Escape-Taste das Fenster immer noch schließt?
  4. Tun Sie nun noch zusätzlich zum Canvas eine Komponente vom Typ TextField in das Fenster (etwa in den Süden mit einem BorderLayout). Bekommt das Canvas noch den Tastatur-Fokus?
  5. Ändern Sie 5. ab, indem Sie bei TestCanvas auf Mausklicks reagieren. Falls in das TestCanvas geklickt wird, rufen Sie requestFocus() auf. Bekommt das Canvas dann Mausereignisse?

Lösungen.

Aufgaben ohne Lösung

  1. Schreiben Sie ein Fenster, das im Zentrum eine Canvas hat und unten eine Panel mit mehreren Knöpfen. Was passiert, wenn der Platz für die Knöpfe nicht ausreicht?
  2. Verwenden Sie in 1. ein Panel mit einem GridLayout und setzen Sie 5 Knöpfe mit den Zahlen 1 bis 5 hinein. Das Canvas soll immer den Text auf dem zuletzt gedrückten Knopf anzeigen. Registrieren Sie dazu einfach das Canvas als ActionListener für die Knöpfe.
  3. Schreiben Sie eine Klasse Test, die zwei Fenster öffnet. Beide Fenster sollen auf windowClosing reagieren. Das Programm soll aber erst beendet werden, wenn der letzte Frame geschlossen wurde.
  4. Eine schwierigere Aufgabe: Schreiben Sie eine Kinderklasse von Frame, die eine Methode
    void addCloseListener(CloseListener c);
    hat. Ein CloseListener ist ein Interface, dass Sie ebenfalls schreiben müssen, und dass eine Methode
    void frameClosed (Frame f);
    implementiert. Nun kann sich ein CloseListener bei Ihrem Frame registrieren. Rufen Sie frameClosed() aus der Methode windowClosed von WindowListener auf.

Zurück zum Java-Kurs