Есть еще кое-что, что позволяет вам изменить пользовательский ввод до того, как он будет зафиксирован: это сам метод commitEdit
(из JFormattedTextField
из DefaultEditor
из JSpinner
). Внутри commitEdit
вы можете видеть, что вызывается метод stringToValue
из JFormattedTextField
AbstractFormatter
. Это означает, что если вы зададите свой собственный 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 = "\\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);
}
}
Я использовал единицы измерения длины (миллиметры, дюймы, футы и ярды). Но вы можете адаптировать это под свои нужды.
Обратите внимание, что в приведенной выше реализации 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 = "\\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);
}
}
Что касается локали, которую вы сказали, если я правильно понял, вы хотите, чтобы запятые и точки работали в одном счетчике? Если это так, вы можете проверить ответ здесь, который как раз об этом. В этом случае проблема снова решается с помощью пользовательского AbstractFormatter
.
person
gthanop
schedule
06.01.2020
new DecimalFormat("", DecimalFormatSymbols.getInstance(this.locale)).parse(input);
- person PAX   schedule 29.01.2014