'Make a JFormattedTextField behave like ATM input
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.
Solution 1:[1]
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.
Solution 2:[2]
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
Solution 3:[3]
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'.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 |