I would like to know if there is anyway to make a JformattedTextField or jtextField behave like an atm money input. With that I mean you enter from the right to left, say you enter 10 you need to press 2 more 0's so that it will be 10.00 . The program enters the decimal point automatically as he types from right to left? If the 2 0's are not entered it will just be .10 . Is this possible? How would that be returned to me if I want to use that string to do calculations on then? I tried the abstract formatter but this doesn't work so nicely. I want to use this for input for the amount of money received by a customer. But make it idiot proof.
-
2I think you can attach listener to the textfield and do whatever functionality you would like to. – kosa Jun 12 '13 at 20:31
4 Answers
This forces the user to always enter text from the right no matter where the caret is positioned. All previous characters are shifted left as a new character is inserted. Formatting will be applied based on your formatter:
import java.awt.*;
import java.text.*;
import javax.swing.*;
import javax.swing.text.*;
public class ABMTextField extends JTextField
{
private DecimalFormat format;
private String decimal;
public ABMTextField(DecimalFormat format)
{
this.format = format;
decimal = Character.toString( format.getDecimalFormatSymbols().getDecimalSeparator() );
setColumns( format.toPattern().length() );
setHorizontalAlignment(JFormattedTextField.TRAILING);
setText( format.format(0.0) );
AbstractDocument doc = (AbstractDocument)getDocument();
doc.setDocumentFilter( new ABMFilter() );
}
@Override
public void setText(String text)
{
Number number = format.parse(text, new ParsePosition(0));
if (number != null)
super.setText( text );
}
public class ABMFilter extends DocumentFilter
{
public void insertString(FilterBypass fb, int offs, String str, AttributeSet a)
throws BadLocationException
{
replace(fb, offs, 0, str, a);
}
public void replace(FilterBypass fb, int offs, int length, String str, AttributeSet a)
throws BadLocationException
{
if (".0123456789".contains(str))
{
Document doc = fb.getDocument();
StringBuilder sb = new StringBuilder( doc.getText(0, doc.getLength()) );
int decimalOffset = sb.indexOf( decimal );
if (decimalOffset != -1)
{
sb.deleteCharAt(decimalOffset);
sb.insert(decimalOffset + 1, decimal);
}
sb.append(str);
try
{
String text = format.format( format.parse( sb.toString() ) );
super.replace(fb, 0, doc.getLength(), text, a);
}
catch(ParseException e) {}
}
else
Toolkit.getDefaultToolkit().beep();
}
public void remove(DocumentFilter.FilterBypass fb, int offset, int length)
throws BadLocationException
{
Document doc = fb.getDocument();
StringBuilder sb = new StringBuilder( doc.getText(0, doc.getLength()) );
int decimalOffset = sb.indexOf( decimal );
if (decimalOffset != -1)
{
sb.deleteCharAt(decimalOffset);
sb.insert(decimalOffset - 1, decimal);
}
sb.deleteCharAt( sb.length() - 1) ;
try
{
String text = format.format( format.parse( sb.toString() ) );
super.replace(fb, 0, doc.getLength(), text, null);
}
catch(ParseException e) {}
}
}
private static void createAndShowUI()
{
DecimalFormat format = new DecimalFormat("###,##0.00");
ABMTextField abm = new ABMTextField( format );
JPanel panel = new JPanel();
panel.add( abm );
JFrame frame = new JFrame("ABMTextField");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( panel );
frame.setSize(200, 200);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
How would that be returned to me if I want to use that string to do calculations on then?
You would need to create a method, maybe getValue() that would use the format.parse(...) method to return an actual number.

- 321,443
- 19
- 166
- 288
Take a look at How to use Formatted Text Fields, in particular Using MaskFormatter.
Something like...
MaskFormatter formatter = new MaskFormatter("##.##");
JFormattedTextField field = JFormattedTextField(formatter);
for example may help.
Simple example
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.HeadlessException;
import java.text.ParseException;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.MaskFormatter;
public class TestFormattedTextField {
public static void main(String[] args) {
new TestFormattedTextField();
}
public TestFormattedTextField() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
try {
JFormattedTextField field = new JFormattedTextField();
MaskFormatter formatter = new MaskFormatter("##.##");
formatter.setPlaceholderCharacter('0');
field.setFormatterFactory(new DefaultFormatterFactory(formatter));
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
frame.add(field);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (ParseException exp) {
exp.printStackTrace();
}
}
});
}
}
Additional Example
Now, I realise that the previous example doesn't meet your exact needs (as you described them), it is a simple solution, I've also added a DocumentFilter
example...
Which will output...
Value = 0.1
Value = $0.10
Which will output
Value = 10.0
Value = $10.00
Code...
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
import java.text.ParseException;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.text.MaskFormatter;
public class TestFormattedTextField {
public static void main(String[] args) {
new TestFormattedTextField();
}
public TestFormattedTextField() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
MoneyField field = new MoneyField();
field.addActionListener(new ActionListener() {
@Override
@SuppressWarnings("empty-statement")
public void actionPerformed(ActionEvent e) {
MoneyField field = (MoneyField) e.getSource();
double value = field.getValue();
System.out.println("Value = " + value);
System.out.println("Value = " + NumberFormat.getCurrencyInstance().format(value));
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
frame.add(field);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MoneyField extends JTextField {
public MoneyField() {
setColumns(5);
setHorizontalAlignment(RIGHT);
((AbstractDocument) getDocument()).setDocumentFilter(new Filter());
}
public double getValue() {
String text = getText();
if (!text.contains(".")) {
text = "0." + text;
}
return Double.parseDouble(text);
}
protected class Filter extends DocumentFilter {
protected String getNumbers(String text) {
StringBuilder sb = new StringBuilder(text.length());
for (char c : text.toCharArray()) {
if (Character.isDigit(c)) {
sb.append(c);
}
}
return sb.toString();
}
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
if (length > 0) {
fb.remove(offset, length);
}
insertString(fb, offset, text, attrs);
}
@Override
public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException {
text = getNumbers(text);
if (text.length() > 0) {
int docLength = fb.getDocument().getLength();
if (docLength == 2) {
text = "." + text;
}
if (docLength + text.length() < 6) {
super.insertString(fb, offset, text, attr);
}
}
}
@Override
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
if (offset == 3) {
offset = 2;
length = 2;
}
super.remove(fb, offset, length);
}
}
}
}
Check out DocumentFilter examples for more details

- 343,457
- 22
- 230
- 366
-
+1, I see we were both working on a DocumentFilter approach which I think is the way to go. – camickr Jun 13 '13 at 02:00
-
@camickr I Still think the `JFormattedTextField` is simpler approach, but the `DocumentFilter` will more closely meet the OP requirements... – MadProgrammer Jun 13 '13 at 09:34
Use DocumentFilter
for the JTextField and override the appropriate method to handle the number formatting. Also it will be nice if you can post what you have tried and 'doesn't work'.

- 4,192
- 3
- 26
- 45
-
`DocumentListener` isn't an appropriate choice for "filtering" input into the document. Changes have already being made to the document by the time `DocumentListener` is called and any modifications within the listener will cause an exception to be thrown – MadProgrammer Jun 13 '13 at 02:04
-
@MadProgrammer - You are right. I was referring to DocumentFilter and typed DocumentListener :-) – Mubin Jun 13 '13 at 08:49
-
Don't you love it when your fingers do one thing and brain does another :P – MadProgrammer Jun 13 '13 at 09:36
-
I have tried to create a regex for it . I have tried the making an input mask, the input mask work until the number becomes bigger than 99.99 . With the Regular expression using the matcher I have found it was way to buggy when it had to check on the key pressed. – LL. Jun 13 '13 at 18:44
I am a few years late to replying to this question, however, I would like to suggest an improvement over the code written by camickr. His code is really good, but I have modified it a bit to have 3 additional methods:
setLong() - sets a value programmatically to the text field;
getLong() - returns a value programmatically from the text field;
clearLong() - programmatically sets value to 0.00;
JCurrencyField class:
import java.awt.Toolkit;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import javax.swing.JFormattedTextField;
import javax.swing.JTextField;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.DocumentFilter;
/**
* @apiNote This currency field class is limited by long's capabilities. It is
* best to use BigDecimals when dealing with money values due to higher
* precision and longer maximal value
*/
public class JCurrencyField extends JTextField {
// ----------------------- CLASS RELATED LOGIC
private static final long serialVersionUID = 1L;
private DecimalFormat format = new DecimalFormat("###,##0.00");
private String decimal;
/**
* This class is responsible for accepting long value input. It has an ATM like
* behavior.
*
* @apiNote
* <h1>WARNING:</h1> Do not use {@link #setText(String)}.<br>
* <br>
* Use:
* <ul>
* <li>{@link #setLong(long)};</li>
* <li>{@link #clearLong()};</li>
* <li>{@link #getLong()}</li>
* </ul>
* methods instead.
*/
public JCurrencyField() {
decimal = Character.toString(format.getDecimalFormatSymbols().getDecimalSeparator());
setColumns(format.toPattern().length());
setHorizontalAlignment(JFormattedTextField.TRAILING);
setText(format.format(0.00));
AbstractDocument doc = (AbstractDocument) getDocument();
doc.setDocumentFilter(new ATMFilter());
}
/**
* This class is responsible for accepting long value input. It has an ATM like
* behavior.
*
* @apiNote
* <h1>WARNING:</h1> Do not use {@link #setText(String)}.<br>
* <br>
* Use:
* <ul>
* <li>{@link #setLong(long)};</li>
* <li>{@link #clearLong()};</li>
* <li>{@link #getLong()}</li>
* </ul>
* methods instead.
*
* @param value the value
*/
public JCurrencyField(long value) {
decimal = Character.toString(format.getDecimalFormatSymbols().getDecimalSeparator());
setColumns(format.toPattern().length());
setHorizontalAlignment(JFormattedTextField.TRAILING);
setText(format.format(0.00));
AbstractDocument doc = (AbstractDocument) getDocument();
doc.setDocumentFilter(new ATMFilter());
setLong(value);
}
/**
* @apiNote This method is overriden, it sets only one char/letter at a time.
*
* @apiNote DO NOT USE THIS METHOD TO SET A VALUE TO THIS JCurrencyField. Use
* {@link #setLong(long)}; {@link #clearLong()}; {@link #getLong()}
* methods instead.
* @param text the new text
*/
@Override
public void setText(String text) {
// Delete value
if (text.equals("")) {
super.setText(text);
}
// Add 1 char of number
Number number = format.parse(text, new ParsePosition(0));
if (number != null)
super.setText(text);
}
/**
* This class changes the behavior of {@link JTextField}.
* <ul>
* <li>When method {@link JTextField#setText(String)} is called it will append
* only one char/letter at the end of the value</li>
* <li>When backspace/delete is pressed, only the last char/letter at the of the
* value will be deleted</li>
* </ul>
*/
public class ATMFilter extends DocumentFilter {
/**
* Inserts a string.
*
* @param fb the fb
* @param offs the offs
* @param str the str
* @param a the a
* @throws BadLocationException the bad location exception
*/
public void insertString(FilterBypass fb, int offs, String str, AttributeSet a) throws BadLocationException {
replace(fb, offs, 0, str, a);
}
/**
* Adds one char at the right end of this component.
*
* @param fb the fb
* @param offs the offs
* @param length the length
* @param str the str
* @param a the a
* @throws BadLocationException the bad location exception
*/
public void replace(FilterBypass fb, int offs, int length, String str, AttributeSet a)
throws BadLocationException {
Document doc = fb.getDocument();
StringBuilder sb = new StringBuilder(doc.getText(0, doc.getLength()));
// Dealing with single char input
if (".0123456789".contains(str)) {
int decimalOffset = sb.indexOf(decimal);
if (decimalOffset != -1) {
sb.deleteCharAt(decimalOffset);
sb.insert(decimalOffset + 1, decimal);
}
sb.append(str);
try {
String text = format.format(format.parse(sb.toString()));
super.replace(fb, 0, doc.getLength(), text, a);
} catch (ParseException e) {
}
} else
Toolkit.getDefaultToolkit().beep();
}
/**
* Removes one char from the right end.
*
* @param fb the fb
* @param offset the offset
* @param length the length
* @throws BadLocationException the bad location exception
*/
public void remove(DocumentFilter.FilterBypass fb, int offset, int length) throws BadLocationException {
Document doc = fb.getDocument();
StringBuilder sb = new StringBuilder(doc.getText(0, doc.getLength()));
int decimalOffset = sb.indexOf(decimal);
if (decimalOffset != -1) {
sb.deleteCharAt(decimalOffset);
sb.insert(decimalOffset - 1, decimal);
}
sb.deleteCharAt(sb.length() - 1);
try {
String text = format.format(format.parse(sb.toString()));
super.replace(fb, 0, doc.getLength(), text, null);
} catch (ParseException e) {
}
}
}
// ----------------------- METHOD TO BE USED BY PROGRAMMER TO MODIFY THE VALUE
/**
* Clears Value from this {@link JCurrencyField}.
*/
public void clearLong() {
try {
String valueStr = String.valueOf(getLong());
int length = valueStr.length();
for (int iChar = 0; iChar < length; iChar++) {
getDocument().remove(0, 0);
}
} catch (BadLocationException e) {
e.printStackTrace();
}
}
/**
* Sets Value for this {@link JCurrencyField}.
*
* @param value the new long
*/
public void setLong(long value) {
// Clear old value
clearLong();
// Set new value
String valueStr = String.valueOf(value);
int length = valueStr.length();
for (int iChar = 0; iChar < length; iChar++) {
setText(valueStr.substring(iChar, iChar + 1));
}
}
/**
* Gets value from this {@link JCurrencyField}.
*
* @return the long
*/
public long getLong() {
// Remove white spaces of any kind, remove . and , -> return only numbers
String s = getText().replace(",", "").replace(".", "").replace(" ", "").replace("\\s", "").replaceAll("\u00a0",
"");
s = s.trim();
return Long.valueOf(s);
}
}
Note: In my modified code I use long as the basis of the currency value instead of String. Basically I assume that money value is a long by doing so.

- 94
- 10
-
As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 16 '22 at 08:02
-
I do apologies, but in my sincere opinion I think you consider my well documented answer as unclear just because you are a bot and not a real human being. – Corey Oct 18 '22 at 14:08