1

In java , i am trying to make simple currency converter, but for that i need a text field which can restrict input to numbers only and more importantly double numbers. I tried using JFormatedTextField but it only format the input after you have done your input and click elsewhere but i need to restrict TextField to consume() each invalid character while doing input.

Possible Attempts:

Using JFormatedTextField:

JFormatedTextField textField = new JFormatedTextField(new DoubleFormat());
textField.setBounds(190, 49, 146, 33);
frame.getContentPane().add(textField);
textField.setColumns(10);

Using KeyTyped Event:

char c = arg0.getKeyChar();
if(!(Character.isDigit(c) || c == KeyEvent.VK_BACK_SPACE || c== KeyEvent.VK_DELETE)){
    arg0.consume();
}

Using KeyTyped Event with regex:

if(!((textField.getText().toString+arg0.getKeyChar()).matches("[0-9]*(.[0-9]*)?"))){
   arg0.consume();
}

Second and third attempt were close but then second attempt failed on point values and third attempt always read first character on textField no matter what it is, So any suggestions ? i am not very fond of JAVA GUI so kindly be patient.

Zulqurnain Jutt
  • 298
  • 3
  • 20
  • 1) Java GUIs have to work on different OS', screen size, screen resolution etc. using different PLAFs in different locales. As such, they are not conducive to pixel perfect layout. Instead use layout managers, or [combinations of them](http://stackoverflow.com/a/5630271/418556) along with layout padding and borders for [white space](http://stackoverflow.com/a/17874718/418556). 2) For this task I would tend to use a `JSpinner` with a `SpinnerNumberModel`. – Andrew Thompson Oct 26 '15 at 16:15
  • it's a simple edit text box required to give input number , if i tend to use spinner , how can i give number of different length ? means 34533434445545345345345.3454543534535345345 – Zulqurnain Jutt Oct 26 '15 at 17:22

4 Answers4

2

If you know how many places before and after decimal point you want, you can also use MaskFormatter. For example:

JFormattedTextField field = new JFormattedTextField(getMaskFormatter("######.##"));

(...)

private MaskFormatter getMaskFormatter(String format) {
    MaskFormatter mask = null;
    try {
        mask = new MaskFormatter(format);
        mask.setPlaceholderCharacter('0');
    }catch (ParseException ex) {
        ex.printStackTrace();
    }
    return mask;
}

However it will chenge a look of JTextField, so it will be always visible 000000.00 in it.

EDIT

Another way, not too elegant, but in my opinion working. Try with DecumentListener, maybe it will suit your needs:

field = new JFormattedTextField();
field.getDocument().addDocumentListener(new DocumentListener() {
    @Override
    public void insertUpdate(DocumentEvent e) {
        Runnable format = new Runnable() {
            @Override
            public void run() {
                String text = field.getText();
                if(!text.matches("\\d*(\\.\\d{0,2})?")){
                    field.setText(text.substring(0,text.length()-1));
                }
            }
        };
        SwingUtilities.invokeLater(format);
    }

    @Override
    public void removeUpdate(DocumentEvent e) {

    }

    @Override
    public void changedUpdate(DocumentEvent e) {

    }
});

I used regex: \\d*(\\.\\d{0,2})? because two decimal places is enough for currency.

m.cekiera
  • 5,365
  • 5
  • 21
  • 35
1

You would need to use a DocumentFilter. Read the section from the Swing tutorial on Implementing a DocumentFilter for an example to get you started.

Your implementation will be more complex because you will need to take the text already in the Document and then insert the new text in the appropriate location in the String and then invoke Double.parseDouble(...) on the String to make sure it is a valid double value.

If the validation succeeds then you continue with the insertion otherwise you can generate beep.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • Isn't there any other workaround without `DocumentFilter` ? , i am ignoring it because i didn't quite understand it. – Zulqurnain Jutt Oct 26 '15 at 15:01
  • 1
    How could you understand it in less then 10 minutes? Did you read the tutorial? Did you download the demo code and test it? Did you try my suggestion which is basically 3 lines of code? `i need to restrict TextField to consume() each invalid character while doing input` Using a DocumentFilter is the proper solution and does exactly what you want – camickr Oct 26 '15 at 15:18
1

You can add a key listener to the text field and implement the keyReleased() method to determine if they value in the text field is a double after every key stroke by the user.

public class CurrencyJTF extends JFrame {
JButton jButton = new JButton("Unfocus");
final JFormattedTextField textField = new JFormattedTextField(new DecimalFormat());
double lastDouble = 0.0;

public CurrencyJTF() throws HeadlessException {
    textField.setColumns(20);
    textField.setText(lastDouble + "");

    this.setLayout(new FlowLayout());

    this.add(textField);
    this.add(jButton);

    textField.addKeyListener(new KeyAdapter() {
        @Override
        public void keyReleased(KeyEvent e) {
            handleKeyReleased();
        }
    });
}

private void handleKeyReleased() {
    String text = textField.getText();
    if (text.isEmpty()) return;

    try {
        lastDouble = Double.parseDouble(text);
    } catch (NumberFormatException ex) {
        textField.setText(lastDouble + ""); // or set to other values you want
    }
}

public static void main(String[] args) {
    JFrame frame = new CurrencyJTF();
    frame.setVisible(true);
    frame.pack();
}
}
Bon
  • 3,073
  • 5
  • 21
  • 40
  • 1
    Not very user friendly. If you enter an invalid character the caret is always moved to the end of the text instead of remaining where the user attempted to insert the text. Also not very programmer friendly because two DocumentEvents (add/remove) will be generated for invalid characters even though the Document has not changed. – camickr Oct 26 '15 at 15:55
  • yes it is showing invalid character for a moment.not a right approach i think – Zulqurnain Jutt Oct 26 '15 at 17:25
0

You can write your own KeyListener something like that:

public class DoubleNumbersKeyListener implements KeyListener {

    final HashSet<Character> valid_keys = new HashSet<>();
    final ArrayList<Character> sequence = new ArrayList<>();

    public DoubleNumbersKeyListener() {
        valid_keys.add('.');
        valid_keys.add('0');
        valid_keys.add('1');
        valid_keys.add('2');
        valid_keys.add('3');
        valid_keys.add('4');
        valid_keys.add('5');
        valid_keys.add('6');
        valid_keys.add('7');
        valid_keys.add('8');
        valid_keys.add('9');
        valid_keys.add((char) KeyEvent.VK_BACK_SPACE);
        valid_keys.add((char) KeyEvent.VK_DELETE);
    }

    @Override
    public void keyTyped(KeyEvent event) {
        char c = event.getKeyChar();
            if (!valid_keys.contains(c)) {
                event.consume();
            } else {
                if (c == KeyEvent.VK_DELETE || c == KeyEvent.VK_BACK_SPACE) {
                    if (!sequence.isEmpty()) {
                        char last = sequence.remove(sequence.size() - 1);
                        if (last == '.') {
                            valid_keys.add(last);
                        }
                    }
                } else {
                    sequence.add(c);
                    if (c == '.') {
                        valid_keys.remove(c);
                    }
                }
            }
    }

    @Override
    public void keyPressed(KeyEvent e) {

    }

    @Override
    public void keyReleased(KeyEvent e) {

    }

}
P.N.N
  • 13
  • 2