Gikkman
Gikkman

Reputation: 762

When can I access a FXML property?

I've been toying with creating custom components for JavaFX for a few days now and I've run into a problem. My intention is to create a component with a custom field, that can later be edited by the user of the component.

So far so good, the field is there and it can be edited. The field Value is my custom field and it can be edited.

Bonus questions: 1) How do I get rid of the User Agent Stylesheet field? 2) The field only accepts positive integers. How do I get it to accept negative integers?

The field "Value" is my custom field

Problem is when I try to access this value from my code. Here's the code for the component's .jar and .fxml

Simple.jar

public class Simple extends AnchorPane implements Initializable{

    public Simple(){
        //Loads the FXML sheet
        FXMLLoader fxmlLoader = new FXMLLoader( getClass().getResource( "Simple.fxml") );
        fxmlLoader.setRoot(this); 
        fxmlLoader.setController(this);

        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }

        System.out.println( "Constructor: " + valueProperty.getValue() );
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        System.out.println( "Init: " + valueProperty.getValue() );
    }


    @FXML protected void printValue(ActionEvent ae){
        System.out.println( "Button: " + valueProperty.getValue() );
    }

    //Editor field for the  value (helps scene builder to render it)
    IntegerProperty valueProperty;
    public void setValue(Integer value){
        valueProperty().setValue(value);
    }
    public Integer getValue(){
        return valueProperty == null ? -10 :  valueProperty.get();
    }
    public final IntegerProperty valueProperty() {
        if( valueProperty == null )
            valueProperty = new SimpleIntegerProperty(-10);
        return valueProperty;
    }
}

Simple.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>

<fx:root type="AnchorPane" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">

      <Button mnemonicParsing="false" onAction="#printValue" text="Button" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />

</fx:root>

These components have been exported as an external library called gikkWidgets and then imported into another project. As I said above, the value field can be accessed in Scene Builder but I cannot launch my application:

Exception in Application start method
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
    at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$152(LauncherImpl.java:182)
    at com.sun.javafx.application.LauncherImpl$$Lambda$50/1645995473.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:745)
Caused by: javafx.fxml.LoadException: 
/D:/Project/gikkWidgets/bin/main/Test.fxml:12

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2605)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2583)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2445)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3218)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3179)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3152)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3128)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3108)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3101)
    at main.Test.start(Test.java:15)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$159(LauncherImpl.java:863)
    at com.sun.javafx.application.LauncherImpl$$Lambda$53/198061478.run(Unknown Source)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$172(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl$$Lambda$45/186276003.run(Unknown Source)
    at com.sun.javafx.application.PlatformImpl.lambda$null$170(PlatformImpl.java:295)
    at com.sun.javafx.application.PlatformImpl$$Lambda$48/1595566805.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$171(PlatformImpl.java:294)
    at com.sun.javafx.application.PlatformImpl$$Lambda$47/237061348.run(Unknown Source)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$145(WinApplication.java:101)
    at com.sun.glass.ui.win.WinApplication$$Lambda$36/2117255219.run(Unknown Source)
    ... 1 more
Caused by: java.lang.RuntimeException: javafx.fxml.LoadException: 
/D:/Project/gikkWidgets/bin/com/gikk/javafx/simple/Simple.fxml

    at com.gikk.javafx.simple.Simple.<init>(Simple.java:26)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
    at java.lang.Class.newInstance(Class.java:442)
    at sun.reflect.misc.ReflectUtil.newInstance(ReflectUtil.java:51)
    at javafx.fxml.FXMLLoader$InstanceDeclarationElement.constructValue(FXMLLoader.java:1005)
    at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:742)
    at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2711)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2531)
    ... 22 more
Caused by: javafx.fxml.LoadException: 
/D:/Goats_Project/gikkWidgets/bin/com/gikk/javafx/simple/Simple.fxml

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2605)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2583)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2445)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2413)
    at com.gikk.javafx.simple.Simple.<init>(Simple.java:24)
    ... 32 more
Caused by: java.lang.NullPointerException
    at com.gikk.javafx.simple.Simple.initialize(Simple.java:34)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2552)
    ... 35 more
Exception running application main.Test

I am guessing it stems from some kind of timing issues with when properties are created. If you comment out the code in Simple.jar, row 29 and 34 (the constructor sysout and the initialize sysout), then it works and the button displays the correct value. However, in the actual component I am working on, I'd need access to the users desired value at construction time. Is this possible somehow?


EDIT

Puce' answer solved the initial problem, I was trying to access an uninitialized variable.

However, applying the fix he suggested (i.e., change valueProperty.getValue()to simple getValue() produces the following output:

Constructor: -10
Init: -10
Button: 5

I.e, the properties value is not set until after initialization. Is it possible somehow to read the property value earlier, so it can be used in the constructor (or at least in the initialize method)?

Upvotes: 3

Views: 1208

Answers (3)

Gikkman
Gikkman

Reputation: 762

James_D show a way to solve this problem, but it required a small tweek which I post here for reference.

I had to change the two constructors to get it to work. Remove the zero-args constructor and change the other:

public Simple(@NamedArg("value") Integer value){
    //Loads the FXML sheet
    FXMLLoader fxmlLoader = new FXMLLoader( getClass().getResource( "Simple.fxml") );
    fxmlLoader.setRoot(this); 
    fxmlLoader.setController(this);

    if(value != null )
        setValue(value);
    else
        setValue(DEFAULT_VALUE);

    try {
        fxmlLoader.load();
    } catch (IOException exception) {
        throw new RuntimeException(exception);
    }



    System.out.println( "Constructor: " + getValue() );
}

This implementation works. If the field "value" is not present in the .fxml file, the argument will be null, and we can set our value manually to DEFAULT_VALUE.

Upvotes: 0

James_D
James_D

Reputation: 209438

You can provide a property value as a parameter to the constructor if you annotate it @NamedArg. (The API docs give no information: see also here.)

So (includes slight change to your property implementation):

public class Simple extends AnchorPane implements Initializable{

    private static final int DEFAULT_VALUE = -10 ;

    public Simple(@NamedArg("value") int value){
        //Loads the FXML sheet
        FXMLLoader fxmlLoader = new FXMLLoader( getClass().getResource( "Simple.fxml") );
        fxmlLoader.setRoot(this); 
        fxmlLoader.setController(this);

        // set value, avoiding property creation if default...
        if (value != DEFAULT_VALUE) {
            setValue(value);
        }

        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }

        System.out.println( "Constructor: " + getValue() );
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        System.out.println( "Init: " + getValue() );
    }


    @FXML protected void printValue(ActionEvent ae){
        System.out.println( "Button: " + getValue() );
    }

    //Editor field for the  value (helps scene builder to render it)
    IntegerProperty valueProperty;
    public final void setValue(Integer value){
        valueProperty().setValue(value);
    }
    public final Integer getValue(){
        return valueProperty == null ? DEFAULT_VALUE : valueProperty.get();
    }
    public final IntegerProperty valueProperty() {
        if( valueProperty == null )
            valueProperty = new SimpleIntegerProperty(DEFAULT_VALUE);
        return valueProperty;
    }
}

This is a JavaFX 8 and later feature: I am not sure if SceneBuilder can operate fully with this.

Upvotes: 1

Puce
Puce

Reputation: 38132

Instead of:

 System.out.println( "Init: " + valueProperty.getValue() );

Try:

 System.out.println( "Init: " + getValue() );

Upvotes: 1

Related Questions