Restricting JTextField input to Integers

252,011

Solution 1

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?

Solution 2

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

Solution 3

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

Solution 4

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

Solution 5

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();
                }
            }
        });
    }
Share:
252,011
AndroidDev
Author by

AndroidDev

Updated on June 09, 2020

Comments

  • AndroidDev
    AndroidDev about 4 years

    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.

  • trashgod
    trashgod almost 12 years
    +1 Also consider InputVerifier.
  • AndroidDev
    AndroidDev almost 12 years
    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
  • Hovercraft Full Of Eels
    Hovercraft Full Of Eels almost 12 years
    @Randy: alter the test(String text) method. Have it return true if text.trim().isEmpty() is true.
  • Admin
    Admin over 10 years
    With this the user can insert letters in the field.
  • user1296058
    user1296058 over 10 years
    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?
  • Hovercraft Full Of Eels
    Hovercraft Full Of Eels over 10 years
    @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.
  • LeeCambl
    LeeCambl over 9 years
    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).
  • djm
    djm over 9 years
    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
    djm over 9 years
    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?
  • cesAR
    cesAR over 7 years
    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.
  • Enwired
    Enwired over 7 years
    @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.
  • BaptisteL
    BaptisteL over 7 years
    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
    BaptisteL over 7 years
    @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 ..
  • Halfacht
    Halfacht over 6 years
    field.setColumns(..) //for the width number above 1000 can't be parsed to an integer. (the usual way)
  • Stevey
    Stevey over 3 years
    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.