Java 接受JSpinner中的测量单位作为输入

Java 接受JSpinner中的测量单位作为输入,java,swing,units-of-measurement,jspinner,numberformatter,Java,Swing,Units Of Measurement,Jspinner,Numberformatter,我有一个数字JSpinner,它接受特定测量单位中的值。现在,我想要一个特殊的JSpinner行为:如果用户输入一个数值并附加一个特定的测量单位字符串(例如“inch”、“pica”),那么输入的数值必须转换成另一个值(取决于单位字符串)。此转换必须在用户离开微调器字段(焦点丢失)或以任何方式发生“CommitteIt”时发生 我尝试了几种变体:微调器的JFormattedTextField的自定义文档过滤器、自定义格式实例和自定义文本字段文档。但是我没有发现任何可能“钩住”JFormatted

我有一个数字JSpinner,它接受特定测量单位中的值。现在,我想要一个特殊的JSpinner行为:如果用户输入一个数值并附加一个特定的测量单位字符串(例如“inch”、“pica”),那么输入的数值必须转换成另一个值(取决于单位字符串)。此转换必须在用户离开微调器字段(焦点丢失)或以任何方式发生“CommitteIt”时发生

我尝试了几种变体:微调器的JFormattedTextField的自定义文档过滤器、自定义格式实例和自定义文本字段文档。但是我没有发现任何可能“钩住”JFormattedTextField的“CommittedIt”方法调用


实现我的需求的最佳方法是什么?有没有一种简单的方法可以做到这一点?

还有一种方法可以让您在提交用户输入之前修改它:它是
committedit
方法本身(属于
JSpinner
DefaultEditor
JFormattedTextField
)。在
committedit
中,您可以看到调用了
JFormattedTextField
AbstractFormatter
的方法
stringToValue
。这意味着,如果您将自己的自定义
AbstractFormatter
指定给文本字段,它可以将任何字符串转换为值,将值转换为任何字符串。这里是异常发生的地方,用于指示提交是否失败

因此,按照您的要求,使用自定义的
抽象格式化程序处理不同的单元:

import java.text.ParseException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JFormattedTextField;
import javax.swing.JFormattedTextField.AbstractFormatter;
import javax.swing.JFormattedTextField.AbstractFormatterFactory;
import javax.swing.JFrame;
import javax.swing.JSpinner;
import javax.swing.JSpinner.DefaultEditor;
import javax.swing.SpinnerNumberModel;

public class MilliMeterMain {

    public static enum Unit {
        MM(1), //Millimeters.
        IN(25.4), //Inches (1 inch == 25.4 mm).
        FT(25.4 * 12), //Feet (1 foot == 12 inches).
        YD(25.4 * 12 * 3); //Yards (1 yard == 3 feet).

        private final double factorToMilliMeters; //How much of this Unit composes a single millimeter.

        private Unit(final double factorToMilliMeters) {
            this.factorToMilliMeters = factorToMilliMeters;
        }

        public double getFactorToMilliMeters() {
            return factorToMilliMeters;
        }

        public double toMilliMeters(final double amount) {
            return amount * getFactorToMilliMeters();
        }

        public double fromMilliMeters(final double amount) {
            return amount / getFactorToMilliMeters();
        }
    }

    public static class UnitFormatter extends AbstractFormatter {

        private static final Pattern PATTERN;

        static {
            //Building the Pattern is not too tricky. It just needs some attention.

            final String blank = "\\p{Blank}"; //Match any whitespace character.
            final String blankGroupAny = "(" + blank + "*)"; //Match any whitespace character, zero or more times and group them.

            final String digits = "\\d"; //Match any digit.
            final String digitsGroup = "(" + digits + "+)"; //Match any digit, at least once and group them.
            final String digitsSuperGroup = "(\\-?" + digitsGroup + "\\.?" + digitsGroup + "?)"; //Matches for example "-2.4" or "2.4" or "2" or "-2" in the same group!

            //Create the pattern part which matches any of the available units...
            final Unit[] units = Unit.values();
            final StringBuilder unitsBuilder = new StringBuilder(Pattern.quote("")); //Empty unit strings are valid (they default to millimeters).
            for (int i = 0; i < units.length; ++i)
                unitsBuilder.append('|').append(Pattern.quote(units[i].name()));
            final String unitsGroup = "(" + unitsBuilder + ")";

            final String full = "^" + blankGroupAny + digitsSuperGroup + blankGroupAny + unitsGroup + blankGroupAny + "$"; //Compose full pattern.

            PATTERN = Pattern.compile(full);
        }

        private Unit lastUnit = Unit.MM;

        @Override
        public Object stringToValue(final String text) throws ParseException {
            if (text == null || text.trim().isEmpty())
                throw new ParseException("Null or empty text.", 0);
            try {
                final Matcher matcher = PATTERN.matcher(text.toUpperCase());
                if (!matcher.matches())
                    throw new ParseException("Invalid input.", 0);
                final String amountStr = matcher.group(2),
                             unitStr = matcher.group(6);
                final double amount = Double.parseDouble(amountStr);
                lastUnit = unitStr.trim().isEmpty()? null: Unit.valueOf(unitStr);
                return lastUnit == null? amount: lastUnit.toMilliMeters(amount);
            }
            catch (final IllegalArgumentException iax) {
                throw new ParseException("Failed to parse input \"" + text + "\".", 0);
            }
        }

        @Override
        public String valueToString(final Object value) throws ParseException {
            final double amount = lastUnit == null? (Double) value: lastUnit.fromMilliMeters((Double) value);
            return String.format("%.4f", amount).replace(',', '.') + ((lastUnit == null)? "": (" " + lastUnit.name()));
        }
    }

    public static class UnitFormatterFactory extends AbstractFormatterFactory {
        @Override
        public AbstractFormatter getFormatter(final JFormattedTextField tf) {
            if (!(tf.getFormatter() instanceof UnitFormatter))
                return new UnitFormatter();
            return tf.getFormatter();
        }
    }

    public static void main(final String[] args) {
        final JSpinner spin = new JSpinner(new SpinnerNumberModel(0d, -1000000d, 1000000d, 1d)); //Default numbers in millimeters.

        ((DefaultEditor) spin.getEditor()).getTextField().setFormatterFactory(new UnitFormatterFactory());

        final JFrame frame = new JFrame("JSpinner infinite value");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(spin);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

至于你说的区域设置,如果我理解正确的话,你想让逗号和点在同一个微调器中运行吗?如果是这样的话,你可以检查一下答案。在这种情况下,问题再次通过使用自定义的
抽象格式化程序

+1解决了。我认为InputMask可以有不同的格式、数据类型,从未尝试过,有两个更简单的选项,JSpinner和ListModel(见Oracle教程--->一年中的一个月),或者直接使用JComboBox,而不是JSpinnerSo的woodoo,JSpinner中有不同的单位,JFormattedTextfield中有不同的值?您不能使用JSpinner的statechanged事件吗?我不能使用组合框。雇主要求一个具有NumberModel所有优点(如特定于语言环境的浮点数格式)的微调器组件。令人惊讶的是,ChangeListener是解决方案!它使我能够在提交之前修改用户输入(如果有测量单位字符串)。我只需要将单位字符串部分和数字字符串部分分开。为了检测依赖于区域设置的浮点数格式,我可以使用:
newdecimalformat(“,DecimalFormatSymbols.getInstance(this.locale)).parse(input)
import java.text.ParseException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JFormattedTextField;
import javax.swing.JFormattedTextField.AbstractFormatter;
import javax.swing.JFormattedTextField.AbstractFormatterFactory;
import javax.swing.JFrame;
import javax.swing.JSpinner;
import javax.swing.JSpinner.DefaultEditor;
import javax.swing.SpinnerNumberModel;

public class StepMain {

    public static enum Unit {
        MM, //Milimeters.
        IN, //Inches (1 inch == 25.4 mm).
        FT, //Feet (1 foot == 12 inches).
        YD; //Yards (1 yard == 3 feet).
    }

    public static class UnitFormatter extends AbstractFormatter {

        private static final Pattern PATTERN;

        static {
            //Building the Pattern is not too tricky. It just needs some attention.

            final String blank = "\\p{Blank}"; //Match any whitespace character.
            final String blankGroupAny = "(" + blank + "*)"; //Match any whitespace character, zero or more times and group them.

            final String digits = "\\d"; //Match any digit.
            final String digitsGroup = "(" + digits + "+)"; //Match any digit, at least once and group them.
            final String digitsSuperGroup = "(\\-?" + digitsGroup + "\\.?" + digitsGroup + "?)"; //Matches for example "-2.4" or "2.4" or "2" or "-2" in the same group!

            //Create the pattern part which matches any of the available units...
            final Unit[] units = Unit.values();
            final StringBuilder unitsBuilder = new StringBuilder(Pattern.quote("")); //Empty unit strings are valid (they default to milimeters).
            for (int i = 0; i < units.length; ++i)
                unitsBuilder.append('|').append(Pattern.quote(units[i].name()));
            final String unitsGroup = "(" + unitsBuilder + ")";

            final String full = "^" + blankGroupAny + digitsSuperGroup + blankGroupAny + unitsGroup + blankGroupAny + "$"; //Compose full pattern.

            PATTERN = Pattern.compile(full);
        }

        private Unit lastUnit = Unit.MM;

        @Override
        public Object stringToValue(final String text) throws ParseException {
            if (text == null || text.trim().isEmpty())
                throw new ParseException("Null or empty text.", 0);
            try {
                final Matcher matcher = PATTERN.matcher(text.toUpperCase());
                if (!matcher.matches())
                    throw new ParseException("Invalid input.", 0);
                final String amountStr = matcher.group(2),
                             unitStr = matcher.group(6);
                final double amount = Double.parseDouble(amountStr);
                lastUnit = Unit.valueOf(unitStr);
                return amount;
            }
            catch (final IllegalArgumentException iax) {
                throw new ParseException("Failed to parse input \"" + text + "\".", 0);
            }
        }

        @Override
        public String valueToString(final Object value) throws ParseException {
            return String.format("%.3f", value).replace(',', '.') + ' ' + lastUnit.name();
        }
    }

    public static class UnitFormatterFactory extends AbstractFormatterFactory {
        @Override
        public AbstractFormatter getFormatter(final JFormattedTextField tf) {
            if (!(tf.getFormatter() instanceof UnitFormatter))
                return new UnitFormatter();
            return tf.getFormatter();
        }
    }

    public static void main(final String[] args) {
        final JSpinner spin = new JSpinner(new SpinnerNumberModel(0d, -1000000d, 1000000d, 0.001d));

        ((DefaultEditor) spin.getEditor()).getTextField().setFormatterFactory(new UnitFormatterFactory());

        final JFrame frame = new JFrame("JSpinner infinite value");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(spin);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}