I improved my previous implementation of TextField validation, this time creating a real Custom Control with real-time validation using bindings. It can be used with FXML without the need for more Java code.
import javafx.beans.binding.BooleanBinding; import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyIntegerProperty; import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.scene.control.TextField; import javafx.scene.effect.BlurType; import javafx.scene.effect.DropShadow; import javafx.scene.effect.Effect; import javafx.scene.paint.Color; public final class ValidatedTextField extends TextField { private final BooleanProperty invalid = new SimpleBooleanProperty(false); private final StringProperty mask; private final IntegerProperty minLength; private final IntegerProperty maxLength; private Effect invalidEffect = new DropShadow(BlurType.GAUSSIAN, Color.RED, 4, 0.0, 0, 0); public ValidatedTextField() { super(); this.mask = new SimpleStringProperty("."); this.minLength = new SimpleIntegerProperty(-1); this.maxLength = new SimpleIntegerProperty(-1); bind(); } public ValidatedTextField(String mask, int minLength, int maxLength, boolean nullable) { this(mask, minLength, maxLength, nullable, null); } public ValidatedTextField(String mask, int minLength, int maxLength, boolean nullable, String string) { super(string); this.mask = new SimpleStringProperty(mask); this.minLength = new SimpleIntegerProperty(minLength); this.maxLength = new SimpleIntegerProperty(maxLength); bind(); } public ReadOnlyBooleanProperty invalidProperty() { return invalid; } public ReadOnlyStringProperty maskProperty() { return mask; } public ReadOnlyIntegerProperty minLengthProperty() { return minLength; } public ReadOnlyIntegerProperty maxLengthProperty() { return maxLength; } public boolean getInvalid() { return invalid.get(); } public String getMask() { return mask.get(); } public void setMask(String mask) { this.mask.set(mask); } public int getMinLength() { return minLength.get(); } public void setMinLength(int minLength) { this.minLength.set(minLength); } public int getMaxLength() { return maxLength.get(); } public void setMaxLength(int maxLength) { this.maxLength.set(maxLength); } public Effect getInvalidEffect() { return this.invalidEffect; } public void setInvalidEffect(Effect effect) { this.invalidEffect = effect; } private void bind() { this.invalid.bind(maskCheck().or(minLengthCheck())); this.textProperty().addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> ov, String t, String t1) { if (textProperty().get().length() > maxLength.get()) { setText(t); } } }); this.invalid.addListener(new ChangeListener<Boolean>() { @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) { if (t ^ t1) { if (t1) {
However, there is a trivial point regarding the βinvalidβ graphic effect. As you can see here:
this.invalid.addListener(new ChangeListener<Boolean>() { @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) { if (t ^ t1) { if (t1) {
I tried using setStyle, but using -fx-font-weight: inherit; breaks the code (not because it should be the default). The implementation of StyleClass does not work, since I cannot return it if false is false.
Any clue? Of course, you can turn off the internal listener and attach another one outside with other effects (fi, showing a green check mark instead of changing the TextField effect).
You can use the code if you don't mind :)