Search code examples
javaregexswingnumbersjtextfield

JTextField with regex applied won't accept value until AFTER the Document is applied


I have a JTextField that I have overridden the Document for, so that I can prevent the user from entering some characters. The way this works is my extension of Document receives a Regex in its constructor, then checks anything the user types against the Regex:

public class ValidDocument extends PlainDocument
{
    private String regex;

    public ValidDocument(String regex)
    {
        this.regex = regex;
    }

    @Override
    public void insertString(int offs, String str, AttributeSet a)
    {
        if(str == null) return;

        try
        {
            if((this.getText(0, getLength()) + str).matches(regex))
                super.insertString(offs, str, a);
        }
        catch(BadLocationException ble)
        {
            System.out.println("Came across a BadLocationException");
        }
    }
}

I had an issue however, where a JTextField that I wanted to display only valid float/double numbers in wasn't displaying its initial value. The code I was using is below:

float value = 25.0f;
JTextField textField = new JTextField("" + value);
textField.setDocument(new ValidDocument("^(\\-)?\\d*(\\.)?\\d*$"));

So the JTextField displayed, but there was no initial value. I tried to enter 25.0 into the field and it accepted it I had originally expected it would. After a bit of stuffing around, I tried adding:

textField.setText("" + value);

And it displayed the value. It occurred to me that it might accept anything I tried to put in setText(), so I added alphabetic characters to it:

textField.setText("as" + value);

As I suspected, it included the alphabetic characters, even though the Regex should have prevented that. So my thoughts are that the Document is bypassed when using this function.

Can anyone shed some light on why applying the ValidDocument to my JTextField is removing the text I placed in the constructor of the text field? I have other JTextFields with less restrictive Regex's that still display the value I set in the constructor. And why would it be overriding the value passed into the constructor, but not the one passed into setText()?


Solution

  • Don't use PlainDocument, use a DocumentFilter, it's what it is deisnged for. The method you're attempting to use has been out of date for more than 10 years, for example...

    public class PatternFilter extends DocumentFilter {
    
        // Useful for every kind of input validation !
        // this is the insert pattern
        // The pattern must contain all subpatterns so we can enter characters into a text component !
        private Pattern pattern;
    
        public PatternFilter(String pat) {
            pattern = Pattern.compile(pat);
        }
    
        public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
                throws BadLocationException {
    
            String newStr = fb.getDocument().getText(0, fb.getDocument().getLength()) + string;
            Matcher m = pattern.matcher(newStr);
            if (m.matches()) {
                super.insertString(fb, offset, string, attr);
            } else {
            }
        }
    
        public void replace(FilterBypass fb, int offset,
                            int length, String string, AttributeSet attr) throws
                BadLocationException {
    
            if (length > 0) fb.remove(offset, length);
            insertString(fb, offset, string, attr);
        }
    }
    

    Which comes from this DocumentFilter Examples

    Take a look at Implementing a Document Filter for more details

    If you don't need real time filtering, another option would be to use a JFormattedTextField or JSpinner. See How to Use Formatted Text Fields and How to Use Spinners for more details