0

I am having a difficult time getting an FXML Text component to update after being dynamically created using FXMLLoader. This supposedly simple program asks a user to input the number of die to roll, creates new dice based upon this input, rolls them with a random number generator, sums these values, and provides a button to re-roll any of the die if they want. Each die is added to the main window (using FXMLoader and a custom die component) showing its index, the value of the roll, and a button to push if you want to re-roll a particular die. The values of the die present and update great. The re-roll buttons work great. However, for some reason, the index for each die does not show in the window - not even initially. The Text component in question (the one that won't show any values) is identified as fx:id="number" in the custom FXML document and as @FXML private Text number in the custom controller.

I'm assuming the issue has to do with the Text component's getters and setters and I'm lucky that the Text component named value works. However, if this is the case, the proper names have alluded me as I have checked many alternatives involving Property in the name, etc.

Note: I understand the sum of the die values won't be updated when the re-roll buttons are pushed. I only care about the die values being updated for now.

The main FXML window for the program is:

<GridPane   fx:id="mainPane" 
        styleClass="mainFxmlClass" 
        xmlns:fx="http://javafx.com/fxml/1" 
        fx:controller="org.lgdor.dieSim.MainWindowController" 
        xmlns="http://javafx.com/javafx/8.0_40"
        prefHeight="800.0" 
        prefWidth="800.0" 
        hgap="10" 
        vgap="10" >
<stylesheets>
    <URL value="@mainwindow.css"/>
</stylesheets>
<padding>
    <Insets top="25" right="25" bottom="10" left="25"/>
</padding>
<Text fx:id="welcomeText" text="Welcome to DieSim" 
      GridPane.columnIndex="0" 
      GridPane.rowIndex="0"
      GridPane.columnSpan="5"/>
<Label fx:id="spinnerText"
       text="Please input the number of dice you want to roll:"
       GridPane.columnIndex="0" 
       GridPane.rowIndex="1"
       GridPane.columnSpan="2"/>
<Spinner fx:id="spinner" editable="true" 
         GridPane.columnIndex="3" 
         GridPane.rowIndex="1" />
<HBox fx:id="buttonBox"
      spacing="10" 
      alignment="center" 
      GridPane.columnIndex="5" 
      GridPane.rowIndex="1">
    <Button text="Roll Dice" onAction="#createDice"/>

</HBox>
<Label fx:id="dieNumberText" text="The number of dice is: "
      GridPane.columnIndex="0" 
      GridPane.rowIndex="2"
      GridPane.columnSpan="2"/>
<Text fx:id="dieNumber" GridPane.columnIndex="3" GridPane.rowIndex="2" />
<Label fx:id="dieSumText" text="The sum of the dice is: "
      GridPane.columnIndex="0" 
      GridPane.rowIndex="3"
      GridPane.columnSpan="2"/>
<Text fx:id="sumNumber" GridPane.columnIndex="3" GridPane.rowIndex="3" />
<VBox fx:id="rowBox"
      spacing="10" 
      alignment="center" 
      GridPane.columnIndex="0" 
      GridPane.rowIndex="4"
      GridPane.columnSpan="5" >
    <Text text="This is where the list of dice goes..."/>

</VBox>

The main controller for this is:

public class MainWindowController implements Initializable {

@FXML
Label spinnerText;

@FXML
Spinner spinner;

@FXML
HBox buttonBox;

@FXML
Text dieNumber;

@FXML
Text sumNumber;

@FXML
int numberOfDice = 0;

@FXML
int sumOfDice = 0;

@FXML
VBox rowBox;

private List<Die> dieList = new ArrayList<>();
private List<GridPane> rowList = new ArrayList<>();

public void createDice() throws IOException{
    numberOfDice = (int)spinner.getValue();

    for (int i=0;i<numberOfDice;i++){
        Die die = new Die(i);
        die.roll(null);
        sumOfDice = sumOfDice + Integer.parseInt(die.getValue());
        dieList.add(die);
        rowBox.getChildren().add(die);
    }
    dieNumber.setText(String.valueOf(numberOfDice));
    sumNumber.setText(String.valueOf(sumOfDice));
    spinner.setVisible(false);
    spinnerText.setVisible(false);
    buttonBox.setVisible(false);
}

/**
 * Initializes the controller class.
 */
@Override
public void initialize(URL url, ResourceBundle rb) {
    spinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 20));
    dieNumber.setText("0");
    sumNumber.setText(String.valueOf("0"));
    spinnerText.setVisible(true);
    spinner.setVisible(true);
    buttonBox.setVisible(true);

}    
}

The die custom component that is dynamically created in the for loop above is (problem component is fx:id="number"):

<fx:root type="javafx.scene.layout.GridPane" 
      xmlns:fx="http://javafx.com/fxml/1"  
      xmlns="http://javafx.com/javafx/8.0_40" >
<HBox spacing="30" alignment="center" GridPane.columnIndex="0" GridPane.rowIndex="0"  >
    <Text fx:id="number" /><!--This is the problem component-->
    <Text fx:id="value"/>
    <Button fx:id="dieButton" text="Roll Dice" onAction="#roll"  />
</HBox>
</fx:root>

and its dynamically created controller is:

public class Die extends GridPane implements Initializable {

@FXML
private Text number;//This is the problem component ...
public String getNumber() {
    return numberTextProperty().get();
}
public void setNumber(String number) {
    numberTextProperty().set(number);
}
public StringProperty numberTextProperty() {
    return number.textProperty();
}

@FXML
private Text value;
public String getValue() {
    return valueTextProperty().get();
}
public void setValue(String value) {
    valueTextProperty().set(value);
}
public StringProperty valueTextProperty() {
    return value.textProperty();
}

public Die(int i) throws IOException{
    FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("DieGUI.fxml"));
    fxmlLoader.setRoot(this);
    fxmlLoader.setController(this);
    this.number = new Text(String.valueOf(i));
    this.setNumber(String.valueOf(1));
    this.value = new Text();
    this.setValue(String.valueOf(1));
    fxmlLoader.load();  
}

@FXML
public void roll(ActionEvent event){
    this.setValue(String.valueOf((int)(6*Math.random()+1)));
}

@Override
public void initialize(URL location, ResourceBundle resources) {
}
}
1
  • Just had a quick glance over your code and I identified two possible problems. First you call setNumber(String.valueOf(1)) once and never set another value to it again. Secondly you create the number text field in the constructor but simultaneously annotate it with @FXML. This is because your Die class is a UI component (extends GridPane) and at the same time is a controller for a completely different UI component (DieGUI). Commented Apr 5, 2018 at 16:52

1 Answer 1

1

The problem is you create a new Text instance, instead of configuring the one already created in the FXML:

this.number = new Text(String.valueOf(i));

The whole purpose of an @FXML annotation is to indicate that the field is initialized by the FXMLLoader, so it is always a mistake to initialize fields annotated @FXML.

Note here that these fields are initialized by the FXMLLoader during the call to load(), so you need to call load() before you configure them in any way.

So your constructor should look like:

public Die(int i) throws IOException{
    FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("DieGUI.fxml"));
    fxmlLoader.setRoot(this);
    fxmlLoader.setController(this);
    fxmlLoader.load();  

    // these don't seem to do anything useful?
    // this.setNumber(String.valueOf(1));
    // this.setValue(String.valueOf(1));

    this.number.setText(String.valueOf(i));
    this.value.setText(String.valueOf(i));
}
Sign up to request clarification or add additional context in comments.

1 Comment

That did it! Thanks! So simple.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.