Question

I have a custom control defined like this:

<fx:root type="javafx.scene.Group" xmlns:fx="http://javafx.com/fxml" fx:controller="com.theCorp.theTool.controllers.LimitsController">
  <Group id="Group" layoutX="0.0" layoutY="0.0">
    <children>
        <Group id="Count" layoutX="0.0" layoutY="20.0">
            <children>
                <Label layoutX="0.0" layoutY="3.0" prefWidth="59.0" text="Count" />
                <TextField id="Count.Min" fx:id="countMin" layoutX="59.0" layoutY="0.0" prefWidth="132.0" promptText="Min" />
                <TextField id="Count.Max" fx:id="countMax" layoutX="191.0" layoutY="0.0" prefWidth="132.0" promptText="Max" />
            </children>
        </Group>
        <Button id="SaveButton" fx:id="saveButton" layoutX="12.0" layoutY="85.0" mnemonicParsing="false" onAction="#save" text="Save" />
    </children>
  </Group>
</fx:root>

I have a Control class that loads this in the usual way:

public class LimitsControl extends Group {
  private static final String fxmlFileName = "Limits.fxml";

  public LimitsControl() {
    super();
    URL fxml = getClass().getResource(fxmlFileName);
    if (fxml == null) {
      fxml = getClass().getResource("/fxml/" + fxmlFileName);
    }
    // Create the loader.
    FXMLLoader loader = new FXMLLoader(fxml);
    loader.setRoot(this);
    try {
      // Load the fxml.
      loader.load();
      // Pull back the controller.
      LimitsController controller = loader.getController();
      // Tell it about me.
      controller.setControl(this);
    } catch (IOException exception) {
      throw new RuntimeException(exception);
    }
  }

}

I have a Controller class that looks a bit like this:

public class LimitsController implements Initializable {
  // Control.
  private LimitsControl control;

  @FXML
  private TextField countMin;
  @FXML
  private TextField countMax;
  @FXML
  private Button saveButton;

  /**
   * Initializes the controller class.
   *
   * @param url
   * @param rb
   */
  @Override
  public void initialize(URL url, ResourceBundle rb) {
    log.info("Loading: " + url + " rb=" + rb+" control="+control);
    // Make the save button call my save.
    saveButton.setOnAction((ActionEvent e) -> {
      save(e);
    });
  }


  public void setControl(LimitsControl control) {
    log.info("Control="+control);
    this.control = control;
  }

  @FXML
  private void save(ActionEvent event) {
    // What is the ID of me?
    Parent myGroup = saveButton.getParent();
    // Pull the id of the control that enclosed my group.
    String myId = myGroup.getParent().getId();
    log.info("Saving: " + myId);
  }

}

And I insert these controls in my scene in several places, each time with a different id like this:

        <TitledPane fx:id="Today" animated="false" text="Today">
          <tooltip>
            <Tooltip text="Parameters will only be used for today." />
          </tooltip>
          <content>
            <LimitsControl id="Today.Limits" />
          </content>
        </TitledPane>

My problem is that these controls must be populated from data that requires the id of the control but when the controller is initialized it does not have a parent.

The id is available when the save button is pressed - see the save method climbing up the parent tree to gather the id of the control. Sadly the parent is null at initialize time.

How can I correctly initialize the fields at a time when the parent id is available? Or am I doing it wrong and there is a right way?

Was it helpful?

Solution

First, inject the control instead of coupling everything with set methods:

<fx:root type="javafx.scene.Group" xmlns:fx="http://javafx.com/fxml" fx:controller="com.theCorp.theTool.controllers.LimitsController" fx:id="control">

and

  // Control.
  @FXML
  private LimitsControl control;

If I understand correctly, the id you are trying to get is the id of the LimitsControl itself. To make this available "early", define a parameter in the constructor of LimitsControl and set the id there:

  public LimitsControl(String id) {
    super();
    setId(id);
        // code as before...
      }

Now, because you no longer have a default constructor, you need to make a Builder for LimitsControl:

public class LimitsControlBuilder {
    private String id ;
    private LimitsControlBuilder() {
        this.id = "" ;
    }
    public static LimitsControlBuilder create() {
        return new LimitsControlBuilder();
    }
    public LimitsControlBuilder id(String id) {
        this.id = id ;
        return this ;
    }

    public LimitsControl build() {
        return new LimitsControl(id);
    }
}

Now you can do

<LimitsControl id="Today.Limits" />

and it will be set as soon as the constructor is invoked.

You can of course create your own properties here instead of using the id, which might be a more appropriate way for the fxml to pass information to the custom control instance than using the css id.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top