Jetzt anmelden...

Login
Passwort
Registrieren
16.08.21 651 Views Kommentare [0] 0 0

credit: ©Siarhei/ adobe

JAVA Tutorial #52

JavaFX EventHandling

Echte Benutzerinteraktion mit deinem JavaFX-Programm bekommst du nur über interaktive Steuerungselemente. Was bei einem Klick auf einem Button geschehen soll beschreibt der EventHandler. Wie einfach das funktioniert, zeigen wir dir in diesem Tutorial.

Falconbyte unterstüzen

Betrieb und Pflege von Falconbyte brauchen viel Zeit und Geld. Um dir auch weiterhin hochwertigen Content anbieten zu können, kannst du uns sehr gerne mit einem kleinen "Trinkgeld" unterstützen.

Schnelles Code-Beispiel

btn.setOnAction(e -> {
    System.out.println("Anweisung 1");
    System.out.println("Anweisung 2");
    // usw.
});

    Inhaltsverzeichnis

  1. Was sind Events und EventHandler?
  2. Das EventHandler-Interface implementieren
  3. Anonyme innere Klasse
  4. Lambda-Ausdruck
  5. Vollständiger Code

Was sind events und EventHandler?

Mit einem EventHandler kannst du steuern, wie dein Programm reagieren soll, wenn ein bestimmtes User-Ereignis (das event) eintritt.

Ein event tritt auf, wenn der Nutzer mit den GUI-Elementen deines Programms interagiert. Zum Beispiel: Er klickt einen Button an, tippt einen Text ein oder bewegt die Maus. Wenn das passiert, wird ein Event-Objekt "abgefeuert".

Event Handler haben die Aufgabe, ausgelöste events zu erkennen und daraufhin bestimmte Programm-Anweisungen durchzuführen. Damit die Methode beim Klick ausgeführt wird, muss sie zuvor aber noch mit dem Button "verknüpft" bzw. registriert werden.

Den ganzen Vorgang kannst du in dieser Infografik erkennen:

Java JavaFX Stage SceneGraph

Am wichtisten ist die Verbindung zwischen Button und Methode. Das abgefeuerte Event-Objekt wird für das Programm oftmals garnicht direkt benötigt.

Das EventHandler-Interface implementieren

Um einen EventHandler zu erzeugen, implementieren wir zuerst das generische Interface EventHandler:

public class Stufe1 extends Application implements EventHandler<ActionEvent> 

Da es viele unterschiedliche Event-Typen gibt (Klick auf Button, Eingabe von Tasten, Mausbewegung, Events für Touchscreens,...), tragen wir einen passenden Typ-Parameter in den spitzen Klammern ein. In diesem Beispiel wählen wir den Typ ActionEvent, der eine Verarbeitung von Events ermöglicht, die durch einen Button-Klick ausgelöst werden.

Das EventHandler-Interface besitzt nur eine einzige Methode namens handle(), die wir dann auch implementieren müssen:

@Override
public void handle(ActionEvent actionEvent) {
         System.out.println("Thank you!");
}              

Diese Methode kümmert sich um die Verarbeitung des Events. Der Methoden-Parameter fängt dabei das durch den Button-Klick ausgelöste Event auf.

Bei einem Klick auf den Button wird der Text "Thank you!" auf der Konsole ausgegeben; das Event-Objekt selbst wird in unserem Beispiel übrigens garnicht weiter beachtet.

Damit die Methode bei einem Button-Klick ausgeführt wird, müssen wir den EventHandler noch auf der gewünschten Button-Referenz "registrieren", indem wir die Methode setOnAction(this) ausführen. Das Schlüsselwort this in der Klammer meint, dass die handle()-Methode in dieser selben Klasse ausgeführt werden soll.

Der ganze Klassencode sieht schließlich so aus:

package sample;

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

public class Stufe1 extends Application implements EventHandler<ActionEvent> {

    Button btn; // Unser Button!!

    @Override
    public void start(Stage primaryStage){

        HBox layout = new HBox();
        btn = new Button("Klick mich");
        btn.setOnAction(this);

        layout.getChildren().add(btn);
        primaryStage.setScene(new Scene(layout, 320, 240));
        primaryStage.show();
    }

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

    @Override
    public void handle(ActionEvent actionEvent) {
             System.out.println("Thank you!");
    }
}
                

Problem: Mehrere Buttons

Da es pro Klasse nur eine handle()-Methode geben kann, ist es aktuell nicht möglich, mehrere Buttons mit unterschiedlichen Funktionen zu programmieren.

Damit das gelingt, können wir mit der Methode getSource(), die wir auf der Event-Referenz ausführen, den Ursprung des Klicks herausfinden und dann entsprechend unterschiedliche Aktionen durchführen.

Angenommen, wir haben einen zweiten Button mit der Variablen btn2, dann sieht die entsprechende Implementierung der handle()-Methode wie folgt aus:

@Override
public void handle(ActionEvent actionEvent) {
    // Wenn auf den ersten Button geklickt wird:
    if (actionEvent.getSource() == btn) {
        System.out.println("Thank you!");
    }
    // Wenn auf den zweiten Button geklickt wird:
    else if (actionEvent.getSource() == btn2) {
        System.out.println("You're welcome!");
    }
} 

So wollen wir das nicht

Diese Umsetzung des Event-Handlings funktioniert, hat aber ein paar Nachteile:

  • Der Button und das dazugehörende Event-Handling sind im Code zu weit voneinander getrennt. Das kann in größeren Projekten sehr schnell unübersichtlich werden.
  • Innerhalb der handle()-Methode muss bei mehreren GUI-Elementen immer wieder die getSource()-Methode inklusive if-else-Kontrollstruktur aufgerufen werden. Das bedeutet, dass das Programm für nur ein ausgelöstes Event im schlimsmtne Fall alle Eventualitäten durchgehen muss. Arme Performance!

Wir können die Konzepte des Event-Handlings technisch deutlich schlanker lösen. Sehe wir uns das im Folgenden einmal an.

Anonyme innere Klasse

Für ein effektivieres Event-Handling bedienen wir uns der Technik der anonymen inneren Klasse. Eine anonyme innere Klasse ist eine Klasse, die innerhalb einer anderen Klasse definiert ist und deren mit new instanziiertes Objekt keine Referenz besitzt.

Bevor wir unseren Code entsprechend anpassen, hier nochmal zur Erinnerung: btn.setOnAction(this); bedeutet, dass bei einem Klick auf den Button der EventHandler ausgeführt wird, der in dieser Klasse (this) implementiert ist.

Jetzt kommt die Vereinfachung: Wir streichen die Implementierung des EventHandler-Interfaces und entfernen auch die handle()-Methode aus der übergeordneten Klasse. Stattdessen löschen wir innerhalb der Klammern von btn.setOnAction(); das Schlüsselwort this und bauen dort direkt ein neue EventHandler-Klasse als anonyme innere Klasse ein. Darin überschreiben wir in einem "Atemzug" die notwendige handle()-Methode. Das Ergebnis sieht dann so aus:

btn.setOnAction(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent actionEvent) {
        System.out.println("Smart and cool!");
    }
});                

Dieser Code ist kompakter und vor allem übersichtlicher, zumal der Auslöser für ein Event und das EventHandling im Code direkt beieinander liegen. Perfekt ist er aber noch nicht 😉

Lambda-Ausdruck

Die perfekte Lösung für das EventHandling besteht im Einsatz von Lambda-Ausdrücken. Die Lambda-Syntax wurde in Java 8 eingeführt und erlaubt uns, den Code auf ein Minimum zu komprimieren.

Die Lambda-Syntax besteht aus drei Teilen: Einem Parameter, einem Arrow-Operator und dem Ausdruck:

Parameter -> Ausdruck 

Setzen wir nun die für unsere Zwecke angepasste Lambda-Sytax in die runden Methoden-Klammern von btn.setOnAction(); ein:

btn.setOnAction(e -> System.out.println("I'm so hot!"));                

Wow! 😎 Die ganze Funktionalität des EventHandlings ist nun auf eine einzige Zeile vereinfacht worden.

Wie ist das möglich?

Da das EventHandler-Interface nur eine einzige Methode hat (nämlich handle()), kann Java durch den Einsatz der Lambda-Syntax den EventHandler aus dem Kontext heraus erkennen.

e ist der Parameter und repräsentiert das Event und rechts neben dem Arrow-Operator folgt der Körper der Methode.

Mehrere Anweisungen

Mehrere Anweisungen innerhalb eines Lambda-Ausdrucks können ganz einfach innerhalb von geschwungene Klammern notiert werden:

btn.setOnAction(e -> {
    System.out.println("Anweisung 1");
    System.out.println("Anweisung 2");
    // usw.
});                

Vollständiger Code

Den vollständigen Code hast du copy-paste-fertig hier ☺️

package sample;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class FinaleStufe extends Application {

    @Override
    public void start(Stage primaryStage){

        HBox layout = new HBox();

        Button btn = new Button("Klick mich");
        btn.setOnAction(e -> {
            System.out.println("I'm so hot!");
            System.out.println("Lets do some Java");
        });

        layout.getChildren().addAll(btn);
        primaryStage.setScene(new Scene(layout, 320, 240));
        primaryStage.show();
    }

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

Falconbyte unterstützen

Kommentar schreiben

Alle Kommentare

Es gibt bislang noch keine Kommentare zu diesem Thema.

switch/ case Anweisung

Benötigen wir eine Unterscheidung nach vielen Fällen empfehlen sich switch-case-Statements.

Objektinteraktion

Wie interagieren Objekte in einem Programm miteinander?

Die Klasse Object

Erfahren Sie alles über die Mutter aller Klassen

FALCONBYTE.NET

Handmade with 🖤️

© 2018-2021 Stefan E. Heller

Impressum | Datenschutz

Falconbyte GitHub facebook programmieren lernen twitter programmieren lernen discord programmieren lernen