接受 JSpinner 中的测量单位作为输入
Posted
技术标签:
【中文标题】接受 JSpinner 中的测量单位作为输入【英文标题】:Accept measuring units in JSpinner as input 【发布时间】:2014-02-20 00:24:37 【问题描述】:我有一个数字 JSpinner,它接受特定测量单位的值。现在,我想要一个特殊的 JSpinner 行为:如果用户输入一个数值并附加一个特定的测量单位字符串(例如“英寸”、“皮卡”),那么输入的数值必须转换为另一个值(取决于在单位字符串上)。当用户离开微调器字段(失去焦点)或以任何方式发生“commitEdit”时,必须发生这种转换。
我尝试了几种变体:自定义文档过滤器、自定义格式实例和用于微调器的 JFormattedTextField 的自定义文本字段文档。但我没有找到任何可能“挂钩”JFormattedTextField 的“commitEdit”方法调用。
实现我的要求的最佳方法是什么?有简单的方法吗?
【问题讨论】:
+1 我认为InputMask可以有不同的格式,数据类型,没试过,有两个更简单的选择JSpinner和ListModel(见Oracle教程--->一年中的月份)或者直接使用JComboBox 用 JSpinner 代替一些 woodoo 那么,您在 JSpinner 中有不同的单位,在 JFormattedTextfield 中有不同的值吗?可以不用JSpinner的statechanged事件吗? 我不能使用组合框。雇主要求 Spinner 组件具有 NumberModel 的所有优点(例如特定于语言环境的浮点数格式)。 令人惊讶的是,ChangeListener 就是解决方案!它使我能够在提交之前修改用户输入(如果有测量单位字符串)。我只需要将单位字符串部分和数字字符串部分分开。为了检测取决于浮点数格式的语言环境,我可以使用:new DecimalFormat("", DecimalFormatSymbols.getInstance(this.locale)).parse(input);
【参考方案1】:
还有其他东西使您能够在用户输入提交之前修改它:它是 commitEdit
方法本身(JFormattedTextField
的 DefaultEditor
987654325@)。在commitEdit
里面可以看到JFormattedTextField
的AbstractFormatter
的方法stringToValue
被调用了。这意味着如果您将自己的自定义AbstractFormatter
提供给文本字段,它可以将任何字符串转换为值,并将值转换为任何字符串。这里是异常发生的地方,以指示提交是否失败。
因此,按照您的要求,按照自定义 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 = "\\pBlank"; //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);
我使用了测量长度的单位(毫米、英寸、英尺和码)。但是您可以根据自己的需要进行调整。
注意在上面的实现中SpinnerNumberModel
只知道毫米。 AbstractFormatter
处理将毫米转换为其他单位并返回(根据用户的输入)。这意味着当您将单位设置为 YD(即码)时,模型仍将以毫米旋转,但 JFormattedTextField
将以码的分数旋转。试试看你自己我的意思。这意味着getValue()
中的JSpinner
/SpinnerNumberModel
将始终返回毫米数,无论文本字段中的单位是什么(AbstractFormatter
将始终进行转换)。
作为第二种情况,如果需要,您可以将转换移到AbstractFormatter
之外。例如,您可以让用户在微调器中输入一个始终独立于测量单位的值。这样,用户总是看到值以等于 1 的步长旋转(在此示例中),同时 AbstractFormatter
将保存用户设置为微调器的最后一个单位的属性。因此,现在当您从JSpinner
/SpinnerNumberModel
获取值时,您将获得一个独立于单位的数字,然后使用设置为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 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 = "\\pBlank"; //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);
至于你所说的语言环境,如果我理解正确的话,你想让逗号和点都在同一个微调器中运行吗?如果是这样,您可以检查答案here,这正是这个问题。在这种情况下,问题再次通过使用自定义AbstractFormatter
解决。
【讨论】:
以上是关于接受 JSpinner 中的测量单位作为输入的主要内容,如果未能解决你的问题,请参考以下文章
从 JSpinner 获取日期并放入具有 DATE 格式的 MySQL 数据库列