英文:
NullException when binding a property of a nested controller
问题
我想在一个FXML界面(nested.fxml)中更改的文本字段的值,在另一个视图(main.fxml)中获取它。我已经定义了一个像这样的nestedController:
package reproduced;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
public class NestedController implements Initializable{
@FXML
private TextField changeValue;
public ObjectProperty<ObjectStringToUpdate> attVal = new SimpleObjectProperty<>();
@Override
@FXML
public void initialize(URL location, ResourceBundle resources) {
this.changeValue.textProperty().addListener(new ChangeListener<Object>() {
public void changed(ObservableValue<?> ov, Object t, Object t1) {
ObjectStringToUpdate newValue = new ObjectStringToUpdate();
newValue.setAttValNested(t1.toString());
attVal.setValue(newValue);
}
});
}
}
而mainController如下:
import java.io.IOException;
import java.net.URL;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.BorderPane;
public class MainController implements Initializable{
@FXML
public BorderPane mainBorderPane;
Property<ObjectStringToUpdate> attVal = new SimpleObjectProperty<>();
@FXML
public NestedController nestedController;//injected nested controller
@Override
public void initialize(URL location, ResourceBundle resources) {
ObjectProperty<ObjectStringToUpdate> binding = nestedController.attVal;
((Property<ObjectStringToUpdate>) this.attVal).bind(binding);
}
@FXML
public void testVal() {
System.out.println(this.attVal.getValue().attValNested);// this when called returns null value because this.attaVal is null
/*Caused by: java.lang.NullPointerException: Cannot read field "attValNested" because the return value of "javafx.beans.property.Property.getValue()" is null
*/
}
@FXML
public void openNestedView() throws IOException {
FXMLLoader loaderOfTabs = new FXMLLoader(getClass().getResource("/nested.fxml"));
AnchorPane tabPane = (AnchorPane) loaderOfTabs.load();
mainBorderPane.setCenter(tabPane);
}
}
然后是ObjectStringToUpdate对象:
public class ObjectStringToUpdate {
public String attValNested;
public String getAttValNested() {
return attValNested;
}
public void setAttValNested(String attVal) {
this.attValNested = attVal;
}
}
最后是应用程序的主入口:
package reproduced;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class UInterface2 extends Application {
@Override
public void start(Stage stage) throws IOException {
String fxmlFile = "main.fxml";
FXMLLoader loader = new FXMLLoader();
Parent rootNode = (Parent) loader.load(getClass().getClassLoader().getResource(fxmlFile));
Scene scene = new Scene(rootNode, 1024, 843);
stage.setTitle("reproduced");
stage.setResizable(false);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
在这个配置下,你应该能够在NestedController中更改文本字段的值,然后通过在MainController中调用testVal()
方法来获取更新后的值,而不会出现空指针异常。
英文:
I want to get the value of a textfield that is changed in one fxml interface (nested.fxml) in another view (main.fxml). I have defined a nestedController like this:
`
package reproduced;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
public class NestedController implements Initializable{
@FXML
private TextField changeValue;
public ObjectProperty<ObjectStringToUpdate> attVal= new SimpleObjectProperty<>();
@Override
@FXML
public void initialize(URL location, ResourceBundle resources) {
this.changeValue.textProperty().addListener(new ChangeListener<Object>() {
public void changed(ObservableValue<?> ov, Object t, Object t1) {
ObjectStringToUpdate newValue = new ObjectStringToUpdate();
newValue.setAttValNested(t1.toString());
attVal.setValue(newValue);
}
});
}
}`
And the mainController is:
`import java.io.IOException;
import java.net.URL;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
public class MainController implements Initializable{
@FXML
public BorderPane mainBorderPane;
Property<ObjectStringToUpdate> attVal = new SimpleObjectProperty<>();
@FXML public NestedController nestedController;//injected nested controller
@Override
public void initialize(URL location, ResourceBundle resources) {
ObjectProperty<ObjectStringToUpdate> binding = nestedController.attVal;
((Property<ObjectStringToUpdate>) this.attVal).bind(binding);
}
@FXML
public void testVal() {
System.out.println(this.attVal.getValue().attValNested);// this when called returns null value because this.attaVal is null
/*Caused by: java.lang.NullPointerException: Cannot read field "attValNested" because the return value of "javafx.beans.property.Property.getValue()" is null
*/
}
@FXML
public void openNestedView() throws IOException {
FXMLLoader loaderOfTabs = new
FXMLLoader(getClass().getResource("/nested.fxml"));
AnchorPane tabPane = (AnchorPane) loaderOfTabs.load();
mainBorderPane.setCenter(tabPane);
}
}`
Then the object:
`public class ObjectStringToUpdate {
public String attValNested;
public String getAttValNested() {
return attValNested;
}
public void setAttValNested(String attVal) {
this.attValNested = attVal;
}
}`
The app main entry is:
`package reproduced;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class UInterface2 extends Application{
@Override
public void start(Stage stage) throws IOException {
String fxmlFile = "main.fxml";
FXMLLoader loader = new FXMLLoader();
Parent rootNode = (Parent) loader.load(getClass().getClassLoader().getResource(fxmlFile));
Scene scene = new Scene(rootNode, 1024, 843);
stage.setTitle("reproduced");
stage.setResizable(false);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}`
fxmls:
main.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:id="rootAnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="843.0" prefWidth="1224.0" fx:controller="reproduced.MainController">
<children>
<BorderPane fx:id="mainBorderPane" layoutY="-1.0" prefHeight="843.0" prefWidth="1024.0">
<top>
<Button mnemonicParsing="false" onAction="#openNestedView" text="open nested View" BorderPane.alignment="CENTER" />
</top>
</BorderPane>
<Button fx:id="testVal" mnemonicParsing="false" onAction="#testVal" text="test" />
<Label fx:id="showValue" text="Label" />
</children>
<fx:include fx:id="nested" source="nested.fxml" visible="false" />
</AnchorPane>`
nested.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<AnchorPane xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:id="rootAnchorPaneInner" fx:controller="reproduced.NestedController">
<children>
<TextField xmlns="http://javafx.com/javafx/8" fx:id="changeValue" />
</children>
</AnchorPane>`
I have also included the fxml component of NestedController in main.fxml properly.
But when I update the textfield value in the nested controller and try to call it in the main Controller through a button click I get null exception: the return value of "javafx.beans.property.ObjectProperty.getValue()" is null
I expect the this.attVal
at MainController.testVal()
to return the value as changed in the NestedController
.
答案1
得分: 4
你正在两次加载FXML文件nested.fxml
。一次是通过main.fxml
中的<fx:include>
元素,然后每次按下"打开嵌套视图"按钮时都会加载一次。
通过<fx:include>
元素加载的nested.fxml
实际上是不可见的,因为你(出于某种原因)在<fx:include>
元素中使用了visible="false"
。当按下按钮时出现的实例是完全不同的,是通过调用loaderOfTabs.load()
创建的。
嵌套控制器当然与通过<fx:include>
创建的实例相关联。由于它不可见,用户永远无法在其文本字段中输入内容,因此永远不会触发其文本字段的textProperty.onChanged
处理程序。因此,在该控制器实例中,attVal
的值从未初始化:也就是说,在该控制器实例中,attVal.getValue()
始终为null,因此当你调用attVal.getValue().attValNested
时,当然会得到NullPointerException
。
我不明白为什么你有多个nested.fxml
UI的实例。要么使用<fx:include>
,要么在代码中加载它。如果你想使用<fx:include>
并在用户按下按钮之前保持它不可见(为什么?),只需这样做:
public class MainController implements Initializable {
// ...
@FXML
public NestedController nestedController; // 注入的嵌套控制器
@FXML
private AnchorPane nested;
// ...
public void openNestedView() throws IOException {
nested.setVisible(true);
}
}
另一方面,如果你想在按下按钮时加载FXML,请从main.fxml
中删除<fx:include>
:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:id="rootAnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="843.0" prefWidth="1224.0" fx:controller="org.jamesd.examples.nestedprop.MainController">
<children>
<BorderPane fx:id="mainBorderPane" layoutY="-1.0" prefHeight="843.0" prefWidth="1024.0">
<top>
<Button mnemonicParsing="false" onAction="#openNestedView" text="打开嵌套视图" BorderPane.alignment="CENTER" />
</top>
</BorderPane>
<Button fx:id="testVal" mnemonicParsing="false" onAction="#testVal" text="测试" />
<Label fx:id="showValue" text="标签" />
</children>
</AnchorPane>
然后,当你获取控制器时,从FXMLLoader
执行绑定:
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
public class MainController implements Initializable {
@FXML
public BorderPane mainBorderPane;
Property<ObjectStringToUpdate> attVal = new SimpleObjectProperty<>();
private NestedController nestedController;
@Override
public void initialize(URL location, ResourceBundle resources) {
}
@FXML
public void testVal() {
System.out.println(this.attVal.getValue().attValNested);
}
@FXML
public void openNestedView() throws IOException {
FXMLLoader loaderOfTabs = new FXMLLoader(getClass().getResource("nested.fxml"));
AnchorPane tabPane = (AnchorPane) loaderOfTabs.load();
nestedController = loaderOfTabs.getController();
ObjectProperty<ObjectStringToUpdate> binding = nestedController.attVal;
this.attVal.bind(binding);
mainBorderPane.setCenter(tabPane);
}
}
英文:
You are loading the FXML file nested.fxml
twice. Once via the <fx:include>
element in main.fxml
, and then subsequently each time you press the "open nested view" button.
The instance of nested.fxml
loaded via <fx:include>
is not actually visible, since you (for some reason) have visible="false"
in the <fx:include>
element. The instance that appears when the button is pressed is a different instance entirely, created via the call to loaderOfTabs.load()
.
The nested controller, of course, is associated with the instance created by <fx:include>
. Since that is not visible, the user can never type in its text field, and so its text field's textProperty.onChanged
handler can never be invoked. Consequently, the value of attVal
in that instance of the controller is never initialized: i.e. in that controller instance attVal.getValue()
will always be null, and so when you invoke attVal.getValue().attValNested
you of course get a NullPointerException
.
I don't understand why you have multiple instances of the nested.fxml
UI. Either use <fx:include>
or load it in code. If you want to use <fx:include>
and keep it invisible until the user presses a button (why???), just do:
public class MainController implements Initializable{
// ...
@FXML public NestedController nestedController;//injected nested controller
@FXML private AnchorPane nested;
// ...
public void openNestedView() throws IOException {
nested.setVisible(true);
}
}
Conversely, if you want to load the FXML when the button is pressed, remove the <fx:include>
from main.fxml
:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:id="rootAnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="843.0" prefWidth="1224.0" fx:controller="org.jamesd.examples.nestedprop.MainController">
<children>
<BorderPane fx:id="mainBorderPane" layoutY="-1.0" prefHeight="843.0" prefWidth="1024.0">
<top>
<Button mnemonicParsing="false" onAction="#openNestedView" text="open nested View" BorderPane.alignment="CENTER" />
</top>
</BorderPane>
<Button fx:id="testVal" mnemonicParsing="false" onAction="#testVal" text="test" />
<Label fx:id="showValue" text="Label" />
</children>
</AnchorPane>
and then just get the controller from the FXMLLoader
and perform the binding when you get it:
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
public class MainController implements Initializable{
@FXML
public BorderPane mainBorderPane;
Property<ObjectStringToUpdate> attVal = new SimpleObjectProperty<>();
private NestedController nestedController;
@Override
public void initialize(URL location, ResourceBundle resources) {
}
@FXML
public void testVal() {
System.out.println(this.attVal.getValue().attValNested);
}
@FXML
public void openNestedView() throws IOException {
FXMLLoader loaderOfTabs = new
FXMLLoader(getClass().getResource("nested.fxml"));
AnchorPane tabPane = (AnchorPane) loaderOfTabs.load();
nestedController = loaderOfTabs.getController();
ObjectProperty<ObjectStringToUpdate> binding = nestedController.attVal;
this.attVal.bind(binding);
mainBorderPane.setCenter(tabPane);
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论