JavaFX FXML控制器 - 构造函数与初始化方法

mrb*_*ela 69 java javafx

我的Application班级看起来像这样:

public class Test extends Application {

    private static Logger logger = LogManager.getRootLogger();

    @Override
    public void start(Stage primaryStage) throws Exception {

        String resourcePath = "/resources/fxml/MainView.fxml";
        URL location = getClass().getResource(resourcePath);
        FXMLLoader fxmlLoader = new FXMLLoader(location);

        Scene scene = new Scene(fxmlLoader.load(), 500, 500);

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
Run Code Online (Sandbox Code Playgroud)

通过先调用默认构造函数然后调用方法,FXMLLoader创建相应控制器的实例(在FXML文件via中给出fx:controller)initialize:

public class MainViewController {

    public MainViewController() {
        System.out.println("first");
    }

    @FXML
    public void initialize() {
        System.out.println("second");
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是:

first
second
Run Code Online (Sandbox Code Playgroud)

那么,为什么该initialize方法存在?使用构造函数或initialize方法初始化控制器所需的东西有什么区别?

谢谢你的建议!

Nik*_*los 94

简而言之:首先调用构造函数,然后@FXML填充任何带注释的字段,然后initialize()调用.因此,构造函数无权访问@FXML引用.fxml文件中定义的组件的字段,同时initialize()可以访问它们.

引自FXML简介:

[...]控制器可以定义一个initialize()方法,当它的相关文档的内容已被完全加载时,它将在实现控制器上调用一次[...]这允许实现类执行任何必要的帖子 - 处理内容.

  • 要点是构造函数不能使用容器注入的实例变量。这是一个先有鸡还是先有蛋的问题。所以流程是构造->注入->初始化。这是许多语言中 IOC 框架普遍存在的习惯用法。构造函数负责分配内存并确保实例的初始状态有效。初始化器添加对注入后状态进行操作的行为。这比构造函数注入更灵活。 (3认同)
  • 我不明白。他这样做的方式是通过`FXMLLoader`,对吗?所以我没有看到等待 `initialize()` - 方法的好处。加载 FXML 后,以下代码就可以访问 `@FXML` 变量。当然,他是在 start 方法中而不是在构造函数中执行此操作,但是 `initialize()` 会在他的情况下带来任何好处吗? (2认同)
  • 注入状态的示例是对 FXML 加载器创建的子视图和控件的引用。在构造过程中以编程方式配置这些子视图的属性是不可能的 - 由于可能的实例缓存、双向引用、实例化顺序限制等 - 因此您可以使用 initialize() 方法来执行此操作,并保证所有注入的引用是非空的并且已初始化。Cocoa 对 viewDidLoad() 使用类似的方法。 (2认同)

Ita*_*tai 78

在注入initialize所有带@FXML注释的成员之后调用该方法.假设您有一个要用数据填充的表视图:

class MyController { 
    @FXML
    TableView<MyModel> tableView; 

    public MyController() {
        tableView.getItems().addAll(getDataFromSource()); // results in NullPointerException, as tableView is null at this point. 
    }

    @FXML
    public void initialize() {
        tableView.getItems().addAll(getDataFromSource()); // Perfectly Ok here, as FXMLLoader already populated all @FXML annotated members. 
    }
}
Run Code Online (Sandbox Code Playgroud)


gkh*_*aos 5

除上述答案外,可能应该注意,还有一种实现初始化的旧方法。fxml库中有一个名为Initializable的接口。

import javafx.fxml.Initializable;

class MyController implements Initializable {
    @FXML private TableView<MyModel> tableView;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        tableView.getItems().addAll(getDataFromSource());
    }
}
Run Code Online (Sandbox Code Playgroud)

参数:

location - The location used to resolve relative paths for the root object, or null if the location is not known.
resources - The resources used to localize the root object, or null if the root object was not localized. 
Run Code Online (Sandbox Code Playgroud)

以及文档说明为何使用简单方法@FXML public void initialize()有效:

NOTE通过自动将位置和资源属性注入控制器,该接口已被取代。FXMLLoader现在将自动调用控制器定义的任何带有适当注释的no-arg initialize()方法。建议尽可能使用注射方法。

  • @Jan 只有在“公开”的情况下才是正确的。您可以将 `initialize()` 方法(可以是无参数,或者与接口中的参数相同)设置为 `private`,并注释为 `@FXML`,这样可以增强封装性。 (4认同)