CorellianAle
CorellianAle

Reputation: 685

JavaFX change node generated by FXML to another node

I have a complicated UI defined by the fxml file. There are a lot of stock TextFields that I would like to change to more advanced versions (clearable text field) provided by ControlsFX library.

The problem:

ControlsFX uses CustomTextField to provide advanced features but it can only be initialized by TextFields.createClearableTextField() which can only be called from code.

I am looking for a way to swap TextFields initialized via FXML with CustomTextFields initialized from code so that these new controls would retain all layout properties defined in FXML.

Defining CustomTextField in FXML is also not helpful because by default it is useless. CustomTextField gains it's features via private static void setupClearButtonField(TextField inputField, ObjectProperty<Node> rightProperty) which i can't call because it's private.

ControlsFX code:

/**
 * Creates a TextField that shows a clear button inside the TextField (on
 * the right hand side of it) when text is entered by the user.
 */
public static TextField createClearableTextField() {
    CustomTextField inputField = new CustomTextField();
    setupClearButtonField(inputField, inputField.rightProperty());
    return inputField;
}

/**
 * Creates a PasswordField that shows a clear button inside the PasswordField
 * (on the right hand side of it) when text is entered by the user.
 */
public static PasswordField createClearablePasswordField() {
    CustomPasswordField inputField = new CustomPasswordField();
    setupClearButtonField(inputField, inputField.rightProperty());
    return inputField;
}

private static void setupClearButtonField(TextField inputField, ObjectProperty<Node> rightProperty) {
    inputField.getStyleClass().add("clearable-field"); //$NON-NLS-1$

    Region clearButton = new Region();
    clearButton.getStyleClass().addAll("graphic"); //$NON-NLS-1$
    StackPane clearButtonPane = new StackPane(clearButton);
    clearButtonPane.getStyleClass().addAll("clear-button"); //$NON-NLS-1$
    clearButtonPane.setOpacity(0.0);
    clearButtonPane.setCursor(Cursor.DEFAULT);
    clearButtonPane.setOnMouseReleased(e -> inputField.clear());
    clearButtonPane.managedProperty().bind(inputField.editableProperty());
    clearButtonPane.visibleProperty().bind(inputField.editableProperty());

    rightProperty.set(clearButtonPane);

    final FadeTransition fader = new FadeTransition(FADE_DURATION, clearButtonPane);
    fader.setCycleCount(1);

    inputField.textProperty().addListener(new InvalidationListener() {
        @Override public void invalidated(Observable arg0) {
            String text = inputField.getText();
            boolean isTextEmpty = text == null || text.isEmpty();
            boolean isButtonVisible = fader.getNode().getOpacity() > 0;

            if (isTextEmpty && isButtonVisible) {
                setButtonVisible(false);
            } else if (!isTextEmpty && !isButtonVisible) {
                setButtonVisible(true);
            }
        }

        private void setButtonVisible( boolean visible ) {
            fader.setFromValue(visible? 0.0: 1.0);
            fader.setToValue(visible? 1.0: 0.0);
            fader.play();
        }
    });
}

Upvotes: 2

Views: 1005

Answers (1)

Slaw
Slaw

Reputation: 45806

You can use fx:factory as described in Introduction to FXML.

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

<?import javafx.scene.layout.StackPane?>
<?import org.controlsfx.control.textfield.TextFields?>

<StackPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
           fx:controller="com.example.Controller">

    <TextFields fx:id="field" fx:factory="createClearableTextField"/>

</StackPane>

Then use it in the controller:

package com.example;

import javafx.fxml.FXML;
import javafx.scene.control.TextField;

public class Controller {

    @FXML private TextField field;

}

Note: If you're using IntelliJ, it will emit an error saying:

Cannot set org.controlsfx.control.textfield.TextFields to field 'field'

In the FXML file, but when I ran a test project everything worked properly.

Upvotes: 4

Related Questions