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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With