58

I know that this question must have been asked and answered a million times, but I just can't find an easy solution. I have a JTextField that is meant to accept only positive integers as input. I need a way to make sure that nothing else gets input here.

I already have a keyListener attached to this control. Removing the other code that this listener is there to handle, I have this:

       txtAnswer.addKeyListener(new KeyAdapter() {
        @Override
        public void keyPressed(KeyEvent e) {

            int key = e.getKeyCode();

            /* Restrict input to only integers */
            if (key < 96 && key > 105) e.setKeyChar('');
        };
    });

As you can see, I'm trying to use the the KeyCode to check whether the key just pressed falls within the range of integers. This seems to work. But what I want to do is to simply disregard the entry if it falls outside of this range. The code e.setKeyChar('') was meant to handle this, but it doesn't work. The code will compile, but it has no visible effect.

Can anybody tell me if I am on the right track? What can I replace e.setKeyChar('') with to make this work? Or am I totally going in the wrong direction?

Thanks.

JimmyPena
  • 8,694
  • 6
  • 43
  • 64
AndroidDev
  • 20,466
  • 42
  • 148
  • 239
  • @Randy : Please do look at this [example](http://stackoverflow.com/questions/9477354/how-to-allow-introducing-only-digits-in-jtextfield/9478124#9478124) also – nIcE cOw Jun 19 '12 at 05:41

6 Answers6

77

Do not use a KeyListener for this as you'll miss much including pasting of text. Also a KeyListener is a very low-level construct and as such, should be avoided in Swing applications.

The solution has been described many times on SO: Use a DocumentFilter. There are several examples of this on this site, some written by me.

For example: using-documentfilter-filterbypass

Also for tutorial help, please look at: Implementing a DocumentFilter.

Edit

For instance:

import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.DocumentFilter;
import javax.swing.text.PlainDocument;

public class DocFilter {
   public static void main(String[] args) {
      JTextField textField = new JTextField(10);

      JPanel panel = new JPanel();
      panel.add(textField);

      PlainDocument doc = (PlainDocument) textField.getDocument();
      doc.setDocumentFilter(new MyIntFilter());


      JOptionPane.showMessageDialog(null, panel);
   }
}

class MyIntFilter extends DocumentFilter {
   @Override
   public void insertString(FilterBypass fb, int offset, String string,
         AttributeSet attr) throws BadLocationException {

      Document doc = fb.getDocument();
      StringBuilder sb = new StringBuilder();
      sb.append(doc.getText(0, doc.getLength()));
      sb.insert(offset, string);

      if (test(sb.toString())) {
         super.insertString(fb, offset, string, attr);
      } else {
         // warn the user and don't allow the insert
      }
   }

   private boolean test(String text) {
      try {
         Integer.parseInt(text);
         return true;
      } catch (NumberFormatException e) {
         return false;
      }
   }

   @Override
   public void replace(FilterBypass fb, int offset, int length, String text,
         AttributeSet attrs) throws BadLocationException {

      Document doc = fb.getDocument();
      StringBuilder sb = new StringBuilder();
      sb.append(doc.getText(0, doc.getLength()));
      sb.replace(offset, offset + length, text);

      if (test(sb.toString())) {
         super.replace(fb, offset, length, text, attrs);
      } else {
         // warn the user and don't allow the insert
      }

   }

   @Override
   public void remove(FilterBypass fb, int offset, int length)
         throws BadLocationException {
      Document doc = fb.getDocument();
      StringBuilder sb = new StringBuilder();
      sb.append(doc.getText(0, doc.getLength()));
      sb.delete(offset, offset + length);

      if (test(sb.toString())) {
         super.remove(fb, offset, length);
      } else {
         // warn the user and don't allow the insert
      }

   }
}

Why is this important?

  • What if the user uses copy and paste to insert data into the text component? A KeyListener can miss this?
  • You appear to be desiring to check that the data can represent an int. What if they enter numeric data that doesn't fit?
  • What if you want to allow the user to later enter double data? In scientific notation?
Community
  • 1
  • 1
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • 1
    +1 Also consider [`InputVerifier`](http://docs.oracle.com/javase/tutorial/uiswing/misc/focus.html#inputVerification). – trashgod Jun 19 '12 at 02:11
  • This is pretty cool. I've been staring at for quite a while now and testing it in a simplfied app. I more or less understand what it does and why (more or less!). It creates one problem for me, though. On my JTextBox, after the user hits the enter key, I call a few methods, which I am still using a KeyListener to accomplish. This seems to work. But the VERY last thing that I need to do with this textbox is to clear it out so that new text can be entered. I was doing this with txtAnswer.setText(""); but now it appears that this document filter is blocking this action. Can anybody suggest? Thank – AndroidDev Jun 20 '12 at 02:37
  • 1
    @Randy: alter the test(String text) method. Have it return true if `text.trim().isEmpty()` is true. – Hovercraft Full Of Eels Jun 20 '12 at 02:46
  • Thanks you for your sample. It's very useful. Your example seem to limit the input to a maximum of 10 digits only. Can you please tell me how to increase this? – user1296058 Feb 14 '14 at 20:09
  • @user1296058: the key is in the `test(...)` method of the DocumentFilter. It passes if the String parses to an ***int*** and fails if it's not an int, and the 10 digit limit you're seeing is nothing more than the limits of the int type. If you want other restrictions, use a different test method. – Hovercraft Full Of Eels Feb 14 '14 at 20:19
  • I used this and it works, however it allows spaces for some reason. How do I block spaces? And how do I put the default value of zero back if they leave the textfield empty? Thanks, – djm Feb 03 '15 at 21:31
  • Ok, my bad. I put a text.trim() in there and that's how the spaces were getting in. How do I restore the default value of zero? – djm Feb 03 '15 at 21:42
  • 5
    I encountered the problem that you cannot erase the last digit, if you have the same problem you can use this piece of code in the remove function. `if(sb.toString().length() == 0) { super.replace(fb, offset, length, "0", null); } else { if (test(sb.toString())) { super.remove(fb, offset, length); } else { // warn the user and don't allow the insert } }` In my exemple I replace it by `"0"`, but you can replace it by `""` – BaptisteL Feb 21 '17 at 13:41
56

You can also use JFormattedTextField, which is much simpler to use. Example:

public static void main(String[] args) {
    NumberFormat format = NumberFormat.getInstance();
    NumberFormatter formatter = new NumberFormatter(format);
    formatter.setValueClass(Integer.class);
    formatter.setMinimum(0);
    formatter.setMaximum(Integer.MAX_VALUE);
    formatter.setAllowsInvalid(false);
    // If you want the value to be committed on each keystroke instead of focus lost
    formatter.setCommitsOnValidEdit(true);
    JFormattedTextField field = new JFormattedTextField(formatter);

    JOptionPane.showMessageDialog(null, field);

    // getValue() always returns something valid
    System.out.println(field.getValue());
}
Peter Tseng
  • 13,613
  • 4
  • 67
  • 57
  • 4
    With this the user can insert letters in the field. –  Dec 19 '13 at 16:24
  • 5
    This method works well, except for one thing: once you have entered a number, the first digit can not be deleted with the Backspace key. – cesAR Oct 03 '16 at 12:23
  • @cesAR : I fixed it by encoding my own implementation, you can refer to Hovercraft Full Of Eels answer, I also added a comment at the end to fix this issue. The problem comes from the fact that it doesn't consider`""` as a valid Interger/Double .. – BaptisteL Feb 21 '17 at 13:47
  • field.setColumns(..) //for the width number above 1000 can't be parsed to an integer. (the usual way) – Halfacht Oct 09 '17 at 10:15
  • I tried this approach, but needed minimum=100 and maximum=300, and setting these prevented the field from being edited at all, because there was no way to modify the text without making it an invalid value. I went with a postprocessing phase where I check the value and whine if it's out of range. – Stevey Nov 19 '20 at 05:51
23

I can't believe I haven't found this simple solution anywhere on stack overflow yet, it is by far the most useful. Changing the Document or DocumentFilter does not work for JFormattedTextField. Peter Tseng's answer comes very close.

NumberFormat longFormat = NumberFormat.getIntegerInstance();

NumberFormatter numberFormatter = new NumberFormatter(longFormat);
numberFormatter.setValueClass(Long.class); //optional, ensures you will always get a long value
numberFormatter.setAllowsInvalid(false); //this is the key!!
numberFormatter.setMinimum(0l); //Optional

JFormattedTextField field = new JFormattedTextField(numberFormatter);
Umi
  • 4,137
  • 1
  • 17
  • 13
  • 10
    The field can also never be empty after you've started typing something in it (i.e. if I type 1234 and decide "actually, I don't want a number in this field", at least 1 character will remain). – LeeCambl Nov 14 '14 at 15:41
  • 6
    @LeeCambl It is possible to solve that problem by making a subclass of `NumberFormatter` which overrides `stringToValue(String text)` to return `null` for an empty string. – Enwired Jan 27 '17 at 01:19
21

Here's one approach that uses a keylistener,but uses the keyChar (instead of the keyCode):

http://edenti.deis.unibo.it/utils/Java-tips/Validating%20numerical%20input%20in%20a%20JTextField.txt

 keyText.addKeyListener(new KeyAdapter() {
    public void keyTyped(KeyEvent e) {
      char c = e.getKeyChar();
      if (!((c >= '0') && (c <= '9') ||
         (c == KeyEvent.VK_BACK_SPACE) ||
         (c == KeyEvent.VK_DELETE))) {
        getToolkit().beep();
        e.consume();
      }
    }
  });

Another approach (which personally I find almost as over-complicated as Swing's JTree model) is to use Formatted Text Fields:

http://docs.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html

paulsm4
  • 114,292
  • 17
  • 138
  • 190
6

I used to use the Key Listener for this but I failed big time with that approach. Best approach as recommended already is to use a DocumentFilter. Below is a utility method I created for building textfields with only number input. Just beware that it'll also take single '.' character as well since it's usable for decimal input.

public static void installNumberCharacters(AbstractDocument document) {
        document.setDocumentFilter(new DocumentFilter() {
            @Override
            public void insertString(FilterBypass fb, int offset,
                    String string, AttributeSet attr)
                    throws BadLocationException {
                try {
                    if (string.equals(".")
                            && !fb.getDocument()
                                    .getText(0, fb.getDocument().getLength())
                                    .contains(".")) {
                        super.insertString(fb, offset, string, attr);
                        return;
                    }
                    Double.parseDouble(string);
                    super.insertString(fb, offset, string, attr);
                } catch (Exception e) {
                    Toolkit.getDefaultToolkit().beep();
                }

            }

            @Override
            public void replace(FilterBypass fb, int offset, int length,
                    String text, AttributeSet attrs)
                    throws BadLocationException {
                try {
                    if (text.equals(".")
                            && !fb.getDocument()
                                    .getText(0, fb.getDocument().getLength())
                                    .contains(".")) {
                        super.insertString(fb, offset, text, attrs);
                        return;
                    }
                    Double.parseDouble(text);
                    super.replace(fb, offset, length, text, attrs);
                } catch (Exception e) {
                    Toolkit.getDefaultToolkit().beep();
                }
            }
        });
    }
Chan
  • 2,601
  • 6
  • 28
  • 45
3

When you type integer numbers to JtextField1 after key release it will go to inside try , for any other character it will throw NumberFormatException. If you set empty string to jTextField1 inside the catch so the user cannot type any other keys except positive numbers because JTextField1 will be cleared for each bad attempt.

 //Fields 
int x;
JTextField jTextField1;

 //Gui Code Here

private void jTextField1KeyReleased(java.awt.event.KeyEvent evt) {                                        
    try {
        x = Integer.parseInt(jTextField1.getText());
    } catch (NumberFormatException nfe) {
        jTextField1.setText("");
    }
}   
berkc
  • 525
  • 3
  • 9
  • 21
user3498019
  • 95
  • 1
  • 2
  • 10