Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use existing method as JavaFX event handler

In virtually all examples I could find, JavaFX event handlers are created as anonymous inner classes, like so:

button.setOnMouseClicked(new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent event) {
        if (event.getClickCount()>1) {
            System.out.println("double clicked!");    
        }
    }
});

But, I really hate anonymous inner classes (UGLY!!!), and I also don't want to create separate classes for each and every event handler. I would like to use existing methods as event handlers, as FXMLLoader does. My first idea was to use reflection and generics, and this is what I came up with:

public final static <E extends Event> EventHandler<E> createEventHandler(
        final Class<E> eventClass, final Object handlerInstance, final String handlerMethod) {
    try {
        final Method method = handlerInstance.getClass().getMethod(handlerMethod, eventClass);
        return new EventHandler<E>() {
            @Override
            public void handle(E event) {
                try {
                    method.invoke(handlerInstance, event);
                } catch (IllegalAccessException | IllegalArgumentException
                        | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        };
    } catch (NoSuchMethodException | SecurityException e) {
        return null;
    }
}

You pass a required Event class, handler instance and method name which will handle the event, and required EventHandler is returned. It works, but it doesn't look very elegant. Does anyone have a better idea?

like image 892
Nikša Baldun Avatar asked Jan 12 '23 17:01

Nikša Baldun


1 Answers

Suggested Approach

Use Tom's idea regarding Java 8 method references:

public void countClick(MouseEvent event) {
    nClickProperty.set(nClickProperty.get() + 1);
}
. . . 
label.setOnMouseClicked(this::countClick);

Executable Sample

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;

public class ClickCounter extends Application {    

    private final IntegerProperty nClickProperty = new SimpleIntegerProperty(0);

    public void countClick(MouseEvent event) {
        nClickProperty.set(nClickProperty.get() + 1);
    }

    @Override
    public void start(Stage stage) throws Exception {
        Label label = new Label();
        label.setPrefSize(100, 50);
        label.setAlignment(Pos.CENTER);

        label.textProperty().bind(
                Bindings.concat("Num clicks: ", nClickProperty.asString())
        );

        label.setOnMouseClicked(this::countClick);

        stage.setScene(new Scene(label));
        stage.show();
    }

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

Futher discussion based upon points in the question and comment sections

I really hate anonymous inner classes (UGLY!!!)

Use Java 8 lambdas instead, they are not quite as ugly:

button.setOnMouseClicked(event -> {
    if (event.getClickCount() > 1) {
        System.out.println("double clicked!");    
    }
});

I would like to use existing methods as event handlers. as FXMLLoader does.

For a general purpose mechanism you could take a look at FXMLLoader source code and extract just the event handling mechanism out to a generic library.

Also, if your UI is defined in FXML, then you can just use the existing functionality in the FXMLLoader to get the behaviour you want. (I'm guessing you are looking for a solution which does not involve FXML though).

Does anyone have a better idea?

Keeping References

Assign the event handler to a reference. This approach is good in terms of usefulness in certain cases, not necessarily beauty.

private final EventHandler<MouseEvent> mouseHandler = event -> {
    if (event.getClickCount() > 1) {
        System.out.println("double clicked!");
    }
};
. . .
button.setOnMouseClicked(mouseHandler);

One advantage of keeping a local reference to a handler is that it provides you a reference to later invoke node.removeEventHandler for a handler that was previously registered using node.addEventHandler.

Using alternate languages

If you are open to alternative languages, then implementing dynamic method calling is a little bit cleaner in a dynamic language. You can see how JRubyFX deals with the issue of event handler assignment from FXML documents (I don't think it relies on FXMLLoader but believe it has a pure Ruby replacement implementation of it's own). Of course, then you are using a dynamic language which may or may not desirable in your case.

like image 150
jewelsea Avatar answered Jan 15 '23 09:01

jewelsea