javaswingdocumentlistener

documentlistener with invokelater goes infinite loop


I have a Jpanel with textfield. I am using documentListener to save the changes as the user types in the text field. User can type between 1-1000, if he types anything else, there would be a error message pop-up. Now, I am using invokeLater, but that causes the infinite loop, if the user enters >1000. How can I fix this.

mMaxLabelLength = new JTextField();
mMaxLabelLength.getDocument().addDocumentListener(this);
@Override
public void changedUpdate(DocumentEvent arg0)
{
}

@Override
public void insertUpdate(DocumentEvent arg0)
{
    saveActions();
    mMaxLabelLength.requestFocus();

}

@Override
public void removeUpdate(DocumentEvent arg0)
{
    saveActions();
    mMaxLabelLength.requestFocus();
}
private boolean saveActions()
{
    // get text
    String strValue = mMaxLabelLength.getText();

    // validate: must be positive integer between 1-1000;
    boolean bSaveIt = true;
    try
    {
        int nValue = Integer.parseInt(strValue);
        if (nValue < 1 || nValue > 1000)
            bSaveIt = false;
    }
    catch (NumberFormatException ne)
    {
        bSaveIt = false;
    }

    // save data to properties if valid
    if (bSaveIt)
    {
        //do something
    }
    else
    {
        // error message
        JOptionPane.showMessageDialog(this, "Please enter an integer value between 1 and 1000.", "Invalid Entry", JOptionPane.INFORMATION_MESSAGE);
                SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                                    int nMaxLabel = getMaxPieLabel();
                mMaxLabelLength.setText(new Integer(nMaxLabel).toString());
            }
        });
        return false;
    }
    setVisible(true);
    return true;
}

Solution

  • It's not really the domain of responsibility for the DocumentListener to modify the state of the Document or to interact with the UI.

    Instead, you should probably be using a DocumentFilter, which will allow you to catch the invalid state before its commit to the Document and a custom event notification to alert interested parties that a violation has occurred, for example...

    import java.awt.EventQueue;
    import java.awt.GridBagLayout;
    import javax.swing.JFrame;
    import javax.swing.JOptionPane;
    import javax.swing.JPanel;
    import javax.swing.JTextField;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    import javax.swing.border.EmptyBorder;
    import javax.swing.text.AbstractDocument;
    import javax.swing.text.AttributeSet;
    import javax.swing.text.BadLocationException;
    import javax.swing.text.DocumentFilter;
    
    public class Example {
    
        public static void main(String[] args) {
            new Example();
        }
    
        public Example() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class TestPane extends JPanel {
    
            public TestPane() {
                setLayout(new GridBagLayout());
                setBorder(new EmptyBorder(20, 20, 20, 20));
                JTextField field = new JTextField(10);
                LimitedRangeDocumentFilter filter = new LimitedRangeDocumentFilter(1, 1000);
                filter.setLimitedRangeDocumentFilterListener(new LimitedRangeDocumentFilterListener() {
                    @Override
                    public void updateWouldBeInvalid(LimitedRangeDocumentFilter filter, String text) {
                        JOptionPane.showMessageDialog(TestPane.this, 
                                text + " is not within " + filter.getMin() + "-" + filter.getMax() + " range",
                                "Error",
                                JOptionPane.ERROR_MESSAGE);
                    }
                });
                ((AbstractDocument)field.getDocument()).setDocumentFilter(filter);
                add(field);
            }
    
        }
    
        public interface LimitedRangeDocumentFilterListener {
            public void updateWouldBeInvalid(LimitedRangeDocumentFilter filter, String text);
        }
    
        public class LimitedRangeDocumentFilter extends DocumentFilter {
    
            private int min;
            private int max;
    
            private LimitedRangeDocumentFilterListener listener;
    
            public LimitedRangeDocumentFilter(int min, int max) {
                this.min = min;
                this.max = max;
            }
    
            public int getMin() {
                return min;
            }
    
            public int getMax() {
                return max;
            }
    
            public void setLimitedRangeDocumentFilterListener(LimitedRangeDocumentFilterListener listener) {
                this.listener = listener;
            }
    
            @Override
            public void insertString(DocumentFilter.FilterBypass fb, int offset,
                    String string, AttributeSet attr)
                    throws BadLocationException {
    
                StringBuilder sb = new StringBuilder(string);
                for (int i = sb.length() - 1; i >= 0; i--) {
                    char ch = sb.charAt(i);
                    if (!Character.isDigit(ch)) {
                        sb.deleteCharAt(i);
                    }
                }
    
                StringBuilder master = new StringBuilder(fb.getDocument().getText(0, fb.getDocument().getLength()));
                master.insert(offset, sb.toString());
                if (wouldBeValid(master.toString())) {
                    super.insertString(fb, offset, sb.toString(), attr);
                } else if (listener != null) {
                    listener.updateWouldBeInvalid(this, master.toString());
                }
            }
    
            @Override
            public void replace(DocumentFilter.FilterBypass fb,
                    int offset, int length, String string, AttributeSet attr) throws BadLocationException {
                if (length > 0) {
                    fb.remove(offset, length);
                }
                insertString(fb, offset, string, attr);
            }
    
            protected boolean wouldBeValid(String text) {
                boolean wouldBeValid = false;
                try {
                    int value = Integer.parseInt(text);
                    if (value >= min && value <= max) {
                        wouldBeValid = true;
                    }
                } catch (NumberFormatException exp) {
                }
                return wouldBeValid;
            }
        }
    
    }
    

    See Implementing a Document Filter and DocumentFilter Examples for more details