首页 文章

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

提问于
浏览
1

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

每当选择相应的选项卡时,我想更新TabPane . 为此,我使用FXML注册事件处理程序,如下所示:

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

在事件处理代码中,我得到了类似的东西

@FXML 
private void tabChanged() throws IOException{
    if(browseCollectionTab.isSelected())
            updateImageView();
}

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

Option 1: 此依赖注入目前实现如下:

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

    AbstractController ctrl = (AbstractController)fxmlLoader.getController();
    ctrl.setModel(this.model);
    ctrl.setUp();

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

Option 3 我可以避免使用FXML并手动实例化所有内容 . 这允许我在调用JavaFX / FXML之前实例化控制器并执行依赖注入 . 网上有很多例子,对于复杂的GUI来说都是一团糟 . 我想坚持FXMLLoader因为这看起来像一个整洁舒适的方式 . 如果这实际上不是最佳做法,请指出 .

Option 4 我可以在控制器的initialize()方法中手动注册事件处理程序(或者就此而言,在执行依赖注入/从其他地方设置控制器之后) . 这无疑首先在FXML中定义事件处理程序 .

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

我必须遗漏一些明显的东西 . 我知道这与常见的设计选择/最佳实践有关;但是,我无法向Google提供正确的关键字 .

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

1 回答

  • 1

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

    简单的解决方案

    考虑更改将控制器与FXML文件关联的方式 . 一个选项是从FXML文件中 remove the fx:controller attribute ,并在代码中设置控制器 . 这使您有机会首先正确初始化控制器:

    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文件的标准方式可以被认为是一个好处,因为它为SceneBuilder提供了检查方法和 @FXML 注释字段等的机会) . 另一个好处是控制器工厂传播到 <fx:include> 附带的任何FXML文件,这允许您在使用它们之前初始化它们的控制器 .

    在下面我假设您的 modelModel 类型:

    首先,将控制器定义为具有将 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();
    

    此技术还允许您使用依赖注入框架 . 例如 . 如果你使用Spring,你可以这样做

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

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

相关问题