NullException在绑定嵌套控制器的属性时发生。

huangapple go评论39阅读模式
英文:

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&lt;ObjectStringToUpdate&gt; attVal= new SimpleObjectProperty&lt;&gt;();

    @Override
    @FXML
    public void initialize(URL location, ResourceBundle resources) {
        this.changeValue.textProperty().addListener(new ChangeListener&lt;Object&gt;() {
	 
        public void changed(ObservableValue&lt;?&gt; 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&lt;ObjectStringToUpdate&gt; attVal =  new SimpleObjectProperty&lt;&gt;();
	 
    @FXML public NestedController nestedController;//injected nested controller
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        ObjectProperty&lt;ObjectStringToUpdate&gt; binding = nestedController.attVal;
        ((Property&lt;ObjectStringToUpdate&gt;) 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 &quot;attValNested&quot; because the return value of &quot;javafx.beans.property.Property.getValue()&quot; is null
*/
    }
    @FXML 
    public void openNestedView() throws IOException {
    	FXMLLoader loaderOfTabs = new 
    	        FXMLLoader(getClass().getResource(&quot;/nested.fxml&quot;));
    	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 = &quot;main.fxml&quot;;
        FXMLLoader loader = new FXMLLoader();
        Parent rootNode = (Parent) loader.load(getClass().getClassLoader().getResource(fxmlFile));
        Scene scene = new Scene(rootNode, 1024, 843);
        stage.setTitle(&quot;reproduced&quot;);
        stage.setResizable(false);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }

}`

fxmls:

main.fxml:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;?import javafx.scene.layout.*?&gt;
&lt;?import java.lang.*?&gt;
&lt;?import javafx.scene.control.*?&gt;
&lt;?import javafx.scene.layout.AnchorPane?&gt;
&lt;?import javafx.scene.layout.BorderPane?&gt;
&lt;AnchorPane xmlns=&quot;http://javafx.com/javafx/8&quot; xmlns:fx=&quot;http://javafx.com/fxml/1&quot; fx:id=&quot;rootAnchorPane&quot; maxHeight=&quot;-Infinity&quot; maxWidth=&quot;-Infinity&quot; minHeight=&quot;-Infinity&quot; minWidth=&quot;-Infinity&quot; prefHeight=&quot;843.0&quot; prefWidth=&quot;1224.0&quot; fx:controller=&quot;reproduced.MainController&quot;&gt;
   &lt;children&gt;
      &lt;BorderPane fx:id=&quot;mainBorderPane&quot; layoutY=&quot;-1.0&quot; prefHeight=&quot;843.0&quot; prefWidth=&quot;1024.0&quot;&gt;
         &lt;top&gt;
            &lt;Button mnemonicParsing=&quot;false&quot; onAction=&quot;#openNestedView&quot; text=&quot;open nested View&quot; BorderPane.alignment=&quot;CENTER&quot; /&gt;
         &lt;/top&gt;
      &lt;/BorderPane&gt;
      &lt;Button fx:id=&quot;testVal&quot; mnemonicParsing=&quot;false&quot; onAction=&quot;#testVal&quot; text=&quot;test&quot; /&gt;
      &lt;Label fx:id=&quot;showValue&quot; text=&quot;Label&quot; /&gt;
   &lt;/children&gt;
   &lt;fx:include fx:id=&quot;nested&quot; source=&quot;nested.fxml&quot; visible=&quot;false&quot; /&gt;
&lt;/AnchorPane&gt;`

nested.fxml:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;?import java.lang.*?&gt;
&lt;?import javafx.scene.control.*?&gt;
&lt;?import javafx.scene.layout.AnchorPane?&gt;
&lt;?import javafx.scene.layout.BorderPane?&gt;
&lt;AnchorPane xmlns=&quot;http://javafx.com/javafx/17&quot; xmlns:fx=&quot;http://javafx.com/fxml/1&quot; fx:id=&quot;rootAnchorPaneInner&quot; fx:controller=&quot;reproduced.NestedController&quot;&gt;
   &lt;children&gt;
      &lt;TextField xmlns=&quot;http://javafx.com/javafx/8&quot; fx:id=&quot;changeValue&quot; /&gt;
   &lt;/children&gt;
&lt;/AnchorPane&gt;`

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 &quot;javafx.beans.property.ObjectProperty.getValue()&quot; 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中的&lt;fx:include&gt;元素,然后每次按下"打开嵌套视图"按钮时都会加载一次。

通过&lt;fx:include&gt;元素加载的nested.fxml实际上是不可见的,因为你(出于某种原因)在&lt;fx:include&gt;元素中使用了visible=&quot;false&quot;。当按下按钮时出现的实例是完全不同的,是通过调用loaderOfTabs.load()创建的。

嵌套控制器当然与通过&lt;fx:include&gt;创建的实例相关联。由于它不可见,用户永远无法在其文本字段中输入内容,因此永远不会触发其文本字段的textProperty.onChanged处理程序。因此,在该控制器实例中,attVal的值从未初始化:也就是说,在该控制器实例中,attVal.getValue()始终为null,因此当你调用attVal.getValue().attValNested时,当然会得到NullPointerException

我不明白为什么你有多个nested.fxml UI的实例。要么使用&lt;fx:include&gt;,要么在代码中加载它。如果你想使用&lt;fx:include&gt;并在用户按下按钮之前保持它不可见(为什么?),只需这样做:

public class MainController implements Initializable {

    // ...

    @FXML
    public NestedController nestedController; // 注入的嵌套控制器
    @FXML
    private AnchorPane nested;

    // ...

    public void openNestedView() throws IOException {
        nested.setVisible(true);
    }
}

另一方面,如果你想在按下按钮时加载FXML,请从main.fxml中删除&lt;fx:include&gt;

<?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 &lt;fx:include&gt; element in main.fxml, and then subsequently each time you press the "open nested view" button.

The instance of nested.fxml loaded via &lt;fx:include&gt; is not actually visible, since you (for some reason) have visible=&quot;false&quot; in the &lt;fx:include&gt; 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 &lt;fx:include&gt;. 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 &lt;fx:include&gt; or load it in code. If you want to use &lt;fx:include&gt; 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 &lt;fx:include&gt; from main.fxml:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;?import javafx.scene.control.Button?&gt;
&lt;?import javafx.scene.control.Label?&gt;
&lt;?import javafx.scene.layout.AnchorPane?&gt;
&lt;?import javafx.scene.layout.BorderPane?&gt;
&lt;AnchorPane xmlns=&quot;http://javafx.com/javafx/8&quot; xmlns:fx=&quot;http://javafx.com/fxml/1&quot; fx:id=&quot;rootAnchorPane&quot; maxHeight=&quot;-Infinity&quot; maxWidth=&quot;-Infinity&quot; minHeight=&quot;-Infinity&quot; minWidth=&quot;-Infinity&quot; prefHeight=&quot;843.0&quot; prefWidth=&quot;1224.0&quot; fx:controller=&quot;org.jamesd.examples.nestedprop.MainController&quot;&gt;
    &lt;children&gt;
        &lt;BorderPane fx:id=&quot;mainBorderPane&quot; layoutY=&quot;-1.0&quot; prefHeight=&quot;843.0&quot; prefWidth=&quot;1024.0&quot;&gt;
            &lt;top&gt;
                &lt;Button mnemonicParsing=&quot;false&quot; onAction=&quot;#openNestedView&quot; text=&quot;open nested View&quot; BorderPane.alignment=&quot;CENTER&quot; /&gt;
            &lt;/top&gt;

        &lt;/BorderPane&gt;
        &lt;Button fx:id=&quot;testVal&quot; mnemonicParsing=&quot;false&quot; onAction=&quot;#testVal&quot; text=&quot;test&quot; /&gt;
        &lt;Label fx:id=&quot;showValue&quot; text=&quot;Label&quot; /&gt;
    &lt;/children&gt;
&lt;/AnchorPane&gt;

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&lt;ObjectStringToUpdate&gt; attVal =  new SimpleObjectProperty&lt;&gt;();

    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(&quot;nested.fxml&quot;));
        AnchorPane tabPane = (AnchorPane) loaderOfTabs.load();

        nestedController = loaderOfTabs.getController();

        ObjectProperty&lt;ObjectStringToUpdate&gt; binding = nestedController.attVal;
        this.attVal.bind(binding);

        mainBorderPane.setCenter(tabPane);

    }

}

huangapple
  • 本文由 发表于 2023年6月2日 04:48:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76385609.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定