3.03 Event Handling

Beispiele zur Einführung

Der wohl spannendste Aspekt beim Erstellen einer Anwendung mit GUI ist das Event Handling. Erst dadurch wir die Anwendung tatsächlich zu einer Anwendung und stellt nicht nur ein Fenster dar, in dem nichts interessantes passiert.

Die Grundidee des Event-Handling in JavaFX ist wie folgt. Gewisse Aktionen wie das Klicken eines Buttons lösen ein Ereignis („Event“) aus. Genauer gesagt, wird durch sie eine Instanz der Klasse Event oder einer der Unterklassen von Event erzeugt. Diese Instanz wird dann an einen Event-Handler gegeben, der darauf reagiert. Damit das Ereignis an einen Event-Handler gegeben werden kann, muss die auslösende Fensterkomponente bei dem entsprechenden Event-Handler zuvor registriert worden sein.

Es gibt verschiedene Wege, einen Event-Handler zu erstellen und eine Fensterkomponente beim ihm zu registrieren. Wir schauen uns als erstes einen Weg an, der zwar seltener verwendet wird, dafür aber etwas einfacher nachzuvollziehen ist. Später kommen wir dann zur üblichen Vorgehensweise.

ActionEvents

Eine Anwendung mit zwei Buttons
Unsere Beispielanwendung

Wir erstellen dazu eine kleine Anwendung mit zwei Buttons. Klicken wir auf den oberen, so erscheint in der Konsole zur Kontrolle die Ausgabe Erster Event Handler reagiert!. Klicken wir auf den unteren, erhalten wir die zwei Meldungen Erster Event Handler reagiert! und Zweiter Event Handler reagiert! in der Konsole.
Bei fertigen Anwendungen sieht ein Benutzer Meldungen in der Konsole natürlich nicht mehr. Zur Kontrolle sind sie bei der Entwicklung einer Anwendung aber durchaus nützlich.

Sehen wir uns einen möglichen Quelltext an:

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;

public class BeispielFX extends Application {

  public static void main(String[] args) {
    launch(args);				
  }

  public void start(Stage meineStage) throws Exception {

    // Erstellen der Komponenten und des Layouts
    meineStage.setTitle("Beispiel Events");

    Button meinErsterButton = new Button();
    meinErsterButton.setText("Klick mich!");
    
    Button meinZweiterButton = new Button();
    meinZweiterButton.setText("Nein, klick lieber mich!");

    BorderPane root = new BorderPane();
    root.setTop(meinErsterButton);
    root.setBottom(meinZweiterButton);
    
    Scene meineScene = new Scene(root, 200, 60);
    
    meineStage.setScene(meineScene);
    meineStage.show();	
    
    
    // Ereignissteuerung
    
    // EventHandler werden erstellt.
    EventHandler<ActionEvent> meinEventHandler = new EventHandler<ActionEvent>() {
      
      public void handle(ActionEvent meinEvent) {
        System.out.println("Erster Event Handler reagiert!");						
      }
      
    };
    
    EventHandler<ActionEvent> meinZweiterEventHandler = new EventHandler<ActionEvent>() {
      
      public void handle(ActionEvent meinEvent) {
        System.out.println("Zweiter Event Handler reagiert!");					
      }
      
    };
    
    // Buttons werden bei den Event Handlern angemeldet.
    meinErsterButton.addEventHandler(ActionEvent.ACTION, meinEventHandler);
    
    meinZweiterButton.addEventHandler(ActionEvent.ACTION, meinEventHandler);
    meinZweiterButton.addEventHandler(ActionEvent.ACTION, meinZweiterEventHandler);
    
  }
}

Zunächst werden wie üblich die Fensterkomponenten erstellt. Dies kennen wir schon aus der Einführung. Daher konzentrieren wir uns auf den unteren Teil der Ereignissteuerung.

Dort wird als erstes eine Instanz meinEventHandler der Klasse EventHandler erstellt. Die Angabe <ActionEvent> legt fest, auf welche Art von Event er reagieren soll. Ein Action-Event ist zum Beispiel das Klicken eines Buttons. Andere Typen von Events sehen wir uns weiter unten an.
Beim Erstellen eines Events-Handlers muss die Methode handle implementiert werden. In dieser wird  beschrieben, wie der Event-Handler auf ein Ereignis reagiert. Sie bekommt automatisch als Parameter den auslösenden Event übergeben – wozu das gut sein kann, werden wir später noch sehen. Wie schon angekündigt, wird in dieser Methode eine Ausgabe in der Konsole veranlasst.

Danach wird noch eine zweite Instanz meinZweiterEventHandler von EventHandler erstellt. Sie unterscheidet sich von der ersten nur durch die Ausgabe.

Anschließend wird meinErsterButton bei meinEventHandler registriert. Dies geschieht mit der Methode addEventHandler. Diese verlangt zwei Parameter:

  • Der erste Parameter gibt den Typ des Events an, der an den Event-Handler weitergegeben werden soll. Diese Angabe erfolgt in Form einer Konstanten wie im Beispiel ActionEvent.ACTION. Wenn wir noch mehr Typen von Events kennen, wird diese Angabe für uns verständlicher.
  • Der zweite Parameter gibt dann den Event-Handler an. In diesem Fall ist es der eben von uns erstellte meinEventHandler.

Beim zweiten Button geschieht zunächst dasselbe. Danach wird er zusätzlich auch bei meinZweiterEventHandler registriert.

Führen wir die Anwendung aus, geschieht folgendes:

  • Bei einem Klick von meinErsterButton wird ein Action-Event erzeugt. Dieser wird an meinEventHandler gereicht. Dieser schreibt kann in der Konsole Erster Event Handler reagiert!.
  • Bei einem Klick von meinZweiterButton wird ebenfalls ein Action-Event erzeugt. Dieser wird zuerst an meinEventHandler gereicht, der wieder Erster Event Handler reagiert! in der Konsole ausgibt. Der Event wird aber auch an meinZweiterEventHandler gereicht, der dann Zweiter Event Handler reagiert! in der Konsole ausgibt.

Wir haben also gesehen, dass einerseits eine Fensterkomponente bei mehreren Event-Handlern registriert sein kann und andererseits auch mehrere Fensterkomponenten bei demselben Event-Handler registriert sein können.

Anonyme EventHandler – Das Standardvorgehen

Wie eingangs schon erwähnt, werden Event-Handler meist ein wenig anders implementiert. Sehen wir uns aber zuerst noch einmal ein Beispiel für die uns nun gewohnte Vorgehensweise an:

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;

public class BeispielFXEventHandler extends Application {

  public static void main(String[] args) {
    launch(args);				
  }

  public void start(Stage myStage) throws Exception {

    myStage.setTitle("BeispielFX");

    Button meinButton = new Button();
    meinButton.setText("Klick mich!");

    BorderPane root = new BorderPane();
    root.setCenter(meinButton);
    Scene scene = new Scene(root, 300, 250);
    myStage.setScene(scene);
    myStage.show();	

    // EventHandler wird zuerst erstellt.
    EventHandler<ActionEvent> meinEventHandler = new EventHandler<ActionEvent>() {
      public void handle(ActionEvent meinEvent) {
        System.out.println("Button geklickt!");						
      }			
    };

    // Dann wird der Button angemeldet.
    meinButton.addEventHandler(ActionEvent.ACTION, meinEventHandler);

  }

}

Hier haben wir einen Event-Handler, bei dem genau ein Button angemeldet wird.

Wenn bei einem Event-Handler so wie hier nur eine Fensterkomponente angemeldet wird, können wir uns die eigene Bezeichnung des Event-Handler (wie hier meinEventHandler) sparen. Der Event-Handler muss dann nicht zuerst erstellt werden und anschließend mit der Komponente verbunden werden. Stattdessen können wir beides in einem Schritt erledigen. Das sieht dann so aus:

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;

public class BeispielFXAnonym extends Application {

  public static void main(String[] args) {
    launch(args);				
  }

  public void start(Stage myStage) throws Exception {

    myStage.setTitle("BeispielFX");

    Button meinButton = new Button();
    meinButton.setText("Klick mich!");

    BorderPane root = new BorderPane();
    root.setCenter(meinButton);
    Scene scene = new Scene(root, 300, 250);
    myStage.setScene(scene);
    myStage.show();	

    // Erstellen des EventHandlers und Anmelden des Buttons in einem Schritt
    meinButton.addEventHandler(ActionEvent.ACTION, new EventHandler<ActionEvent>() {
      public void handle(ActionEvent myEvent) {
        System.out.println("Button geklickt!");						
      }			
    });

  }

}

An diese Schreibweise muss man sich vielleicht zunächst etwas gewöhnen. Du wirst aber in den meisten Beispielen genau diese Schreibweise vorfinden.

Man spricht bei dieser Art der Implementierung von einer anonymen Klasse EventHandler.

KeyEvents und Attribute von Events

Wie schon angedeutet, gibt es neben den Action-Events noch weitere Typen von Events. Als ersten weiteren Typ sehen wir uns hier Key-Events an. Ein Key-Event wird durch Drücken einer Taste auf der Tatstatur erzeugt.

In dieser Abwandlung unseres Beispiels von oben wird beim Drücken einer Taste eine Meldung in der Konsole angegeben und das Zeichen der Taste wird ausgegeben.

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;

public class BeispielFX extends Application {

  public static void main(String[] args) {
    launch(args);				
  }

  public void start(Stage meineStage) throws Exception {

    // Erstellen der Komponenten und des Layouts
    meineStage.setTitle("Beispiel Events");

    // Button ist jetzt als final deklariert.
    final Button meinErsterButton = new Button();
    meinErsterButton.setText("Klick mich!");
    
    Button meinZweiterButton = new Button();
    meinZweiterButton.setText("Nein, klick lieber mich!");

    BorderPane root = new BorderPane();
    root.setTop(meinErsterButton);
    root.setBottom(meinZweiterButton);
    
    Scene meineScene = new Scene(root, 200, 60);
    
    meineStage.setScene(meineScene);
    meineStage.show();	
    
    
    // Ereignissteuerung
    
    // EventHandler werden erstellt.
    EventHandler<ActionEvent> meinEventHandler = new EventHandler<ActionEvent>() {
      
      public void handle(ActionEvent meinEvent) {
        System.out.print("Event Handler reagiert! ");	
        if(meinEvent.getSource() == meinErsterButton){
          System.out.println("Er reagierte auf den 1. Button.");
        }else{
          System.out.println("Er reagierte auf den 2. Button.");
        }
      }
      
    };
    
    // Ein EventHandler fuer KeyEvents
    EventHandler<KeyEvent> meinZweiterEventHandler = new EventHandler<KeyEvent>() {

      public void handle(KeyEvent meinEvent) {
        System.out.print("Dritter Event Handler reagiert! ");	
        System.out.println(meinEvent.getCharacter());
      }
    };
    
    // Buttons werden beim ersten Event Handler angemeldet.
    meinErsterButton.addEventHandler(ActionEvent.ACTION, meinEventHandler);
    
    meinZweiterButton.addEventHandler(ActionEvent.ACTION, meinEventHandler);
    
    // Die gesamte Stage wird registriert.
    meineStage.addEventHandler(KeyEvent.KEY_TYPED, meinZweiterEventHandler);
    
  }
}

Wie wir sehen, wurde der zweite Event-Handler angepasst. Er reagiert nun auf Key- statt auf Action-Events.
Außerdem sehen wir hier erstmals, warum es sinnvoll sein kann, den Event als Parameter an die handle-Methode zu übergeben. Wir können so verschiedene Eigenschaften des Events abfragen. In diesem Fall wird mit der Methode getCharacter() das Symbol der gedrückten Taste abgefragt.
Bei diesem Event-Handler wurde übrigens die gesamte Stage registriert.

Auch die handle-Methode des ersten Event-Handlers fragt nun eine Eigenschaft des Events ab. Sie prüft in einer if-Abfrage, ob der Event vom ersten Button erzeugt wurde. Beachte, dass dazu der erste Button als final deklariert werden muss, da man ihn sonst in dieser Methode nicht verwenden darf.

Vielleicht ist Dir aufgefallen, dass beim Anmelden der Stage beim Event-Handler der erste Parameter KeyEvent.KEY_TYPED lautet. Mit dieser Angabe wird der Typ des Key-Events noch genauer festgelegt. Diese Angabe sorgt dafür, dass beim Tippen einer Taste ein Event an den Event-Handler geschickt wird. Alternativ könnte man auch zum Beispiel KeyEvent.KEY_RELEASED angeben. Dann würde der Event erst beim Loslassen der Taste erzeugt. Wenn dies testest, solltest Du am besten eine Taste lange gedrückt halten und dann loslassen. Dann erkennst Du den Unterschied.

Typen von Events

Wir haben bereits Action-Events und zwei Arten von Key-Events gesehen. Nun ist es an der Zeit, dass wir uns die weiteren Standardtypen von Events ansehen. Wir werden allerdings nicht zu allen Beispiele betrachten – das würde den Rahmen sprengen.

Klasse
& Ereignis
Typen
ActionEvent
Z.B. Klicken eines Buttons.
ActionEvent.ACTION
ContextMenuEvent

Kontextmenü wird geöffnet.

ContextMenuEvent.CONTEXT_MENU_REQUEST
DragEvent

Drag & Drop wird verwendet.

DragEvent.DRAG_DONE
DragEvent.DRAG_DROPPED
DragEvent.DRAG_ENTERED
DragEvent.DRAG_ENTERED_TARGET
DragEvent.DRAG_EXITED
DragEvent.DRAG_EXITED_TARGET
DragEvent.DRAG_OVER
InputMethodEvent

Text einer Komponente wird verändert.

InputMethodEvent.INPUT_METHOD_TEXT_CHANGED
KeyEvent

Tatstatur wird betätigt.

KeyEvent.CHAR_UNDEFINED
KeyEvent.KEY_PRESSED
KeyEvent.KEY_RELEASED
KeyEvent.KEY_TYPED
MouseEvent

Maus wird betätigt.

MouseEvent.DRAG_DETECTED
MouseEvent.MOUSE_CLICKED
MouseEvent.MOUSE_DRAGGED
MouseEvent.MOUSE_ENTERED
MouseEvent.MOUSE_ENTERED_TARGET
MouseEvent.MOUSE_EXITED
MouseEvent.MOUSE_EXITED_TARGET
MouseEvent.MOUSE_MOVED
MouseEvent.MOUSE_PRESSED
MouseEvent.MOUSE_RELEASED
MouseDragEvent

Drag & Drop mit Maus.

MouseDragEvent.MOUSE_DRAG_ENTERED
MouseDragEvent.MOUSE_DRAG_ENTERED_TARGET
MouseDragEvent.MOUSE_DRAG_EXITED
MouseDragEvent.MOUSE_DRAG_EXITED_TARGET
MouseDragEvent.MOUSE_DRAG_OVER
MouseDragEvent.MOUSE_DRAG_RELEASED
WindowEvent

Fenster wird geöffnet etc.

WindowEvent.WINDOW_CLOSE_REQUEST
WindowEvent.WINDOW_HIDDEN
WindowEvent.WINDOW_HIDING
WindowEvent.WINDOW_SHOWING
WindowEvent.WINDOW_SHOWN

Beispiel MouseEvent

Sehen wir uns zur Illustration ein Beispiel an, in dem Mouse-Events verarbeitet werden.

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;

public class BeispielFXMaus extends Application {

  public static void main(String[] args) {
    launch(args);				
  }

  public void start(Stage myStage) throws Exception {

    myStage.setTitle("Beispiel FX");

    final ToggleButton meinToggleButton = new ToggleButton("Koordinaten verfolgen");		
    final Label xKoord = new Label();
    final Label yKoord = new Label();
    final Label mausIstWeg = new Label();

    final BorderPane root = new BorderPane();
    root.setBottom(meinToggleButton);
    root.setLeft(xKoord);
    root.setRight(yKoord);
    root.setCenter(mausIstWeg);

    final Scene myScene = new Scene(root, 300, 250);
    myStage.setScene(myScene);
    myStage.show();	

    meinToggleButton.addEventHandler(ActionEvent.ACTION, new EventHandler<ActionEvent>() {
      public void handle(ActionEvent meinEvent) {
      }
    });

    root.addEventHandler(MouseEvent.MOUSE_MOVED, new EventHandler<MouseEvent>() {
      public void handle(MouseEvent myEvent) {
        if (meinToggleButton.isSelected()){
          xKoord.setText(Double.toString(myEvent.getSceneX()));
          yKoord.setText(Double.toString(myEvent.getSceneY()));
        }
      }
    });
    
    root.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
      public void handle(MouseEvent myEvent) {
        mausIstWeg.setText("Nun ist sie weg!");
      }
    });	
    
    root.addEventHandler(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() {
      public void handle(MouseEvent myEvent) {
        mausIstWeg.setText("Da ist sie wieder!");
      }
    });


  }

}
Ein Scrennshot der beschriebenen Anwendung.
So sieht unser Beispiel aus.

Sobald die Maus innerhalb des Fensters (genauer: innerhalb unseres BorderPane-Layouts) bewegt wird, findet ein Mouse-Event des Typs MOUSE_MOVED statt. Der erste Event-Handler sorgt dann dafür, dass die Koordiaten des Mauszeigers in den beiden Labels angezeigt werden, sofern der Toggle-Button aktiviert ist.

Um an die Koordinaten zu kommen wird der erzeugte Mouse-Event verwendet. Über die Methoden getSceneX() und getSceneY() kann man erfahren, an welcher Stelle sich die Maus beim Auslösen des Event befunden hat.

Verlässt bzw. betritt der Mauszeiger das Fenster, finden Mouse-Event der Typen MOUSE_EXITED bzw. MOUSE_ENTERED statt. Dies führt zu einem entsprechendem Text im Label in der Mitte des Fensters.