JSpinner:通知输入是否有效

时间:2014-02-05 08:27:41

标签: java swing validation jspinner

我有一个带有NumberModel的JSpinner。 Spinner的文本字段允许任意输入,但只接受commitEdit上的数字输入。这意味着,如果我输入一个数字后跟任何字母字符并按Enter键,则格式化程序会尝试解析输入并最终切断垃圾输入:"2adsklfja" --> "2"

我想在尝试解析整个输入之前收到垃圾输入通知。有没有提供此信息的事件?

更新:我接受一些数字和字母输入的组合。微调器存储测量值。如果用户输入"23 inch",则我检测到子串英寸并相应地转换数值。我的目标是在无法检测到测量单位字符串或未知时显示错误警告。

更新(不完整的解决方案): 此解决方案以以下格式帮助用户输入:number text

我在微调器的文本字段中添加了动作侦听器,并为微调器添加了一个更改侦听器。两个侦听器都会调用尝试解析文本字段中的潜在度量单位字符串的代码。如果这不成功,则当前输入可以是仅数字输入,或者具有未知的尾随字母串。我们需要通过使用正则表达式检查当前输入字符串来检测第二种情况:如果它完全是数字,则它不是无效输入。我们还必须考虑十进制分隔符和数千个分隔符:

final DefaultFormatterFactory formatFact = (DefaultFormatterFactory)spinnerTextField.getFormatterFactory();
final NumberFormatter formatter = (NumberFormatter)formatFact.getDefaultFormatter();
final String currentValue = spinnerTextField.getText().trim();

// detecting known measuring unit strings
...

// catch invalid strings
if (!unitFound) {
  final char decSeparator = ((DecimalFormat)formatter.getFormat()).getDecimalFormatSymbols().getDecimalSeparator();
  final char thousandsSeparator = ((DecimalFormat)formatter.getFormat()).getDecimalFormatSymbols()
    .getGroupingSeparator();
  final boolean numberOnly = currentValue.matches("[\\d\\Q" + decSeparator + thousandsSeparator + "\\E]+");
  System.err.println("Invalid=" + !numberOnly + (!numberOnly ? ": " + currentValue : ""));
}

1 个答案:

答案 0 :(得分:0)

你说:

  

Spinner的文本字段允许任意输入,但只接受commitEdit上的数字输入。

如果您只是希望commitEdit()来电接受类似" 125英寸"的内容,请尝试以下方法:

  1. JSpinner提供DefaultEditor
  2. 获取DefaultEditor' JFormattedTextField
  3. 让它可编辑。
  4. 使用自定义(简单)AbstractFormatterFactory
  5. 提供
  6. 您的自定义AbstractFormatterFactory会自定义(简单)AbstractFormatter
  7. 这些AbstractFormatter处理将用户输入字符串解析为值,然后返回,这样您就可以检查是否存在任何解析错误,并在需要时抛出ParseException。 LI>

    遵循示例代码:

    import java.awt.GridLayout;  
    import java.text.ParseException;  
    import java.util.HashMap;  
    import java.util.Objects;  
    import javax.swing.JButton;  
    import javax.swing.JFormattedTextField;  
    import javax.swing.JFormattedTextField.AbstractFormatter;  
    import javax.swing.JFormattedTextField.AbstractFormatterFactory;  
    import javax.swing.JFrame;  
    import javax.swing.JOptionPane;  
    import javax.swing.JPanel;  
    import javax.swing.JSpinner;  
    import javax.swing.JSpinner.DefaultEditor;  
    import javax.swing.SpinnerNumberModel;  
    
    public final class CommitSpinner extends JPanel {  
        private String currentUnit;
    
        /**
         * Your formatter converts the text to integer by ignoring any measuring units,
         * and then converts the integer back to string by appending the last measuring unit:
         */
        private final class MyFormatter extends AbstractFormatter {
            @Override
            public Object stringToValue(final String text) throws ParseException {
                currentUnit = parseUnitPart(text);
                return parseIntegerPart(text);
            }
    
            @Override
            public String valueToString(final Object value) throws ParseException {
                return Objects.toString(value) + ' '  + currentUnit;
            }
        }
    
        private final class MyFormatterFactory extends AbstractFormatterFactory {
            private final HashMap<JFormattedTextField, AbstractFormatter> formatters;
    
            private MyFormatterFactory() {
                formatters = new HashMap<>();
            }
    
            /**
             * Because this method is a 'getter', I implemented the factory with a HashMap.
             * If it was a 'createFormatter' method for example, you could simply return a new
             * instance of MyFormatter.
             * @param tf the formatted text field to obtain its formatter.
             * @return the formatter for the given formatted text field.
             */
            @Override
            public AbstractFormatter getFormatter(final JFormattedTextField tf) {
                if (!formatters.containsKey(tf))
                    formatters.put(tf, new MyFormatter());
                return formatters.get(tf);
            }
        }
    
        private CommitSpinner() {
            super(new GridLayout(0, 1));
    
            currentUnit = "inch"; //Let's say the first value of the spinner is measured in inches.
    
            final JSpinner spin = new JSpinner(new SpinnerNumberModel(0, 0, 10000, 1)); //Your values here. Let's say for now this is a spinner for integers.
    
            final DefaultEditor editor = new DefaultEditor(spin); //We need a DefaultEditor (to be able to obtain the JFormattedTextField and customize it).
    
            final JFormattedTextField field = editor.getTextField();
            field.setFocusLostBehavior(JFormattedTextField.COMMIT); //Could be "PERSIST" also, but it shall not be "COMMIT_OR_REVERT" (which is the default).
            field.setFormatterFactory(new MyFormatterFactory());
            field.setEditable(true); //Allow user input (obvious reasons).
    
            spin.setEditor(editor);
    
            //The commitButton button will "commitEdit()", and the ParseException exception part works as expected!
            final JButton commitButton = new JButton("Check input");
            commitButton.addActionListener(e -> {
                try {
                    spin.commitEdit();
                    JOptionPane.showMessageDialog(null, currentUnit + " = " + spin.getValue());
                }
                catch (final ParseException pe) {
                    JOptionPane.showMessageDialog(null, pe.toString(), "Illegal input!", JOptionPane.ERROR_MESSAGE);
                }
            });
    
            add(spin);
            add(commitButton);
        }
    
        public static void main(final String[] args) {
            final JFrame frame = new JFrame("CommitSpinner demo");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(new CommitSpinner());
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    
        //Let's parse user input... What could go wrong?... xD
        private static int parseIntegerPart(final String text) throws ParseException {
            if (text == null)
                throw new ParseException("Text is null.", 0);
            if (text.trim().isEmpty())
                throw new ParseException("Text is empty.", 0);
            final String[] args = text.split(" ");
            if (args.length != 2)
                throw new ParseException("Text has invalid number of arguments (required 2, found " + args.length + ").", 0);
            final String unit = args[1].trim();
            if (!unit.equalsIgnoreCase("inch")
                && !unit.equalsIgnoreCase("cm"))
                throw new ParseException("Nor 'inch', nor 'cm' detected.", 0);
            try {
                return Integer.valueOf(args[0].trim());
            }
            catch (final NumberFormatException nfe) {
                throw new ParseException(args[0] + " is not a valid integer value.", 0);
            }
        }
    
        //Let's parse user input... What could go wrong?... xD
        private static String parseUnitPart(final String text) throws ParseException {
            if (text == null)
                throw new ParseException("Text is null.", 0);
            if (text.trim().isEmpty())
                throw new ParseException("Text is empty.", 0);
            final String[] args = text.split(" ");
            if (args.length != 2)
                throw new ParseException("Text has invalid number of arguments (required 2, found " + args.length + ").", 0);
            final String unit = args[1].trim().toLowerCase();
            if (!unit.equals("inch") && !unit.equals("cm"))
                throw new ParseException("Nor 'inch', nor 'cm' detected.", 0);
            return unit;
        }
    }  
    

    你也说过:

      

    我想在尝试解析整个输入之前收到有关垃圾输入的通知。

    JFormattedTextField中的自定义DocumentFilter可以解决问题。

    AbstractFormatter中有一个名为getDocumentFilter()的方法。如果您看到代码,则会调用此方法向DocumentFilter添加JFormattedTextField

    DocumentFilter S&#39;在用户输入时调用方法,即你可以在他提交&#34;提交之前解析用户的输入。它。

    默认实现返回null,这意味着不会添加DocumentFilter

    所以我只是覆盖了这个方法来实现我的自定义(简单)DocumentFilter,然后在用户输入时检查用户输入......

    如果解析了用户的输入,那么我只需将标签的前景色设置为绿色。如果没有,那么红色。但这只是为了证明你可以处理这些事件。

    遵循示例代码:

    import java.awt.Color;
    import java.awt.GridLayout;
    import java.text.ParseException;
    import java.util.HashMap;
    import java.util.Objects;
    import javax.swing.JButton;
    import javax.swing.JFormattedTextField;
    import javax.swing.JFormattedTextField.AbstractFormatter;
    import javax.swing.JFormattedTextField.AbstractFormatterFactory;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JOptionPane;
    import javax.swing.JPanel;
    import javax.swing.JSpinner;
    import javax.swing.JSpinner.DefaultEditor;
    import javax.swing.SpinnerNumberModel;
    import javax.swing.text.AttributeSet;
    import javax.swing.text.BadLocationException;
    import javax.swing.text.DocumentFilter;
    
    public final class RealTimeSpinner extends JPanel {
        private String currentUnit;
        private final JLabel stateLabel;
    
        private final class MyFormatter extends AbstractFormatter {
            @Override
            public Object stringToValue(final String text) throws ParseException {
                currentUnit = parseUnitPart(text);
                return parseIntegerPart(text);
            }
    
            @Override
            public String valueToString(final Object value) throws ParseException {
                return Objects.toString(value) + ' '  + currentUnit;
            }
    
            @Override
            protected DocumentFilter getDocumentFilter() {
                return new DocumentFilter() {
                    private void after(final FilterBypass fb) throws BadLocationException {
                        try {
                            //Obtain the user's input text so far:
                            final String theWholeNewText = fb.getDocument().getText(0, fb.getDocument().getLength());
    
                            //Try parse the user's input so far:
                            parseUnitPart(theWholeNewText);
                            parseIntegerPart(theWholeNewText);
    
                            //If the parsing succeeds, then set the foreground color to GREEN:
                            stateLabel.setForeground(Color.GREEN.darker());
                        }
                        catch (final ParseException pe) {
    
                            //If the parsing fails, then set the foreground color to RED:
                            stateLabel.setForeground(Color.RED.darker());
                        }
                    }
    
                    @Override
                    public void remove(final FilterBypass fb, final int offset, final int length) throws BadLocationException {
                        super.remove(fb, offset, length);
                        after(fb);
                    }
    
                    @Override
                    public void insertString(final FilterBypass fb, final int offset, final String string, final AttributeSet attr) throws BadLocationException {
                        super.insertString(fb, offset, string, attr);
                        after(fb);
                    }
    
                    @Override
                    public void replace(final FilterBypass fb, final int offset, final int length, final String text, final AttributeSet attrs) throws BadLocationException {
                        super.replace(fb, offset, length, text, attrs);
                        after(fb);
                    }
                };
            }
        }
    
        private final class MyFormatterFactory extends AbstractFormatterFactory {
            private final HashMap<JFormattedTextField, AbstractFormatter> formatters;
    
            private MyFormatterFactory() {
                formatters = new HashMap<>();
            }
    
            /**
             * Because this method is a 'getter', I implemented the factory with a HashMap.
             * If it was a 'createFormatter' method for example, you could simply return a new
             * instance of MyFormatter.
             * @param tf the formatted text field to obtain its formatter.
             * @return the formatter for the given formatted text field.
             */
            @Override
            public AbstractFormatter getFormatter(final JFormattedTextField tf) {
                if (!formatters.containsKey(tf))
                    formatters.put(tf, new MyFormatter());
                return formatters.get(tf);
            }
        }
    
        private RealTimeSpinner() {
            super(new GridLayout(0, 1));
    
            currentUnit = "inch"; //Let's say the first value of the spinner is measured in inches.
    
            final JSpinner spin = new JSpinner(new SpinnerNumberModel(0, 0, 10000, 1)); //Your values here. Let's say for now this is a spinner for integers.
    
            final DefaultEditor editor = new DefaultEditor(spin); //We need a DefaultEditor (to be able to obtain the JFormattedTextField and customize it).
    
            final JFormattedTextField field = editor.getTextField();
            field.setFocusLostBehavior(JFormattedTextField.COMMIT); //Could be "PERSIST" also, but it shall not be "COMMIT_OR_REVERT" (which is the default).
            field.setFormatterFactory(new MyFormatterFactory());
            field.setEditable(true); //Allow user input (obvious reasons).
    
            spin.setEditor(editor);
    
            final JButton commitButton = new JButton("Check input");
            commitButton.addActionListener(e -> {
                try {
                    spin.commitEdit();
                    JOptionPane.showMessageDialog(null, currentUnit + " = " + spin.getValue());
                }
                catch (final ParseException pe) {
                    JOptionPane.showMessageDialog(null, pe.toString(), "Illegal input!", JOptionPane.ERROR_MESSAGE);
                }
            });
    
            stateLabel = new JLabel("This is the color of the state of the input.", JLabel.CENTER);
            stateLabel.setForeground(Color.GREEN.darker());
    
            add(spin);
            add(commitButton);
            add(stateLabel);
        }
    
        public static void main(final String[] args) {
            final JFrame frame = new JFrame("CommitSpinner demo");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(new RealTimeSpinner());
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    
        //Let's parse user input... What could go wrong?... xD
        private static int parseIntegerPart(final String text) throws ParseException {
            if (text == null)
                throw new ParseException("Text is null.", 0);
            if (text.trim().isEmpty())
                throw new ParseException("Text is empty.", 0);
            final String[] args = text.split(" ");
            if (args.length != 2)
                throw new ParseException("Text has invalid number of arguments (required 2, found " + args.length + ").", 0);
            final String unit = args[1].trim();
            if (!unit.equalsIgnoreCase("inch")
                && !unit.equalsIgnoreCase("cm"))
                throw new ParseException("Nor 'inch', nor 'cm' detected.", 0);
            try {
                return Integer.valueOf(args[0].trim());
            }
            catch (final NumberFormatException nfe) {
                throw new ParseException(args[0] + " is not a valid integer value.", 0);
            }
        }
    
        //Let's parse user input... What could go wrong?... xD
        private static String parseUnitPart(final String text) throws ParseException {
            if (text == null)
                throw new ParseException("Text is null.", 0);
            if (text.trim().isEmpty())
                throw new ParseException("Text is empty.", 0);
            final String[] args = text.split(" ");
            if (args.length != 2)
                throw new ParseException("Text has invalid number of arguments (required 2, found " + args.length + ").", 0);
            final String unit = args[1].trim().toLowerCase();
            if (!unit.equals("inch") && !unit.equals("cm"))
                throw new ParseException("Nor 'inch', nor 'cm' detected.", 0);
            return unit;
        }
    }