Trouble with String Builder: Filtering Characters in Java Swing JTextField

62 Views Asked by At

I'm fairly new to Java and stackoverflow. Please alert me if I make a mistake or if I provide insufficient information.

I'm working on a Java Swing project and trying to use a custom DocumentFilter class to monitor and filter input in a JTextField. Specifically, I want to ensure that only three digits and certain special characters (-, comma) can be Typed in.

Here is the class i wrote:

import javax.swing.*;
import javax.swing.text.*;

public class NumberOnlyField extends JTextField {

    public NumberOnlyField() {
        PlainDocument doc = (PlainDocument) getDocument();
        doc.setDocumentFilter(new NumberOnlyFilter());
    }

    private class NumberOnlyFilter extends DocumentFilter {

        @Override
        public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
                throws BadLocationException {
            if (string == null) {
                return;
            }

            super.insertString(fb, offset, onlynumbers(string), attr);
        }

        @Override
        public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs)
                throws BadLocationException {
            if (text == null) {
                return;
            }

            super.replace(fb, offset, length, onlynumbers(text), attrs);
        }
    }

    private String onlynumbers(String string) {
        StringBuilder builder = new StringBuilder(string);
        for (int i = builder.length() - 1; i >= 0; i--) {
            char ch = builder.charAt(i);
            if ((!Character.isDigit(ch) && ch != '-' && ch != ',') || i > 3) {
                builder.deleteCharAt(i);
            }
        }
        return string;
    }
}

Here is use the TextField:

JTextField minRain = new NumberOnlyField();
        minRain.setBounds(155, 130, 35, 20);
        add(minRain);
        minRain.setColumns(10);

The filter isn't functioning at all. The text field appears normally with no errors occurring and i can write evrything in it. Given its straightforward nature, I'm struggling to pinpoint what I might be doing incorrectly.

2

There are 2 best solutions below

0
davidalayachew On

A DocumentFilter is certainly one way to do it, but if you're fine switching gears, it would probably be much easier to just use a DocumentListener. It's less work that way.

Here is a quick, easy example.


final JTextField textField = new JTextField();
final DocumentListener listener = 
   new DocumentListener()
   {

      public void insertUpdate(final DocumentEvent event)
      {
         yourValidationMethod(event);            
      }
               
      public void changedUpdate(final DocumentEvent event) {}
               
      public void removeUpdate(final DocumentEvent event)
      {
         yourValidationMethod(event);
      }

      private void yourValidationMethod(final DocumentEvent event)
      {
         final String string = event.getDocument().getText(0, event.getDocument().getLength());
         //do validations on this string
      }

   };

textField.getDocument().addDocumentListener(listener);

I also see that you are trying to edit the text. Normally, people just put up some warning that the text is bad, but that's fine. That's easy to do here too. Just use Document#remove(int offset, int length). So if someone types in a 1, then a 2, and then a f, you just get the offset, which is the index of the last character in the document, and then you get the length, which is 1, since you only want to remove the last character, then plug those 2 into the method, and you are good.

0
camickr On

Doesn't exactly answer your question but it should give you a better structure to work with for your DocumentFilter.

The code below:

  1. doesn't make any changes to the StringBuilder. It only uses to the StringBuilder to insert the text you want into the existing text. Then you can perform your edits for validity.
  2. the insertString(...) method simply invokes the replace(...) method so you don't have to repeat the logic.

You should only need to change the code in the validReplace(..) method. I would add the "length" test after the empty test and then invoke your loop to test each character for validity:

import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;

public class IntegerFilter extends DocumentFilter
{
    @Override
    public void insertString(FilterBypass fb, int offset, String text, AttributeSet attributes)
        throws BadLocationException
    {
        replace(fb, offset, 0, text, attributes);
    }

    @Override
    public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attributes)
        throws BadLocationException
    {
        //  In case someone tries to clear the Document by using setText(null)

        if (text == null)
            text = "";

        //  Build the text string assuming the replace of the text is successfull

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

        if (validReplace(sb.toString()))
            super.replace(fb, offset, length, text, attributes);
        else
            Toolkit.getDefaultToolkit().beep();
    }

    private boolean validReplace(String text)
    {
        //  In case setText("") is used to clear the Document

        if (text.isEmpty())
            return true;

        //  Verify input is an Integer

        try
        {
//          Integer.parseInt( text );
            Double.parseDouble( text );
            return true;
        }
        catch (NumberFormatException e)
        {
            return false;
        }
    }

    private static void createAndShowGUI()
    {
        JTextField textField = new JTextField(4);
        AbstractDocument doc = (AbstractDocument) textField.getDocument();
        doc.setDocumentFilter( new IntegerFilter() );
        textField.setText("123");
        textField.setText("123567");
        textField.setText(null);

        JFrame frame = new JFrame("Integer Filter");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout( new java.awt.GridBagLayout() );
        frame.add( textField );
        frame.setSize(220, 200);
        frame.setLocationByPlatform( true );
        frame.setVisible( true );
    }

    public static void main(String[] args) throws Exception
    {
        EventQueue.invokeLater( () -> createAndShowGUI() );
/*
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowGUI();
            }
        });
*/
    }

}