JFormattedTextField格式化百分比数字?

我想使用JFormattedTextField将浮点数格式化为百分比值,允许输入从0到100%(转换为0.0f-1.0f),始终显示百分号并禁止任何无效字符。

现在我用NumberFormat.getPercentInstance()和NumberFormatter属性进行了一些实验,但没有成功。

有没有办法创建一个遵循标准类遵守这些规则的JFormattedTextField? 或者我必须实现自己的NumberFormatter?

这就是我到目前为止(无法输入100%,输入0完全打破它):

public class MaskFormatterTest { public static void main(String[] args) throws Exception { JFrame frame = new JFrame("Test"); frame.setLayout(new BorderLayout()); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); NumberFormat format = NumberFormat.getPercentInstance(); NumberFormatter formatter = new NumberFormatter(format); formatter.setMaximum(1.0f); formatter.setMinimum(0.0f); formatter.setAllowsInvalid(false); formatter.setOverwriteMode(true); JFormattedTextField tf = new JFormattedTextField(formatter); tf.setColumns(20); tf.setValue(0.56f); frame.add(tf); frame.pack(); frame.setVisible(true); } } 

好的,我已经成功了。 解决方案远非简单,但至少它完全符合我的要求。 除了返回双打而不是浮动。 一个主要的限制是它不允许小数位数,但是现在我可以忍受它。

 import java.awt.BorderLayout; import java.text.NumberFormat; import java.text.ParseException; import javax.swing.JComponent; import javax.swing.JFormattedTextField; import javax.swing.JSpinner; import javax.swing.SpinnerNumberModel; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultFormatterFactory; import javax.swing.text.DocumentFilter; import javax.swing.text.NavigationFilter; import javax.swing.text.NumberFormatter; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.Position.Bias; public class JPercentField extends JComponent { private static final double MIN_VALUE = 0.0d; private static final double MAX_VALUE = 1.0d; private static final double STEP_SIZE = 0.01d; private static final long serialVersionUID = -779235114254706347L; private JSpinner spinner; public JPercentField() { initComponents(); initLayout(); spinner.setValue(MIN_VALUE); } private void initComponents() { SpinnerNumberModel model = new SpinnerNumberModel(MIN_VALUE, MIN_VALUE, MAX_VALUE, STEP_SIZE); spinner = new JSpinner(model); initSpinnerTextField(); } private void initSpinnerTextField() { DocumentFilter digitOnlyFilter = new PercentDocumentFilter(getMaximumDigits()); NavigationFilter navigationFilter = new BlockLastCharacterNavigationFilter(getTextField()); getTextField().setFormatterFactory( new DefaultFormatterFactory(new PercentNumberFormatter(createPercentFormat(), navigationFilter, digitOnlyFilter))); getTextField().setColumns(6); } private int getMaximumDigits() { return Integer.toString((int) MAX_VALUE * 100).length(); } private JFormattedTextField getTextField() { JSpinner.NumberEditor jsEditor = (JSpinner.NumberEditor) spinner.getEditor(); JFormattedTextField textField = jsEditor.getTextField(); return textField; } private NumberFormat createPercentFormat() { NumberFormat format = NumberFormat.getPercentInstance(); format.setGroupingUsed(false); format.setMaximumIntegerDigits(getMaximumDigits()); format.setMaximumFractionDigits(0); return format; } private void initLayout() { setLayout(new BorderLayout()); add(spinner, BorderLayout.CENTER); } public double getPercent() { return (Double) spinner.getValue(); } public void setPercent(double percent) { spinner.setValue(percent); } private static class PercentNumberFormatter extends NumberFormatter { private static final long serialVersionUID = -1172071312046039349L; private final NavigationFilter navigationFilter; private final DocumentFilter digitOnlyFilter; private PercentNumberFormatter(NumberFormat format, NavigationFilter navigationFilter, DocumentFilter digitOnlyFilter) { super(format); this.navigationFilter = navigationFilter; this.digitOnlyFilter = digitOnlyFilter; } @Override protected NavigationFilter getNavigationFilter() { return navigationFilter; } @Override protected DocumentFilter getDocumentFilter() { return digitOnlyFilter; } @Override public Class getValueClass() { return Double.class; } @Override public Object stringToValue(String text) throws ParseException { Double value = (Double) super.stringToValue(text); return Math.max(MIN_VALUE, Math.min(MAX_VALUE, value)); } } /** * NavigationFilter that avoids navigating beyond the percent sign. */ private static class BlockLastCharacterNavigationFilter extends NavigationFilter { private JFormattedTextField textField; private BlockLastCharacterNavigationFilter(JFormattedTextField textField) { this.textField = textField; } @Override public void setDot(FilterBypass fb, int dot, Bias bias) { super.setDot(fb, correctDot(fb, dot), bias); } @Override public void moveDot(FilterBypass fb, int dot, Bias bias) { super.moveDot(fb, correctDot(fb, dot), bias); } private int correctDot(FilterBypass fb, int dot) { // Avoid selecting the percent sign int lastDot = Math.max(0, textField.getText().length() - 1); return dot > lastDot ? lastDot : dot; } } private static class PercentDocumentFilter extends DocumentFilter { private int maxiumDigits; public PercentDocumentFilter(int maxiumDigits) { super(); this.maxiumDigits = maxiumDigits; } @Override public void insertString(FilterBypass fb, int offset, String text, AttributeSet attrs) throws BadLocationException { // Mapping an insert as a replace without removing replace(fb, offset, 0, text, attrs); } @Override public void remove(FilterBypass fb, int offset, int length) throws BadLocationException { // Mapping a remove as a replace without inserting replace(fb, offset, length, "", SimpleAttributeSet.EMPTY); } @Override public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { int replaceLength = correctReplaceLength(fb, offset, length); String cleanInput = truncateInputString(fb, filterDigits(text), replaceLength); super.replace(fb, offset, replaceLength, cleanInput, attrs); } /** * Removes all non-digit characters */ private String filterDigits(String text) throws BadLocationException { StringBuilder sb = new StringBuilder(text); for (int i = 0, n = sb.length(); i < n; i++) { if (!Character.isDigit(text.charAt(i))) { sb.deleteCharAt(i); } } return sb.toString(); } /** * Removes all characters with which the resulting text would exceed the maximum number of digits */ private String truncateInputString(FilterBypass fb, String filterDigits, int replaceLength) { StringBuilder sb = new StringBuilder(filterDigits); int currentTextLength = fb.getDocument().getLength() - replaceLength - 1; for (int i = 0; i < sb.length() && currentTextLength + sb.length() > maxiumDigits; i++) { sb.deleteCharAt(i); } return sb.toString(); } private int correctReplaceLength(FilterBypass fb, int offset, int length) { if (offset + length >= fb.getDocument().getLength()) { // Don't delete the percent sign return offset + length - fb.getDocument().getLength(); } return length; } } } 

1)考虑使用JSpinner而不是JFormattedTextField因为你可以为初始值设置SpinnerNumberModel

来自API

 Integer value = new Integer(50); Integer min = new Integer(0); Integer max = new Integer(100); Integer step = new Integer(1); 

并且对于JSpinner (使用SpinnerNumberModel )的简单hack,它不允许另一个输入为Digits,否则可能输入任何Chars

2)对于JFormattedTextField你必须实现

  • 的DocumentListener
  • 文献

对于JFormattedTextField的两种情况,如果值小于或大于所需范围,则必须为catch编写解决方法…

编辑:

在此处输入图像描述

根本不是真的,:-)你这么远……简单的错误:-),你的结果有小错误,请看这段代码

 import java.awt.BorderLayout; import java.text.NumberFormat; import javax.swing.*; import javax.swing.text.*; public class TestDigitsOnlySpinner { public static void main(String... args) { SwingUtilities.invokeLater(new Runnable() { public void run() { JFrame frame = new JFrame("enter digit"); JSpinner jspinner = makeDigitsOnlySpinnerUsingDocumentFilter(); frame.getContentPane().add(jspinner, BorderLayout.CENTER); frame.getContentPane().add(new JButton("just another widget"), BorderLayout.SOUTH); frame.pack(); frame.setVisible(true); } private JSpinner makeDigitsOnlySpinnerUsingDocumentFilter() { JSpinner spinner = new JSpinner(new SpinnerNumberModel()); JSpinner.NumberEditor jsEditor = (JSpinner.NumberEditor) spinner.getEditor(); JFormattedTextField textField = jsEditor.getTextField(); final DocumentFilter digitOnlyFilter = new DocumentFilter() { @Override public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException { if (stringContainsOnlyDigits(string)) { super.insertString(fb, offset, string, attr); } } @Override public void remove(FilterBypass fb, int offset, int length) throws BadLocationException { super.remove(fb, offset, length); } @Override public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { if (stringContainsOnlyDigits(text)) { super.replace(fb, offset, length, text, attrs); } } private boolean stringContainsOnlyDigits(String text) { for (int i = 0; i < text.length(); i++) { if (!Character.isDigit(text.charAt(i))) { return false; } } return true; } }; /*NumberFormat format = NumberFormat.getIntegerInstance(); format.setGroupingUsed(false);// or add the group chars to the filter NumberFormat format = NumberFormat.getInstance();*/ NumberFormat format = NumberFormat.getPercentInstance(); format.setGroupingUsed(false); format.setGroupingUsed(true);// or add the group chars to the filter format.setMaximumIntegerDigits(10); format.setMaximumFractionDigits(2); format.setMinimumFractionDigits(5); textField.setFormatterFactory(new DefaultFormatterFactory(new InternationalFormatter(format) { private static final long serialVersionUID = 1L; @Override protected DocumentFilter getDocumentFilter() { return digitOnlyFilter; } })); return spinner; } }); } } 

Imho https://docs.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html给出了一个很好的例子(请参阅“指定格式化程序和使用Formatter工厂”一节)。

关键是使用百分比格式显示值,使用自定义NumberFormatter来编辑值。 该方法还允许使用分数位。

 // create a format for displaying percentages (with %-sign) NumberFormat percentDisplayFormat = NumberFormat.getPercentInstance(); // create a format for editing percentages (without %-sign) NumberFormat percentEditFormat = NumberFormat.getNumberInstance(); // create a formatter for editing percentages - input will be transformed to percentages (eg. 50 -> 0.5) NumberFormatter percentEditFormatter = new NumberFormatter(percentEditFormat) { private static final long serialVersionUID = 1L; @Override public String valueToString(Object o) throws ParseException { Number number = (Number) o; if (number != null) { double d = number.doubleValue() * 100.0; number = new Double(d); } return super.valueToString(number); } @Override public Object stringToValue(String s) throws ParseException { Number number = (Number) super.stringToValue(s); if (number != null) { double d = number.doubleValue() / 100.0; number = new Double(d); } return number; } }; // set allowed range percentEditFormatter.setMinimum(0D); percentEditFormatter.setMaximum(100D); // create JFormattedTextField JFormattedTextField field = new JFormattedTextField( new DefaultFormatterFactory( new NumberFormatter(percentDisplayFormat), new NumberFormatter(percentDisplayFormat), percentEditFormatter));