Textfield Javafx with dynamic monetary value mask

Asked

Viewed 1,206 times

1

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

0.09 -> 0.99 -> 9.99-> 99.90

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

  • Use Java 8 or 9? Want to use some currency API or just handle text?

  • I’m using Java 8 and I just want to treat the text (because I’m not familiar with APIS yet)

1 answer

1


I made a small extension for the Javafx Textfield that formats like a monetary field. I’m using Double in this solution but you can change how it suits you:

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 impute and formats to currency using Numberformat from a locale. The method formatText(String) removes everything that is not a number from the text and places a point in two decimal places, filling with zeros on the left if the number is small. Below an example of use:

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());
            }
        });

inserir a descrição da imagem aqui

Maybe there are some bugs I didn’t notice, anything signal to help improve it!

  • That’s exactly what I was looking for! I will study this code to learn more and see if it needs any specific adaptation to my project. If I make any significant changes I will post here as a curiosity. Gustavo Fragoso Thank you so much for sharing your knowledge.

  • A question arose: how can I use the Currencyfield object from an FXML file ? I tested in a FX project without FXML and it worked exactly as I needed it...

  • To put in FXML is required an empty constructor (no arguments). It would be 'this(new Locale("pt","BR"), 0.0); Take a look at how I used FXML in this component: https://github.com/gbfragoso/MaskedTextField

  • Perfect. Thank you

Browser other questions tagged

You are not signed in. Login or sign up in order to post.