Issue
I have a complicated UI defined by the fxml file. There are a lot of stock TextField
s 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 TextField
s initialized via FXML with CustomTextField
s 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();
}
});
}
Solution
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.
Answered By - Slaw