Search code examples
javaregexswingjtextfielddocumentfilter

How in Java I can create a field that can accept only digits and once dot?


I look all over the net and all the things I tried didn't work....

I found that I can use some JTextFormatterField but it didn't work.

Then I found that I can use DocumentFilter with regex and that what I did:

JTextField jFormattedTextFieldMoneyToConvert = new JTextField();
    ((AbstractDocument) jFormattedTextFieldMoneyToConvert.getDocument()).setDocumentFilter(new DocumentFilter(){
        Pattern regEx = Pattern.compile("^\\d+\\.?\\d*$");

        @Override
        public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
            Matcher matcher = regEx.matcher(text);
            if (!matcher.matches()) {
                return;
            }
            super.replace(fb, offset, length, text, attrs);
        }
    });

but it doesn't work...It accepts only digits. I want it to accept also dots. And I also need it to not to start with a dot and not to end with a dot.

what I'm doing wrong?


Solution

  • I had a similar problem a while back. The problem wasn't with the regex, but my misunderstanding of how the filter works. My assumption, like yours was that the String text passed to the replace method, is the entire string, when it's really only the character being typed. Similar discussion here.

    While all of @Bohemian's regex kung-fu might be what your looking for in terms of a matching regex (I don't know, it's not my forte), the problem (with the assumption) can be fixed by obtaining the entire document text, then try and match that, i.e.

    String completeText = fb.getDocument().getText(0, fb.getDocument().getLength());
    

    "And I also need it to not to start with a dot and not to end with a dot"

    Think about this logically for a second. The filter is called for each character input. But you don't want to end with . but you want to allow . somewhere in the middle. Not allowing . at the end negate the possibility of a . at all as you would need to type a . at the end before the next number

    You could probably just write a focus listener to add a 0 or remove the . when the field loses focus, if the user leaves a hanging .


    UPDATE: full example

    import java.awt.*;
    import javax.swing.*;
    import javax.swing.text.*;
    
    public class FilterDemo {
    
        public FilterDemo() {
            JFrame frame = new JFrame();
            frame.setLayout(new GridBagLayout());
            frame.setSize(300, 300);
            frame.add(createFilteredField());
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
    
        }
    
        private JTextField createFilteredField() {
            JTextField field = new JTextField(20);
            AbstractDocument document = (AbstractDocument) field.getDocument();
            final int maxCharacters = 10;
            document.setDocumentFilter(new DocumentFilter() {
                @Override
                public void replace(FilterBypass fb, int offs, int length,
                        String str, AttributeSet a) throws BadLocationException {
    
                    String text = fb.getDocument().getText(0,
                            fb.getDocument().getLength());
                    text += str;
                    if (text.matches("^\\d+\\.?\\d*$")) {   //  "^[0-9]+[.]?[0-9]{0,}$"
                        super.replace(fb, offs, length, str, a);
                    } else {
                        Toolkit.getDefaultToolkit().beep();
                    }
    
                }
    
                @Override
                public void insertString(FilterBypass fb, int offs, String str,
                        AttributeSet a) throws BadLocationException {
    
                    String text = fb.getDocument().getText(0,
                            fb.getDocument().getLength());
                    text += str;
                    if (text.matches("^\\d+\\.?\\d*$")) {
                        super.insertString(fb, offs, str, a);
                    } else {
                        Toolkit.getDefaultToolkit().beep();
                    }
                }
            });
            return field;
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    new FilterDemo();
                }
            });
        }
    }