Search code examples
javaswingadapterjtextfieldshort

JTextField accept only valid unsigned Shorts? (Using KeyAdapter)


Below is the KeyAdapter I tried to get working to only accept values less than 65535. It seems as though it gets it one keystroke behind where it actually should. For example, If I type "55", the System.out.println will yield "5", doing "3298" will yield "329", etc.

// Allows for unsigned short values only
KeyAdapter unsignedShortAdapter = new KeyAdapter() {

    public void keyTyped(KeyEvent e) {
        char c = e.getKeyChar();
        int tempInt = 0;
        JTextField temp = null;

        if (!((Character.isDigit(c) || (c == KeyEvent.VK_BACK_SPACE) || (c == KeyEvent.VK_DELETE)))) {
            getToolkit().beep();
            e.consume();
        }
        try {
            temp = (JTextField) e.getSource();
            System.out.println(temp.getText());
            tempInt = (Integer.parseInt(temp.getText().toString()));
        } catch (NumberFormatException e1) {

        } finally {
            if (tempInt > (Short.MAX_VALUE * 2)) {
                 getToolkit().beep();
                 e.consume();
                temp.setText(temp.getText().substring(0, temp.getText().length() - 1));

                invalidate();
            }

        }
    }

};

Solution

  • So, instead of a KeyListener, which you've found, is unreliable and will cause lots of nasty side effects (and possible Document Mutation exceptions :P), we should use a DocumentFilter, cause that's what it's designed for

    public class ShortFilter extends DocumentFilter {
    
        protected boolean valid(String text) {
    
    
            boolean valid = true;
            for (char check : text.toCharArray()) {
    
                if (!Character.isDigit(check)) {
    
                    valid = false;
                    break;
    
                }
    
            }
    
            if (valid) {
    
                int iValue = Integer.parseInt(text);
    
                valid = iValue <= (Short.MAX_VALUE * 2);
    
            }
    
            return valid;
    
        }
    
        @Override
        public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException {
    
            StringBuilder sb = new StringBuilder(fb.getDocument().getText(0, fb.getDocument().getLength()));
            sb.insert(offset, text);
    
            if (valid(sb.toString())) {
    
                super.insertString(fb, offset, text, attr);
    
            }
    
        }
    
        @Override
        public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
    
            if (length > 0) {
    
                StringBuilder sb = new StringBuilder(fb.getDocument().getText(0, fb.getDocument().getLength()));
    
                sb.delete(offset, length);
                sb.insert(offset, text);
    
                if (valid(sb.toString())) {
    
                    super.replace(fb, offset, length, text, attrs);
    
                }
    
            } else {
    
                insertString(fb, offset, text, attrs);
    
            }
    
        }
    }
    

    You will need to apply this to the field's document

    ((AbstractDocument) field.getDocument()).setDocumentFilter(new ShortFilter());
    

    I'd check out

    For some more info

    UPDATE for Decimal inclusion

    Basically, if you want to allow the inclusion of a decimal, you need to allow for the character in the valid method.

    You also need to check the current document's contents

    StringBuilder sb = new StringBuilder(fb.getDocument().getText(0, fb.getDocument().getLength()));
    
    // Update the StringBuilder as per noraml
    // Check valid as per normal
    
    if (text.contains(".") && sb.contains(".")) {
      // already have decimal place
    } else {
      // Business as usual...
    }