2011-09-27 144 views
2

我想用JFormattedTextField格式化一个浮点数作为百分比值,它允许0到100%的输入(转换为0.0f-1.0f),总是显示百分号并且不允许任何无效字符。JFormattedTextField格式化百分比数字?

现在我已经尝试了一下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); 
    } 
} 
+0

什么是您的默认语言环境,您是否根据该语言环境输入文本? – Thomas

+0

作为一个方面说明:您可以使用带有SpinnerNumberModel的JSpinner,这也会更加方便用户。 – Thomas

+0

我的默认语言环境是DE_DE,但我不知道这应该如何改变我输入文本的方式,因为文本字段只显示没有分组字符的整数值。 JSpinner是个不错的主意 - 我绝对会去尝试一下。 –

回答

3

好吧,我已经做到了。解决方案远非简单,但至少它确实是我想要的。除了返回双打而不是浮动。一个主要限制是它不允许小数位,但现在我可以忍受。

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; 
     } 
    } 

} 
+0

真的这么长时间代码我的+1分发解决方案 – mKorbel

2

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),它不会允许另一个输入作为数字,否则是有可能的输入JFormattedTextField任何Chars

2),你必须实现

  • 的DocumentListener
  • 文件

和这两种情况下为JFormattedTextField你必须编写捕捉解决方案,如果值小于或大于所需范围...

编辑:

enter image description here

不是真的,你是:-)从...简单的错误:-)到目前为止,有小失误与你的结果,请看看这段代码

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; 
      } 
     }); 
    } 
} 
+0

显然,不可能得到一个组件,其行为与我在我的问题中描述的相似。至少不是标准的Swing组件或一些简单的技巧。我现在使用范围从0到100的JSpinner(如您所建议的那样),将值转换为手动浮动并忽略百分号的缺失。在嵌入式JFormattedTextField的NumberFormatter中将allowInvalid设置为false并将overwriteMode设置为true,则行为可以满足我的需求。 –

+0

@Roland请看我的编辑 – mKorbel

+0

谢谢。但是这个代码仍然存在一些问题。当百分号或小数点分隔符被删除时,无法将它们还原。两者都可以在DocumentFilter中修复几个ifs。为这样一个简单的任务做了很多努力。 ;) –

1

恕我直言https://docs.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html给出了一个相当不错的例子(见“指定格式化程序和使用格式化工厂”)。

关键是要使用百分比格式来显示值和自定义的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));