Textfield JavaFX with dynamic mask for monetary values

1

I'm trying to create a textfield java FX for a financial application. I would like this texfield to follow the same pattern as the numerical fields found in electronic banking boxes. For example: in ATM the initial value in the value field is "0.00". When the user types the value he wants to extract, for example, typing starts from right to left, replacing the leading zeros ... For example, I want to remove $ 99.90 (user type the 9 key three times and the 0 key once) and it happens this:

  

0.09 - > 0.99 - > 9.99- > 99.90

Does anyone have an idea how to create this mask? I have seen several answers on similar topics but I have not been able to adapt any of them to my project (maybe because I'm a beginner in Java and I'm still learning about the String class, textfield methods, etc.)

    
asked by anonymous 12.04.2018 / 04:57

1 answer

1

I made a small extension to the JavaFX TextField that formats it as if it were a money field. I'm using Double in this solution but you can change it as you see fit:

import java.text.NumberFormat;
import java.util.Locale;

import javafx.application.Platform;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.NodeOrientation;
import javafx.scene.control.TextField;

/**
 * Simple Currency Field for JavaFX
 * @author Gustavo
 * @version 1.0
 */
public class CurrencyField extends TextField{

    private NumberFormat format;
    private SimpleDoubleProperty amount;

    public CurrencyField(Locale locale) {
        this(locale, 0.00);
    }

    public CurrencyField(Locale locale, Double initialAmount) {
        setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
        amount = new SimpleDoubleProperty(this, "amount", initialAmount);
        format = NumberFormat.getCurrencyInstance(locale);
        setText(format.format(initialAmount));

        // Remove selection when textfield gets focus
        focusedProperty().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
            Platform.runLater(() -> {
                int lenght = getText().length();
                selectRange(lenght, lenght);
                positionCaret(lenght);
            });
        });

        // Listen the text's changes
        textProperty().addListener(new ChangeListener<String>() {

            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                formatText(newValue);
            }
        });
    }

    /**
     * Get the current amount value
     * @return Total amount
     */
    public Double getAmount() {
        return amount.get();
    }

    /**
     * Property getter
     * @return SimpleDoubleProperty
     */
    public SimpleDoubleProperty amountProperty() {
        return this.amount;
    }

    /**
     * Change the current amount value
     * @param newAmount
     */
    public void setAmount(Double newAmount) {
        if(newAmount >= 0.0) {
            amount.set(newAmount);
            formatText(format.format(newAmount));
        }
    }

    /**
     * Set Currency format
     * @param locale
     */
    public void setCurrencyFormat(Locale locale) {
        format = NumberFormat.getCurrencyInstance(locale);
        formatText(format.format(getAmount()));
    }

    private void formatText(String text) {
        if(text != null && !text.isEmpty()) {
            String plainText = text.replaceAll("[^0-9]", "");

            while(plainText.length() < 3) {
                plainText = "0" + plainText;
            }

            StringBuilder builder = new StringBuilder(plainText);
            builder.insert(plainText.length() - 2, ".");

            Double newValue = Double.parseDouble(builder.toString());
            amount.set(newValue);
            setText(format.format(newValue));
        }
    }

    @Override
    public void deleteText(int start, int end) {
        StringBuilder builder = new StringBuilder(getText());
        builder.delete(start, end);
        formatText(builder.toString());
        selectRange(start, start);
    }

}

Basically it takes the impute and formats it for currency using the NumberFormat from a locale. The formatText(String) method removes everything that is not a text number and places a period in two decimal places, filling with leading zeros if the number is small. Here is an example of usage:

CurrencyField cur = new CurrencyField(new Locale("pt","BR"));

// Usando esta property você pode ver as mudanças no valor do textfield
cur.amountProperty().addListener(new ChangeListener<Number>() {

            @Override
            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
                System.out.println(newValue.doubleValue());
            }
        });

MaybeIhavesomebugsIdidnotnotice, anything to signal to help improve- lo !

    
08.05.2018 / 02:43