5

I am trying to define dynamic evaluated variables with fx:define, but I can't get a variable to be evaluated from another one, I don't know if it's even possible?

<GridPane hgap="${10*m.dp}" vgap="${10*m.dp}" xmlns="http://javafx.com/javafx/8.0.51" xmlns:fx="http://javafx.com/fxml/1">
    <fx:define>
        <Measurement fx:id="m" />
        <!-- This works, but is not what I need -->
        <Double fx:id="width" fx:value="300" />
        <!-- This doesn't work -->  
<!--        <Double fx:id="width" fx:value="${300*m.dp}" /> -->
    </fx:define>
    <padding>
        <Insets bottom="$width" left="$width" right="$width" top="$width" />
    </padding>
    <Text text="hello" />
    <Button GridPane.rowIndex="1" text="button" prefWidth="${300*m.dp}" />
    <Button GridPane.rowIndex="2" text="button2" prefWidth="$width" />
</GridPane>

What I want here is to compute the width from my computed dp (Density independent pixel - value is 1 in HD screen, 2 in 4K screen, 0.xx on my 1600px width screen). The "width" variable I want is to be used in very many component in my real case, this is why I wanted a variable - for concision.

The java code to run it

public class Measurement {
  private double dp;

  public Measurement(){
    Screen primary = Screen.getPrimary();
    dp=primary.getBounds().getWidth()/1920;
  }

  /** 
   * Equivalent of 1 px in 1920. 
   */
  public double getDp(){
    return dp;
  }

  public void setDp (double dp) {
    this.dp = dp;
  }

}
public class MApplication extends Application {
  public static void main (String[] args) {
    launch (args);
  }

  @Override
  public void start (Stage primaryStage) throws Exception {
    FXMLLoader fxmlLoader = new FXMLLoader();
    Parent root = fxmlLoader.load(getClass().getResource("index.fxml").openStream());
    Scene scene = new Scene(root, 300, 275);
    primaryStage.setMaximized(true);
    primaryStage.setScene(scene);
    primaryStage.show ();
  }


}

Actually, it s not clear to me which and where expression can be used, I see many answers here :Is it possible to use arithmetic expression in FXML?

here:Bind Font Size in JavaFX?

and here: http://docs.oracle.com/javase/8/javafx/api/javafx/fxml/doc-files/introduction_to_fxml.html#expression_binding

I don't understand in which case I can use the expression language,is there a specification?

Edited: I adedd a Link to javafx-8 documentation, And I removed my obsolete comment on javaFX-2

Miss Chanandler Bong
  • 4,081
  • 10
  • 26
  • 36
pdem
  • 3,880
  • 1
  • 24
  • 38
  • Can you use multiple `fx:define` tags? Is so maybe try to see if one can see the definition made in the former. – Cyrille Pontvieux Jun 16 '16 at 15:20
  • @Cyrille_Pontvieux Yes we can, and i tried, but it changes nothing, It is much like multiple declaration of define block, with the same variable declaration, but if only I knewed where the implementation of "fx:define" is, I could try to debug. – pdem Jun 16 '16 at 15:45

1 Answers1

1

Too late bute this might be helpful for somebody. When you use <Double fx:id="width" fx:value="300"/> inside a <fx:define> block, FMXLLoader goes the declared type, Double in this case, and tries to call the method Double.valueOf("300") and this works since this call returns a Double object with the value of 300. When you use <Double fx:id="width" fx:value="${300*m.dp}"/>, a NumberFormatException will be thrown since the value "${300*m.dp}" doesn't represent a valid double value.

FXMLLoader evaluates the expressions that start with "${" only when the type is observable. In order to bind a value to another in FXML, both of them should be observable properties. In your case, you can add the following property to Measurement class:

public class Measurement {
    public final DoubleProperty dp = new SimpleDoubleProperty();

    public Measurement() {
        Screen primary = Screen.getPrimary();
        dp.set(primary.getBounds().getWidth() / 1920.0);
    }

    public double getDp() {
        return dp.get();
    }

    public void setDp(double dp) {
        this.dp.set(dp);
    }

    public final DoubleProperty widthProperty() {
        if (width == null) {
            width = new SimpleDoubleProperty();
            width.bind(dp.multiply(300.0));
        }
        return width;
    }

    private DoubleProperty width;

    public final double getWidth() {
        return width == null ? 0 : width.get();
    }
}

And then use it like this in your FXML:

<fx:define>
    <Measurement fx:id="measurement"/>
</fx:define>
<Button prefWidth="${measurement.width}" />

Note: IMHO, I don't think you need to make Measurement class instantiatable. There is no meaning of repeating the same measures and create a new instances in every FXML file. An alternative solution would be a class with a private constructor that holds static properties and used in FXML by calling <fx:factory> or <fx:constant>.

Miss Chanandler Bong
  • 4,081
  • 10
  • 26
  • 36
  • What about writing the size in the fxml instead of the java, will it be possible? The usage was sizing many buttons at 300*dp, and some for example at 400*dp, for 4K compatiblity. – pdem Jan 07 '20 at 15:45
  • You can define `width` property in FXML and initialize it in your code using the annotation `@FXML`. if you have more than one configuration, you can use two different properties, let's say `width` and `width4k`. The first will be bound to dp*300 and the other will be dp*400. I can help you with the implementation if you have any problems. – Miss Chanandler Bong Jan 07 '20 at 16:01