0

I am using the following code to create a GridPane of TextFields in JavaFX:

GridPane grid = new GridPane();
for (int i = 0; i < 9; i++) 
    for (int j = 0; j < 9; j++) {
        grid.add(createTextField(), j, i);
    }
}

createTextField() is a method I made that returns a TextField with certain properties I set.

I would like to start using FXML files to create my GUIs in JavaFX, but I don't quite understand how to replace Java logic with an XML file. How would I recreate the above code using FXML? Does FXML have for loops?

qwerty
  • 810
  • 1
  • 9
  • 26
  • 4
    There's no way to do the same thing in FXML. This kind of UI lends itself much more to Java code than FXML. – James_D Jul 14 '20 at 15:10
  • 2
    No you can't. What you can do is create the grid in FXML or using [SceneBuilder](https://gluonhq.com/products/scene-builder/) for instance. If you'd still like to do it dynamically, as in it's not always 9x9 grid, then you can create an empty grid in FXML and then add the grid cells as you do now. The only difference would be that you can structure it as you want (using SceneBuilder) and your controller would include `@FXML GridPane myGrid` or any other name you chose in SceneBuilder. – Julian Broudy Jul 14 '20 at 16:15

2 Answers2

4

The FXML format does not provide a way to loop.

An FXML file should only describe the static (i.e. non-dynamic), non-repetitive parts of your UI. Making the UI fully dynamic is not possible with just an FXML file and declaring, for instance, 81 different TextFields quickly becomes unmanageable. These parts of the UI should be handled by the controller instead, which lets you connect Java code to an FXML file. In your case the fields should probably be added in the initialize method of the controller. For example:

FXML:

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

<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.GridPane?>

<GridPane fx:id="grid" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
          hgap="3" vgap="3" alignment="CENTER" fx:controller="com.example.Controller">
  <padding>
    <Insets topLeftBottomRight="5"/>
  </padding>
</GridPane>

Controller:

package com.example;

import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;

public class Controller {

  @FXML private GridPane grid;
  private TextField[][] fields;

  @FXML
  private void initialize() {
    grid.addEventFilter(KeyEvent.KEY_PRESSED, this::handleArrowNavigation);

    fields = new TextField[9][9];
    for (int i = 0; i < fields.length; i++) {
      for (int j = 0; j < fields[0].length; j++) {
        fields[i][j] = createTextField();
        grid.add(fields[i][j], j, i);
      }
    }
  }

  private TextField createTextField() {
    // ...
  }

  private void handleArrowNavigation(KeyEvent event) {
    // ...
  }
}

Since this seems to be connected to your previous question I also showed how to add the event filter, since there's no way to do so in the FMXL file (only onXXX event-handler properties can be set).

In this example it may not be worth it to use FXML since you're effectively only using FXML to declare a GridPane. But that obviously depends on how the rest of your application is designed.

Slaw
  • 37,820
  • 8
  • 53
  • 80
3

As stated in the comments and the other answer, there's no way to mimic loops in FXML, and this kind of functionality should be written in Java rather than FXML.

Note that one pattern is to create a custom class to handle the parts you need to write in code. I generally dislike subclassing layout classes solely for the purpose of adding controls to them, though in this case this does at least add some functionality, as well as playing nicely with FXML.

TextFieldGrid.java:

package org.jamesd.examples.textfieldgrid;

import javafx.beans.NamedArg;
import javafx.beans.property.StringProperty;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;

public class TextFieldGrid extends GridPane {

    private final StringProperty[][] text ;
    
    
    public TextFieldGrid(
            @NamedArg("numberOfColumns") int numberOfColumns,
            @NamedArg("numberOfRows") int numberOfRows 
    ) {
        
        text = new StringProperty[numberOfColumns][numberOfRows];
        for (int i = 0 ; i < numberOfRows ; i++) {
            for (int j = 0 ; j < numberOfColumns ; j++) {
                TextField textField = new TextField();
                add(textField, j, i);
                text[j][i] = textField.textProperty();
            }
        }
    }
    
    public StringProperty textProperty(int column, int row) {
        return text[column][row];
    }
    
    public final String getText(int column, int row) {
        return textProperty(column, row).get();
    }
    
    public final void setText(int column, int row, String text) {
        textProperty(column, row).set(text);
    }
    
    public int getNumberOfColumns() {
        return text.length;
    }
    
    public int getNumberOfRows() {
        return text[0].length;
    }
}

Which you can use, e.g. in an FXML file:

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

<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>

<?import org.jamesd.examples.textfieldgrid.TextFieldGrid?>

<BorderPane xmlns="http://javafx.com/javafx/8.0.171"
    xmlns:fx="http://javafx.com/fxml/1"
    fx:controller="org.jamesd.examples.textfieldgrid.Controller">
    <top>
        <Label text="Enter 81 values below:" />
    </top>

    <center>
        <TextFieldGrid fx:id="textFieldGrid" numberOfColumns="9" numberOfRows="9" />
    </center>

    <bottom>
        <Button text="Check values" onAction="#checkValues" />
    </bottom>

</BorderPane>

and reference the functionality in the normal way in the controller:

package org.jamesd.examples.textfieldgrid;

import javafx.fxml.FXML;

public class Controller {

    @FXML
    private TextFieldGrid textFieldGrid ;
    
    @FXML
    private void checkValues() {
        for (int i = 0 ; i < textFieldGrid.getNumberOfColumns(); i++) {
            for (int j = 0; j < textFieldGrid.getNumberOfRows(); j++) {
                System.out.printf("[%d, %d]: %s%n", i, j, textFieldGrid.getText(i, j));
            }
        }
    }

}

Just for completeness, the application class:

package org.jamesd.examples.textfieldgrid;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class App extends Application {


    @Override
    public void start(Stage stage) throws IOException {
        Scene scene = new Scene(FXMLLoader.load(getClass().getResource("TextFieldGrid.fxml")));
        stage.setScene(scene);
        stage.show();
    }


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

}
James_D
  • 201,275
  • 16
  • 291
  • 322