使用现有方法作为JavaFX事件处理程序

时间:2013-12-09 17:48:39

标签: reflection event-handling javafx-2

在我发现的几乎所有示例中,JavaFX事件处理程序都是作为匿名内部类创建的,  像这样:

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

但是,我真的很讨厌匿名内部类(UGLY !!!),而且我也不想为每个事件处理程序创建单独的类。我想将现有方法用作事件处理程序,就像FXMLLoader一样。我的第一个想法是使用反射和泛型,这就是我提出的:

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;
    }
}

传递将处理事件的必需Event类,处理程序实例和方法名称,并返回所需的EventHandler。它有效,但看起来不是很优雅。有没有人有更好的主意?

1 个答案:

答案 0 :(得分:8)

建议的方法

使用Tom关于Java 8方法引用的想法:

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

可执行样本

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); }    
}

根据问题和评论部分中的要点进行进一步的讨论

  

我真的很讨厌匿名的内部课程(UGLY !!!)

使用Java 8 lambdas instead,它们并不那么难看:

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

我想将现有方法用作事件处理程序。正如FXMLLoader那样。

对于通用机制,您可以查看FXMLLoader源代码并仅将事件处理机制提取到通用库。

此外,如果您的UI是在FXML中定义的,那么您只需使用FXMLLoader中的现有功能即可获得所需的行为。 (我猜你正在寻找一种不涉及FXML的解决方案。)

  

有没有人有更好的主意?

保留参考资料

将事件处理程序分配给引用。在某些情况下,这种方法在有用性方面是好的,不一定是美。

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

保持对处理程序的本地引用的一个优点是,它为您提供了以后为以前使用node.removeEventHandler注册的处理程序调用{​​{3}}的引用。

使用替代语言

如果您对其他语言持开放态度,那么在动态语言中实现动态方法调用会更加清晰。你可以看到node.addEventHandler如何处理来自FXML文档的事件处理程序赋值问题(我认为它不依赖于FXMLLoader,但相信它有自己的纯Ruby替换实现)。当然,那么你使用的动态语言在你的情况下可能是可取的,也可能是不合适的。