如何解决JavaFX FXML 包含 fxml 导致 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 文件的控制器使用不同的类。 (我能想到的唯一例外是,如果您想定义不同的 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时需要指定控制器工厂(这样控制器通过将模型实例传递给构造函数来实例化):
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();
// ...
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。