JavaFXFXML包含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.fml:

<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的控制器("嵌套控制器"(注入主控制器。(请参阅文档。(

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();
// ...

相关内容

最新更新