JavaFX FXML 包含 fxml 导致 NullPointerException
JavaFX FXML include fxml causes NullPointerException
我想将一个按钮提取到一个新的 fxml 文件中,并用它更改主标签。无需提取即可完美运行。
main.fxml:
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<Button onAction="#changeLabel" text="sayHello" />
</VBox>
主控制器:
public class MainController {
@FXML
private Label label;
@FXML
private void changeLabel() {
label.setText("Changed");
}
}
通过提取,我在 MainController.changeLabel() 中得到 NullPointerException
main.fxml 包含:
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<fx:include source="button.fxml"/>
</VBox>
button.fxml:
<AnchorPane xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Button onAction="#changeLabel" text="sayHello" />
</AnchorPane>
什么会导致此 NPE?
您应该(几乎?)始终为不同的 FXML 文件使用不同的 class 控制器。 (我能想到的唯一例外是,如果您想定义不同的 FXML 文件来表示相同控件的不同布局。)
一种方法是将包含的 FXML 的控制器(“嵌套控制器”)注入主控制器。 (参见 documentation。)
public class MainController {
@FXML
private Label label;
@FXML
private ButtonController buttonController ;
@FXML
private void initialize() {
buttonController.setOnButtonPressed(this::changeLabel);
}
private void changeLabel() {
label.setText("Changed");
}
}
public class ButtonController {
private Runnable onButtonPressed ;
public void setOnButtonPressed(Runnable onButtonPressed) {
this.onButtonPressed = onButtonPressed ;
}
public Runnable getOnButtonPressed() {
return onButtonPressed ;
}
@FXML
private void changeLabel() {
if (onButtonPressed != null) {
onButtonPressed.run();
}
}
}
然后 FXML 文件看起来像
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<fx:include fx:id="button" source="button.fxml"/>
</VBox>
和
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.ButtonController">
<Label fx:id="label" text="default"/>
<fx:include source="button.fxml"/>
</VBox>
一般来说,控制器之间相互引用是个坏主意,因为它会破坏封装并增加不必要的依赖关系。更好的方法是使用 MVC 设计。
public class Model {
private final StringProperty text = new SimpleStringProperty() ;
public StringProperty textProperty() {
return text ;
}
public final String getText() {
return textProperty().get();
}
public final void setText(String text) {
textProperty().set(text);
}
}
现在你可以做
public class MainController {
@FXML
private Label label;
private final Model model ;
public MainController(Model model) {
this.model = model ;
}
@FXML
private void initialize() {
label.textProperty().bind(model.textProperty());
}
}
和
public class ButtonController {
private final Model model ;
public ButtonController(Model model) {
this.model = model ;
}
@FXML
private void changeLabel() {
model.setText("Changed");
}
}
FXML文件如上,加载FXML时需要指定一个controller factory(以便通过将模型实例传递给constructors来实例化controller):
final Model model = new Model();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/main.fxml");
loader.setControllerFactory(type -> {
if (type.equals(MainController.class)) return new MainController(model);
if (type.equals(ButtonController.class)) return new ButtonController(model);
throw new IllegalArgumentException("Unexpected controller type: "+type);
});
Parent root = loader.load();
// ...
我想将一个按钮提取到一个新的 fxml 文件中,并用它更改主标签。无需提取即可完美运行。
main.fxml:
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<Button onAction="#changeLabel" text="sayHello" />
</VBox>
主控制器:
public class MainController {
@FXML
private Label label;
@FXML
private void changeLabel() {
label.setText("Changed");
}
}
通过提取,我在 MainController.changeLabel() 中得到 NullPointerException
main.fxml 包含:
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<fx:include source="button.fxml"/>
</VBox>
button.fxml:
<AnchorPane xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Button onAction="#changeLabel" text="sayHello" />
</AnchorPane>
什么会导致此 NPE?
您应该(几乎?)始终为不同的 FXML 文件使用不同的 class 控制器。 (我能想到的唯一例外是,如果您想定义不同的 FXML 文件来表示相同控件的不同布局。)
一种方法是将包含的 FXML 的控制器(“嵌套控制器”)注入主控制器。 (参见 documentation。)
public class MainController {
@FXML
private Label label;
@FXML
private ButtonController buttonController ;
@FXML
private void initialize() {
buttonController.setOnButtonPressed(this::changeLabel);
}
private void changeLabel() {
label.setText("Changed");
}
}
public class ButtonController {
private Runnable onButtonPressed ;
public void setOnButtonPressed(Runnable onButtonPressed) {
this.onButtonPressed = onButtonPressed ;
}
public Runnable getOnButtonPressed() {
return onButtonPressed ;
}
@FXML
private void changeLabel() {
if (onButtonPressed != null) {
onButtonPressed.run();
}
}
}
然后 FXML 文件看起来像
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.MainController">
<Label fx:id="label" text="default"/>
<fx:include fx:id="button" source="button.fxml"/>
</VBox>
和
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.ButtonController">
<Label fx:id="label" text="default"/>
<fx:include source="button.fxml"/>
</VBox>
一般来说,控制器之间相互引用是个坏主意,因为它会破坏封装并增加不必要的依赖关系。更好的方法是使用 MVC 设计。
public class Model {
private final StringProperty text = new SimpleStringProperty() ;
public StringProperty textProperty() {
return text ;
}
public final String getText() {
return textProperty().get();
}
public final void setText(String text) {
textProperty().set(text);
}
}
现在你可以做
public class MainController {
@FXML
private Label label;
private final Model model ;
public MainController(Model model) {
this.model = model ;
}
@FXML
private void initialize() {
label.textProperty().bind(model.textProperty());
}
}
和
public class ButtonController {
private final Model model ;
public ButtonController(Model model) {
this.model = model ;
}
@FXML
private void changeLabel() {
model.setText("Changed");
}
}
FXML文件如上,加载FXML时需要指定一个controller factory(以便通过将模型实例传递给constructors来实例化controller):
final Model model = new Model();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/path/to/main.fxml");
loader.setControllerFactory(type -> {
if (type.equals(MainController.class)) return new MainController(model);
if (type.equals(ButtonController.class)) return new ButtonController(model);
throw new IllegalArgumentException("Unexpected controller type: "+type);
});
Parent root = loader.load();
// ...