我想将一个按钮提取到一个新的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();
// ...