2

I created a simple javafx program. I want to seprate the digits three by three when I entering the digits in the textfiled. I used two solution which are given in the stackoverflow links(How to format text of TextField? JavaFX , Java 8 U40 TextFormatter (JavaFX) to restrict user input only for decimal number)

but none them are working for me. the first solution(set textformatter) was useless for me(or maybe I couldn't work with it in a right way) but the second one was working but only accept 4 digits and the other numbers that I enterd in the textfield are the in the same style that I enterd them without comma.

I want to seprate every three digits like this: 12,564,546,554 if anyone know the solution please help me to overcome this problem. thanks.

 import javafx.application.Application;
 import javafx.scene.Scene;
 import javafx.scene.control.TextField;
 import javafx.scene.control.TextFormatter;
 import javafx.scene.layout.HBox;
 import javafx.stage.Stage;

 import java.text.DecimalFormat;
 import java.text.ParsePosition;


 public class DelimiterExample extends Application{


public static void main(String[] args) {
    launch(args);
}


@Override
public void start(Stage primaryStage) throws Exception {

    TextField textField = new TextField();
    HBox hBox = new HBox();

    //solution one

    DecimalFormat format = new DecimalFormat( "#,###" );

    textField.setTextFormatter( new TextFormatter<>(c ->
    {
        if ( c.getControlNewText().isEmpty() )
        {
            return c;
        }

        ParsePosition parsePosition = new ParsePosition( 0 );
        Object object = format.parse( c.getControlNewText(), parsePosition );

        if ( object == null || parsePosition.getIndex() < c.getControlNewText().length() )
        {
            return null;
        }
        else
        {

            return c;
        }

    }));


    // solution two

    textField.textProperty().addListener((obs , oldVal , newVal)-> {

        if (newVal.matches("\\d*")) {
            DecimalFormat formatter = new DecimalFormat("#,###");
            String newvalstr = formatter.format(Float.parseFloat(newVal));
            //System.out.println(newvalstr);
            textField.setText(newvalstr);
        }


    });

    hBox.getChildren().add(textField);
    Scene scene = new Scene(hBox , 100 , 100);
    primaryStage.setScene(scene);
    primaryStage.show();
}
}
Sameer
  • 75
  • 9
  • You want the numbers between the commas, right? Have you tried [`String.split(String)`](https://docs.oracle.com/javase/10/docs/api/java/lang/String.html#split(java.lang.String)) using `","` as the argument? – Slaw Jun 05 '18 at 19:06
  • No. I want to format the number when I entering it in the textfield like this:145,636,826.I don't want the numbers between commas as you said. I want to put commas between digits to format them like currency. I don't have any commas for the first time. Split function is not working for my goal. – Sameer Jun 05 '18 at 19:43
  • Ah, sorry, I misunderstood. – Slaw Jun 05 '18 at 20:28
  • It's ok.Thanks for your reply. – Sameer Jun 05 '18 at 20:40

3 Answers3

3

Any modification that is not a selection change can be fixed by modifying the chars before the range:

final char seperatorChar = ',';
final Pattern p = Pattern.compile("[0-9" + seperatorChar + "]*");
textField.setTextFormatter(new TextFormatter<>(c -> {
    if (!c.isContentChange()) {
        return c; // no need for modification, if only the selection changes
    }
    String newText = c.getControlNewText();
    if (newText.isEmpty()) {
        return c;
    }
    if (!p.matcher(newText).matches()) {
        return null; // invalid change
    }

    // invert everything before the range
    int suffixCount = c.getControlText().length() - c.getRangeEnd();
    int digits = suffixCount - suffixCount / 4;
    StringBuilder sb = new StringBuilder();

    // insert seperator just before caret, if necessary
    if (digits % 3 == 0 && digits > 0 && suffixCount % 4 != 0) {
        sb.append(seperatorChar);
    }

    // add the rest of the digits in reversed order
    for (int i = c.getRangeStart() + c.getText().length() - 1; i >= 0; i--) {
        char letter = newText.charAt(i);
        if (Character.isDigit(letter)) {
            sb.append(letter);
            digits++;
            if (digits % 3 == 0) {
                sb.append(seperatorChar);
            }
        }
    }

    // remove seperator char, if added as last char
    if (digits % 3 == 0) {
        sb.deleteCharAt(sb.length() - 1);
    }
    sb.reverse();
    int length = sb.length();

    // replace with modified text
    c.setRange(0, c.getRangeEnd());
    c.setText(sb.toString());
    c.setCaretPosition(length);
    c.setAnchor(length);

    return c;
}));
fabian
  • 80,457
  • 12
  • 86
  • 114
  • 1
    Perfect.It works like a charm. It is exactly the thing that I want.Thank you so much for your help. – Sameer Jun 06 '18 at 07:15
3

Please guys don't reinvent the wheel. The class javafx.util.converter.NumberStringConverter do all the needed work for us:

TextField numberField = new TextField();
TextFormatter<Number> textFormatter = new TextFormatter<>(new NumberStringConverter());
numberField.setTextFormatter(textFormatter);

NumberStringConverter is your friend ;), if you want to listen the correct number changes on the textfield, simply use the next textFormatter property (forgive direct method invocations over the textField object):

textFormatter.valueProperty(); //Returns ObjectProperty<Number>

Then you can retrieve any kind of number subclass (number.intValue(), number.floatValue(), etc), and the ChangeListener only will be triggered when the user writes a valid numeric value.

If you want to manually set a number value to the textfield:

float value = 1000f; //or any type of Number subclass
textFormatter.setValue(value);

P.D: NumberStringConverter has a Locale attribute that can be changed to apply the appropriate format of the desired Country.

GaRzY
  • 65
  • 1
  • 4
1

Thanks again @fabian.I stucked in this problem for two days. I also found that I can have the currency style that I want with this code.Now I have two perfect solution.

      textField.setOnKeyTyped(event -> {
        String typedCharacter = event.getCharacter();
        event.consume();

        if (typedCharacter.matches("\\d*")) {
            String currentText = 
            textField.getText().replaceAll("\\.","").replace(",", "");
            long longVal = Long.parseLong(currentText.concat(typedCharacter));
            textField.setText(new DecimalFormat("#,##0").format(longVal));
        }
    });
Sameer
  • 75
  • 9
  • 1
    I'll never understand why often devs are so reluctant to use the highest abstraction available ... and create brittle apps ;) Your approach will fail if the text is entered by pasting or dragging text into the field, so it's incomplete. TextFormatter as suggested by [Fabian](https://stackoverflow.com/a/50710146/203657) is the recommended solution (available since fx8u40) – kleopatra Aug 04 '19 at 11:07