2

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();
        }
    });
}
CorellianAle
  • 645
  • 8
  • 16
  • What happens when you use the CustomTextField in place of a TextField in your code and FXML? `createClearableTextField()` returns a TextField, so it seems like formatting and placement should continue to work correctly. – BankBuilder Apr 15 '19 at 19:42

1 Answers1

4

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.

Slaw
  • 37,820
  • 8
  • 53
  • 80
  • Unfortunately, it fails to load FXML file with `java.lang.NoSuchMethodException`. It tries to load `createClearableTextField()` as a static method of `javafx.scene.control.TextField`, but it exists in `org.controlsfx.control.textfield.TextFields`. – CorellianAle Apr 16 '19 at 07:03
  • I've overcame this problem by creating `class MyTextField extends TextField` and providing `public static TextField create() { return TextFields.createClearableTextField()` }. Then I wrote `` in FXML file. – CorellianAle Apr 16 '19 at 07:08
  • I can't reproduce that problem, even when I have both `TextField` and `TextFields` imported; I tried on JavaFX 8u202, JavaFX 11.0.2, and JavaFX 12. Are you sure you didn't just have a typo (e.g. `TextField` instead of `TextFields`)? – Slaw Apr 16 '19 at 11:31
  • Can confirm, it works on JavaFX 15.0.1 with ControlsFX 11.0.3 in Intellij Community 2020.3, thank you! – heethjain21 Dec 14 '20 at 17:04