JavaFX和FXML:控制器事件处理程序和初始化最佳实践



我遇到了一个设计问题,这与Javafx控制器的事件处理和初始化的顺序有关。

每当选择"对应的选项卡"时,我想更新TABPANE。为了实现这一目标,我使用FXML进行注册事件处理程序,如下所示:

<Tab fx:id="browseCollectionTab" onSelectionChanged="#tabChanged" text="Browse Images">

如果处理代码,我得到了

之类的东西
@FXML 
private void tabChanged() throws IOException{
    if(browseCollectionTab.isSelected())
            updateImageView();
}

updateImageView依次使用依赖注入传递给控制器的数据源加载图像。

选项1:目前,此依赖注入如下:

FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFile));
    Parent root = fxmlLoader.load(); 
    AbstractController ctrl = (AbstractController)fxmlLoader.getController();
    ctrl.setModel(this.model);
    ctrl.setUp();

选项2 我可以使用控制器的初始化方法()方法使用单例将其初始化。这确实打破了依赖注入,这不是我首选的解决方案。

选项3 我可以避免使用FXML并手动实例化所有内容。这使我可以在调用Javafx/FXML之前实例化控制器并执行依赖注入。在线上有许多示例,最终都陷入了复杂的GUI。我想坚持FXMlloader,因为这似乎是一种整洁而舒适的方式。如果这实际上不是最佳实践,请指出。

选项4 我可以在控制器的初始化方法中手动注册事件处理程序(或为此,在执行依赖项注入/设置控制器之后)。首先,这无视将事件处理程序定义为FXML的重点。

那么,选项1和2的问题是什么?在上,在控制器上执行任何初始化之前,TabChanged实际上是调用的,导致无效指针异常。现在,我可以忽略所有事件,直到控制器初始化为止 - 这可能是一个坏主意,因为只会错过一次的事件。另一个选择是在(可能)许多事件处理程序中执行初始化。这似乎也不是一个可行的选择。

我必须缺少明显的东西。我知道这与常见的设计选择/最佳实践有关;但是,我无法将正确的关键字喂给Google。

我期待您的帮助/建议 - 谢谢!

您显示的示例实际上是一个非常不寻常的示例:通常在加载过程完成之前无法调用事件处理程序。选项卡选择是一种异常,因为您确实在响应可能通过编程方式发生的属性的变化,并且当将选项卡添加到空的选项卡窗格中时确实会发生。因此,这是一个不寻常的情况,可以在加载完成之前调用事件处理程序。

简单解决方案

考虑将控制器与FXML文件关联的方式。一个选项是从FXML文件中删除fx:controller属性,然后在代码中设置控制器。这使您有机会首先正确初始化控制器:

FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFile));
AbstractController ctrl = new ConcreteControllerImplementation();
ctrl.setModel(this.model);
ctrl.setUp();
Parent root = fxmlLoader.load(); 

更复杂的方法

另一个更为复杂的选项是使用控制器工厂。这是将控制器类映射到实际控制器实例的函数。在这种情况下,您仍然在FXML文件中具有fx:controller属性(创建FXML文件的标准方法可以视为受益人,因为它为Scene -Builder提供了一个检查方法和@FXML通知字段的机会,等等)。另一个好处是,控制器工厂被传播到<fx:include>包含的任何FXML文件,这也使您可以在使用它们的控制器之前初始化它们。

在下面我假设您的model是类型Model

首先,定义您的控制器具有将Model作为参数的构造函数,即。

public class ConcreteControllerImplementation extends AbstractController {
    private final Model model ;
    public ConcreteControllerImplementation(Model model) {
        this.model = model ;
        // do setup here, not in separate method...
    }
    public void initialize(URL url, ResourceBundle resources) {
        // normal controller setup stuff here
        // any @FXML annotated fields are now initialized
    }
}

要创建可重复使用的控制器工厂,您需要一些反射:

Model model = ... ;
Callback<Class<?>, Object> controllerFactory = type -> {
    try {
        for (Constructor<?> c : type.getConstructors()) {
            if (c.getParameterCount() == 1 && c.getParameterTypes()[0].equals(Model.class)) {
                return c.newInstance(model);
            }
        }
        // no matching constructor: just use default (no-arg) constructor:
        return type.newInstance();
    } catch (Exception exc) {
        // fatal...
        throw new RuntimeException(exc);
    }
};

然后您只做

FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxmlFile));
fxmlLoader.setControllerFactory(controllerFactory);
Parent root = fxmlLoader.load(); 

此技术还允许您使用依赖注入框架。例如。如果您使用春季,则可以做

ApplicationContext context = ... ;
FXMLLoader loader = new FXMLLoader(getClass().getResource(...));
loader.setControllerFactory(context::getBean);

现在,您的控制器实例将由Spring Bean Factory创建和管理,您可以使用Spring依赖注入将模型注入其中。

最新更新