Simple basic Calculator Javafx - javafx

First of all it's my first app, I'm trying to code a calculator. When pressing an operator, if there is old one, calculate it and send the result to continue with it to the new process. The calculation process don't go to the second step, anyone can help to make this code work properly?
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class main extends Application {
String num1 ="";
String num2 ="";
String op ;
double result= 0;
boolean oldop =false ;
// the GUI component
public void start(Stage stage) throws Exception {
Button one = new Button("1");
Button two = new Button("2");
Button pls = new Button("+");
Button eql = new Button("=");
Button ac = new Button("AC");
Label lbl = new Label("empty");
FlowPane pane = new FlowPane();
pane.setHgap(10);
pane.getChildren().addAll(one,two,pls,eql,ac,lbl);
Scene scene = new Scene(pane);
stage.setScene(scene);
stage.show();
// The Actions on buttons
one.setOnAction(e ->
{
if(!oldop){
num1+='1';
lbl.setText(num1);}
else {
num2+='1';
lbl.setText(num2);}});
two.setOnAction(e ->
{
if(!oldop){
num1+='2';
lbl.setText(num1);}
else {
num2+='2';
lbl.setText(num2);}});
pls.setOnAction(e -> {
if(!oldop){
oldop = true;
op="+";
lbl.setText(op);}
else {
result=calc(num1 , num2 ,op);
num1=String.valueOf(result);
num2="";
op="+";
lbl.setText(num1+op);
oldop = true;}});
eql.setOnAction(e ->{
if(oldop){
result=calc(num1 , num2 , op);
lbl.setText(String.valueOf(result));
oldop=false;
num2="";}
else
return;});
ac.setOnAction(e -> {
num1="";
num2="";
result=0;
oldop=false;});
}
// The calculation method
public int calc (String n1 , String n2 , String op){
switch (op) {
case "+" :
return Integer.parseInt(n1) + Integer.parseInt(n2) ;
case "-" :
return Integer.parseInt(n1) - Integer.parseInt(n2) ;
case "*" :
return Integer.parseInt(n1) * Integer.parseInt(n2) ;
case "/" :
return Integer.parseInt(n1) / Integer.parseInt(n2) ;
default :
return 0;
}
}
public static void main(String[] args) {
Application.launch(args);
}
}

The problem seems to be that you cannot use the result of an previous operation in the second step, because you use String.valueOf which gives e.g. 3.0 for the int 3 (result of 1+2). This string cannot be used again in calc as it cannot be parsed back to an int with `Integer.parseInt.
I would suggest to work with int and only convert them to strings for the labels.
An ulgy workaround would be to add the following lines at the beginning of calc:
n1=n1.split("\\.")[0];
n2=n2.split("\\.")[0];

Related

How can I change the scene By pressing a specific key(b) on the the keyboard?

In my application, there are two scenes: mainScene and bossScene where mainScene is used when starting up the application.
I'm trying to implement the boss key functionality where by pressing the 'b' key on the the keyboard should change the scene to bossScene. And also by pressing the button in bossScene should switch back to mainScene.
I'm getting an error on InteliJ saying "Cannot resolve method setOnKeyPressed in List
My Code:
public void start(Stage stage) throws Exception {
stage.setTitle("BossKey Example");
// Scene and layout for the main view
VBox root = new VBox();
Scene mainScene = new Scene(root, 500, 300);
// Scene for the BOSS view
Scene bossScene = new Scene(new Label("Nothing suspicious here"), 500, 300);
List<TextField> fields = new ArrayList<TextField>();
for (int i = 0; i < 10; i++) {
fields.add(new TextField());
}
fields.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent keyEvent) {
switch (keyEvent.getCharacter()){
case "b": stage.setScene(bossScene); break;
}
}
});
/////// Added addEventFilter, still not working
mainScene.addEventFilter(KeyEvent.KEY_PRESSED, new
EventHandler<KeyEvent() {
#Override
public void handle(KeyEvent keyEvent) {
switch (keyEvent.getCharacter()){
case "b": stage.setScene(bossScene); break;
}
keyEvent.consume();
}
});
// Create components for main view
root.getChildren().addAll(fields);
root.getChildren().add(new Button("Hello!"));
stage.setScene(mainScene);
stage.show();
}
}
KeyCombination filters
You should use a key combination in an event filter, e.g., CTRL+B or SHORTCUT+B.
For details on how to apply key combinations, see:
javafx keyboard event shortcut key
Why a key combination is superior to filtering on the character "b":
If you filter on a "b" character, the feature won't work if caps lock is down.
If you filter on a "b" character, you will be unable to type "b" in the text field.
You might think you could write scene.setOnKeyPressed(...), however, that won't work as expected in many cases. A filter is required rather than a key press event handler because the key events may be consumed by focused fields like text fields if you use a handler, so a handler implementation might not activate in all desired cases.
Filtering on a key combination avoids the issues with trying to handle a character key press. The key combinations rely on key codes which represent the physical key pressed and don't rely on the state of other keys such as caps lock unless you explicitly add additional logic for that.
If you don't understand the difference between an event filter and an event handler and the capturing and bubbling phases of event dispatch, then study:
the oracle event handling tutorial.
KeyCombination filter implementation
final EventHandler<KeyEvent> bossEventFilter = new EventHandler<>() {
final KeyCombination bossKeyCombo = new KeyCodeCombination(
KeyCode.B,
KeyCombination.CONTROL_DOWN
);
public void handle(KeyEvent e) {
if (bossKeyCombo.match(e)) {
if (stage.getScene() == mainScene) {
stage.setScene(bossScene);
} else if (stage.getScene() == bossScene) {
stage.setScene(mainScene);
}
e.consume();
}
}
};
mainScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
bossScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
Accelerator alternative
An accelerator could be used instead of an event filter. Information on applying an accelerator is also in an answer to the linked question, I won't detail this alternative further here.
Example Solution
Standalone executable example code:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.*;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.io.IOException;
public class SceneSwap extends Application {
#Override
public void start(Stage stage) throws IOException {
final Scene mainScene = new Scene(
createLayout(
"Press CTRL+B to enter boss mode",
Color.PALEGREEN
)
);
final Scene bossScene = new Scene(
createLayout(
"Press CTRL+B to exit boss mode",
Color.PALEGOLDENROD
)
);
final EventHandler<KeyEvent> bossEventFilter = new EventHandler<>() {
final KeyCombination bossKeyCombo = new KeyCodeCombination(
KeyCode.B,
KeyCombination.CONTROL_DOWN
);
public void handle(KeyEvent e) {
if (bossKeyCombo.match(e)) {
if (stage.getScene() == mainScene) {
stage.setScene(bossScene);
} else if (stage.getScene() == bossScene) {
stage.setScene(mainScene);
}
e.consume();
}
}
};
mainScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
bossScene.addEventFilter(KeyEvent.KEY_PRESSED, bossEventFilter);
stage.setScene(mainScene);
stage.show();
}
private VBox createLayout(String text, Color color) {
VBox mainLayout = new VBox(10,
new Label(text),
new TextField()
);
mainLayout.setPadding(new Insets(10));
mainLayout.setStyle("-fx-background: " + toCssColor(color));
return mainLayout;
}
private String toCssColor(Color color) {
int r = (int) Math.round(color.getRed() * 255.0);
int g = (int) Math.round(color.getGreen() * 255.0);
int b = (int) Math.round(color.getBlue() * 255.0);
int o = (int) Math.round(color.getOpacity() * 255.0);
return String.format("#%02x%02x%02x%02x" , r, g, b, o);
}
public static void main(String[] args) {
launch();
}
}

JavaFX: How to scroll to a specific line in the textarea

package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;
import java.util.stream.IntStream;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
final StringBuilder sb = new StringBuilder();
IntStream.range(1, 100).forEach(i -> sb.append("Line " + i + "\n"));
TextArea ta = new TextArea();
ta.setText(sb.toString());
//how to I get line 30 at top of the visible textarea
double someValue = 0;
ta.setScrollTop(someValue);
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(ta, 300, 300));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
How do I get line 30 at top of the visible textarea?
I think 'someValue' should be relative to the total height which can be scrolled
But what is the total height that can be scrolled
This is the result I want to achieve:
This is a bit tricky. We could just determine each line height and call ta.setScrollTop((line - 1) * lineHeight);, but we do not know what line spacing TextArea uses.
But I found that TextAreaSkin contains public methods for determining bounds for any selected character, we just need to know its index.
#Override
public void start(Stage primaryStage) throws Exception{
final StringBuilder sb = new StringBuilder();
IntStream.range(1, 100).forEach(i -> sb.append("Line " + i + "\n"));
TextArea ta = new TextArea();
ta.setText(sb.toString());
// TextArea did not setup its skin yet, so we can't use it right now.
// We just append our task to the user tasks queue.
Platform.runLater(() -> {
// Define desired line
final int line = 30;
// Index of the first character in line that we look for.
int index = 0;
// for this example following line will work:
// int index = ta.getText().indexOf("Line " + line);
// for lines that do not contain its index we rely on "\n" count
int linesEncountered = 0;
boolean lineFound = false;
for (int i = 0; i < ta.getText().length(); i++) {
// count characters on our way to our desired line
index++;
if(ta.getText().charAt(i) == '\n') {
// next line char encountered
linesEncountered++;
if(linesEncountered == line-1) {
// next line is what we're looking for, stop now
lineFound = true;
break;
}
}
}
// scroll only if line found
if(lineFound) {
// Get bounds of the first character in the line using internal API (see comment below the code)
Rectangle2D lineBounds = ((com.sun.javafx.scene.control.skin.TextAreaSkin) ta.getSkin()).getCharacterBounds(index);
// Scroll to the top-Y of our line
ta.setScrollTop(lineBounds.getMinY());
}
});
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(ta, 300, 300));
primaryStage.show();
}
This solution works on Java 8, on 9+ TextAreaSkin was moved to the public package, so everything you need to make it work is to replace com.sun.javafx.scene.control.skin.TextAreaSkin with javafx.scene.control.skin.TextAreaSkin

JavaFX : How to control input in a TextField ?

I try to do an application with JavaFX.
I have a little question :
I have a TextField and i would like it will be a TextField for "phone number" (0477/40.00.09 for example).
Unfortunatly, i can't do this.
I would like that '/' and '.' are always write in the TextField.
I also would like when the user has wrote the fourth first numbers, the TextField set the cursor behind the '/'. So the user don't take care for '/' and '.'. He has just to type the number of the phone number.
How can i do this ?
Thanks for your help,
Mikis
(Sorry for my English ....)
You can use a TextFormatter to control the text inside a TextField.
PhoneNumber TextField described in the question should do the following:
Add / after 4 characters
Add . after 7 and 10 characters
Doesn't allow more than 13 characters
Here is a sample which does all of the above:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TextFieldFormatter extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
final TextField textField = new TextField();
textField.setTextFormatter(new TextFormatter<String>(change -> {
final int oldLength = change.getControlText().length();
int newLength = change.getControlNewText().length();
// Handle backspace
if (newLength < oldLength) return change;
// Add / after 4 characters
// Add . after 7 and 10 characters
// Do not accept more than 13 characters
switch (newLength) {
case 4 :
change.setText(change.getText() + "/");
newLength++;
break;
case 7: case 10:
change.setText(change.getText() + ".");
newLength++;
break;
case 14:
return null;
}
// Set caret position
change.setCaretPosition(newLength);
change.setAnchor(newLength);
return change;
}));
BorderPane root = new BorderPane();
root.setCenter(textField);
Scene scene = new Scene(root, 200, 200);
stage.setScene(scene);
stage.setTitle("TextField Example");
stage.show();
}
}
You can addListener() to TextField propertyText and control the inputs.In your question you did not put any condition so just you need a useful implementation for that you can use this simple code :
TextField field = new TextField();
field.setPromptText("xxxx/xx.xx.xx");
int maxDigits = 13;
field.textProperty().addListener((ObservableValue<? extends String> observable, String oldValue, String newValue) -> {
if (newValue.length() == 4 && oldValue.length() != 5) {
field.setText(newValue + "/");
} else if (newValue.length() == 7 && oldValue.length() != 8 || newValue.length() == 10 && oldValue.length() != 13) {
field.setText(newValue + ".");
}
if (newValue.length() > maxDigits) {
field.setText(oldValue);
}
});
Finaly, i found what i was searching in this project :
https://github.com/jidesoft/jidefx-oss
But i adapted it to my needs. Do I put the code for the new object "MyMaskTextField" ?

UndoFX: Undo/Redo recording every pixel of drag

I'm using UndoFX & ReactFX for implementing the Undo/Redo function for my 2D shape application.
The problem is when i move my shape the EventStream records every X/Y pixel of movement. I just want to record the last position (when the user releases the drag).
What i have tried so far:
Instead of using changesOf(rect.xProperty()).map(c -> new xChange(c)); and
changesOf(rect.yProperty()).map(c -> new yChange(c));
I created a DoubleProperty x,y, and saved the shape x,y Property to these variables when the user mouse is released.
Lastly i change the changesOf to: changesOf(this.x).map(c -> new xChange(c)); and changesOf(this.y).map(c -> new yChange(c));
But that did not work, it behaved just like before.
....
private class xChange extends RectangleChange<Double> {
public xChange(Double oldValue, Double newValue) {
super(oldValue, newValue);
}
public xChange(Change<Number> c) {
super(c.getOldValue().doubleValue(), c.getNewValue().doubleValue());
}
#Override void redo() { rect.setX(newValue); }
#Override xChange invert() { return new xChange(newValue, oldValue); }
#Override Optional<RectangleChange<?>> mergeWith(RectangleChange<?> other) {
if(other instanceof xChange) {
return Optional.of(new xChange(oldValue, ((xChange) other).newValue));
} else {
return Optional.empty();
}
}
#Override
public boolean equals(Object other) {
if(other instanceof xChange) {
xChange that = (xChange) other;
return Objects.equals(this.oldValue, that.oldValue)
&& Objects.equals(this.newValue, that.newValue);
} else {
return false;
}
}
}
...
EventStream<xChange> xChanges = changesOf(rect.xProperty()).map(c -> new xChange(c));
EventStream<yChange> yChanges = changesOf(rect.yProperty()).map(c -> new yChange(c));
changes = merge(widthChanges, heightChanges, xChanges, yChanges);
undoManager = UndoManagerFactory.unlimitedHistoryUndoManager(
changes, // stream of changes to observe
c -> c.invert(), // function to invert a change
c -> c.redo(), // function to undo a change
(c1, c2) -> c1.mergeWith(c2)); // function to merge two changes
You need to merge the changes in x with the changes in y. At present, a change in x followed by a change in y cannot be merged, so if you move the shape so that it alternates x and y changes (e.g. moving it diagonally), then each individual change will not merge with the previous one.
One way to do this is to generate changes whose old and new values are the locations, e.g. represented by Point2D objects. Here's a quick example:
import java.util.Objects;
import java.util.Optional;
import org.fxmisc.undo.UndoManager;
import org.fxmisc.undo.UndoManagerFactory;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import org.reactfx.SuspendableEventStream;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class UndoRectangle extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle rect = new Rectangle(50, 50, 150, 100);
rect.setFill(Color.CORNFLOWERBLUE);
EventStream<PositionChange> xChanges = EventStreams.changesOf(rect.xProperty()).map(c -> {
double oldX = c.getOldValue().doubleValue();
double newX = c.getNewValue().doubleValue();
double y = rect.getY();
return new PositionChange(new Point2D(oldX, y), new Point2D(newX, y));
});
EventStream<PositionChange> yChanges = EventStreams.changesOf(rect.yProperty()).map(c -> {
double oldY = c.getOldValue().doubleValue();
double newY = c.getNewValue().doubleValue();
double x = rect.getX();
return new PositionChange(new Point2D(x, oldY), new Point2D(x, newY));
});
SuspendableEventStream<PositionChange> posChanges = EventStreams.merge(xChanges, yChanges)
.reducible(PositionChange::merge);
UndoManager undoManager = UndoManagerFactory.unlimitedHistoryUndoManager(posChanges,
PositionChange::invert,
c -> posChanges.suspendWhile(() -> {
rect.setX(c.getNewPosition().getX());
rect.setY(c.getNewPosition().getY());
}),
(c1, c2) -> Optional.of(c1.merge(c2))
);
class MouseLoc { double x, y ; }
MouseLoc mouseLoc = new MouseLoc();
rect.setOnMousePressed(e -> {
mouseLoc.x = e.getSceneX();
mouseLoc.y = e.getSceneY();
});
rect.setOnMouseDragged(e -> {
rect.setX(rect.getX() + e.getSceneX() - mouseLoc.x);
rect.setY(rect.getY() + e.getSceneY() - mouseLoc.y);
mouseLoc.x = e.getSceneX();
mouseLoc.y = e.getSceneY();
});
rect.setOnMouseReleased(e -> undoManager.preventMerge());
Pane pane = new Pane(rect);
Button undo = new Button("Undo");
undo.disableProperty().bind(Bindings.not(undoManager.undoAvailableProperty()));
undo.setOnAction(e -> undoManager.undo());
Button redo = new Button("Redo");
redo.disableProperty().bind(Bindings.not(undoManager.redoAvailableProperty()));
redo.setOnAction(e -> undoManager.redo());
HBox buttons = new HBox(5, undo, redo);
buttons.setAlignment(Pos.CENTER);
BorderPane.setMargin(buttons, new Insets(5));
BorderPane root = new BorderPane(pane, null, null, buttons, null);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class PositionChange {
private final Point2D oldPosition ;
private final Point2D newPosition ;
public PositionChange(Point2D oldPos, Point2D newPos) {
this.oldPosition = oldPos ;
this.newPosition = newPos ;
}
public Point2D getOldPosition() {
return oldPosition;
}
public Point2D getNewPosition() {
return newPosition;
}
public PositionChange merge(PositionChange other) {
return new PositionChange(oldPosition, other.newPosition);
}
public PositionChange invert() {
return new PositionChange(newPosition, oldPosition);
}
#Override
public boolean equals(Object o) {
if (o instanceof PositionChange) {
PositionChange other = (PositionChange) o ;
return Objects.equals(oldPosition, other.oldPosition)
&& Objects.equals(newPosition, other.newPosition);
} else return false ;
}
#Override
public int hashCode() {
return Objects.hash(oldPosition, newPosition);
}
}
public static void main(String[] args) {
launch(args);
}
}
Note that it's important the "undo" is implemented as an "atomic" change, so the undo manager sees (and ignores) a single change when you implement the undo. This can be achieved by suspending the event stream during the undo.

How to limit the amount of characters a javafx textfield

I´m using a FXML to set my form, but I need to set the limit of characters in textfields. How can I made this ?
You can't directly set a limit to number of characters. But you can add a listener to lengthProperty() of the textfield
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TextFieldLimit extends Application {
private static final int LIMIT = 10;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(final Stage primaryStage) {
final TextField textField = new TextField();
textField.lengthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
if (newValue.intValue() > oldValue.intValue()) {
// Check if the new character is greater than LIMIT
if (textField.getText().length() >= LIMIT) {
// if it's 11th character then just setText to previous
// one
textField.setText(textField.getText().substring(0, LIMIT));
}
}
}
});
VBox vbox = new VBox(20);
vbox.getChildren().add(textField);
Scene scene = new Scene(vbox, 400, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
}
One more elegance solution
Pattern pattern = Pattern.compile(".{0,25}");
TextFormatter formatter = new TextFormatter((UnaryOperator<TextFormatter.Change>) change -> {
return pattern.matcher(change.getControlNewText()).matches() ? change : null;
});
textField.setTextFormatter(formatter);
where 0 and 25 - min and max amount of chars. + ability to set a pattern of input text
Here is my solution to limit the length of a textfield.
I would not recommend solutions which use a listener (on text property or on length property), they do not behave correctly in all situations (for what I have seen).
I create an HTML input text with a max length, and compare it to my textfield in JavaFX. I had the same behavior with paste operations (Ctrl + V), cancel operations (Ctrl + Z) in both cases. The goal here is to check if the text is valid BEFORE modifying the textfield.
We could use a similar approach for a numeric text field.
import java.util.Objects;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.control.TextField;
public class LimitedTextField extends TextField {
private final IntegerProperty maxLength;
public LimitedTextField() {
super();
this.maxLength = new SimpleIntegerProperty(-1);
}
public IntegerProperty maxLengthProperty() {
return this.maxLength;
}
public final Integer getMaxLength() {
return this.maxLength.getValue();
}
public final void setMaxLength(Integer maxLength) {
Objects.requireNonNull(maxLength, "Max length cannot be null, -1 for no limit");
this.maxLength.setValue(maxLength);
}
#Override
public void replaceText(int start, int end, String insertedText) {
if (this.getMaxLength() <= 0) {
// Default behavior, in case of no max length
super.replaceText(start, end, insertedText);
}
else {
// Get the text in the textfield, before the user enters something
String currentText = this.getText() == null ? "" : this.getText();
// Compute the text that should normally be in the textfield now
String finalText = currentText.substring(0, start) + insertedText + currentText.substring(end);
// If the max length is not excedeed
int numberOfexceedingCharacters = finalText.length() - this.getMaxLength();
if (numberOfexceedingCharacters <= 0) {
// Normal behavior
super.replaceText(start, end, insertedText);
}
else {
// Otherwise, cut the the text that was going to be inserted
String cutInsertedText = insertedText.substring(
0,
insertedText.length() - numberOfexceedingCharacters
);
// And replace this text
super.replaceText(start, end, cutInsertedText);
}
}
}
}
Tested with JavaFX 8 and Java 8u45
This is a very simple solution that seems to work for me.
textfield.setOnKeyTyped(event ->{
int maxCharacters = 5;
if(tfInput.getText().length() > maxCharacters) event.consume();
});
I use a simple call to ChangeListener, where I test the condition to perform stops.
textFild.addListener((observable, oldValue, newValue) -> {
if (newValue.length() == MAX_SIZE) {
textField.setText(oldValue);
}
});
This is a better way to do the job on a generic text field:
public static void addTextLimiter(final TextField tf, final int maxLength) {
tf.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(final ObservableValue<? extends String> ov, final String oldValue, final String newValue) {
if (tf.getText().length() > maxLength) {
String s = tf.getText().substring(0, maxLength);
tf.setText(s);
}
}
});
}
Works perfectly, except for that Undo bug.
the following 1-liner will exactly do it, wheras 5 is the limit of the TextField tf:
tf.setTextFormatter(new TextFormatter<>(c -> c.getControlNewText().matches(".{0,5}") ? c : null));
This is a solution that works well:
#FXML
void limitTextFields(KeyEvent event) {
int maxLength = 5;
TextField tf = (TextField) event.getSource();
if (tf.getText().length() > maxLength) {
tf.deletePreviousChar();
}
}

Resources