make custom JTextField (JCurrencyField)

我的梦境 提交于 2019-12-13 00:52:52

问题


I making a JCurrencyField to use it in my program, I almost finished creating it but I've faced an issue.

JCurrencyField is a normal JTextField but it cannot accept any character that is not a number or decimal point . and I made some specifications for this JCurrencyField:

  • The user cannot insert more than one decimal point ..
  • The user cannot insert more than two digits after the decimal point.

The issue I've faced was not being able to insert any digits before the decimal point if there are two digits after the decimal point.

Here is my code:

import javax.swing.*;
import javax.swing.text.*;
public class JCurrencyField extends JTextField
{
    public JCurrencyField()
    {
        ((AbstractDocument)getDocument()).setDocumentFilter(new NumberOnlyFilter());
    }

    private class NumberOnlyFilter extends DocumentFilter
    {
        public void insertString(DocumentFilter.FilterBypass fb, int offset,   
        String text, AttributeSet attr) throws BadLocationException
        {
            if(!containsOnlyNumbers(text)) return;
            fb.insertString(offset, text, attr);   
        }    

        public void replace(DocumentFilter.FilterBypass fb, int offset, int length,   
        String text, AttributeSet attr) throws BadLocationException
        {
            if(!containsOnlyNumbers(text)) return;
            fb.replace(offset, length, text, attr);
        }

        /**
        * This method checks if a String contains only numbers
        */
        public boolean containsOnlyNumbers(String str)
        {

            //It can't contain only numbers if it's null or empty...
            if (str == null || str.length() == 0) return false;


            int counter = 0;
            for(int i = 0; i < getText().length(); i++)
            {
                if(counter > 1) return false;
                if(getText().charAt(i) == '.')
                {
                    counter++;
                }
            }

            int fp_counter = 0;
            int fp_index = -1;
            for(int i = 0; i < str.length(); i++)
            {
                if(counter >= 1) break;
                if(str.charAt(i) == '.')
                {
                    fp_counter++;
                    fp_index = i;
                }
            }

            if(fp_counter > 1) return false;

            if(str.length() > fp_index + 3) return false;

            if(counter >= 1)
            {
                for(int i = 0; i < getText().length(); i++)
                {
                    if(getText().charAt(i) == '.') counter++;
                }
            }

            for (int j = 0; j < str.length(); j++)
            {
                if(counter >= 1 && (str.charAt(j) == '.')) return false;
            }

            int index = 0;
            boolean fp_Flag = false;
            int sp_count = 0;
            for(int k = 0; k < getText().length(); k++)
            {
                if(getText().charAt(k) == '.')
                {
                    index = k;
                    fp_Flag = true;
                }
                if(fp_Flag) sp_count++;
                if(sp_count > 2) return false;
            }

            //if(fp_Flag && str.length() > 2) return false;
            if(fp_Flag && index + 1 < getText().length() && str.length() > 1) return false;
            //if(index + 2 < getText().length() && fp_Flag) return false;


            for (int l = 0; l < str.length(); l++)
            {

                //If we find a non-digit character we return false.
                if(str.charAt(l) == '.') continue;
                if(!Character.isDigit(str.charAt(l)))
                return false;
            }

            return true;
        }
    }
}

Test Program:

import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
public class Test extends JFrame

{
    private JCurrencyField txt = new JCurrencyField();
    public Test()
    {
        super("Test...");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        try{ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());}
        catch(Exception e){ System.out.println("Unable to load Windows look and feel");}
        setPreferredSize(new Dimension(300, 100));
        ((JPanel) getContentPane()).setBorder(new EmptyBorder(13, 13, 13, 13) );
        setLayout(new FlowLayout());
        txt.setPreferredSize(new Dimension(100,30));
        add(txt);
        pack();
        setLocationRelativeTo(null);
        setVisible(true);
        setResizable(false);
    }
    public static void main(String[] args)
    {
        new Test();
    }
}

could you please help me to solve this issue?


回答1:


You're testing the wrong string in your DocumentFilter.

You shouldn't be testing the text String but rather the String built from text and Document obtained from the FilterBypass parameter since this is the String that you want to see if it fulfills the criteria. For instance, not this:

    public void insertString(DocumentFilter.FilterBypass fb, int offset,   
    String text, AttributeSet attr) throws BadLocationException
    {
        if(!containsOnlyNumbers(text)) return;
        fb.insertString(offset, text, attr);   
    }    

    public void replace(DocumentFilter.FilterBypass fb, int offset, int length,   
    String text, AttributeSet attr) throws BadLocationException
    {
        if(!containsOnlyNumbers(text)) return;
        fb.replace(offset, length, text, attr);
    }

But rather this:

   private class NumberOnlyFilter extends DocumentFilter {
      public void insertString(DocumentFilter.FilterBypass fb, int offset,
               String text, AttributeSet attr) throws BadLocationException {
         StringBuilder sb = new StringBuilder();
         sb.append(fb.getDocument().getText(0, fb.getDocument().getLength()));
         sb.insert(offset, text);
         if (!containsOnlyNumbers(sb.toString()))
            return;
         fb.insertString(offset, text, attr);
      }

      public void replace(DocumentFilter.FilterBypass fb, int offset,
               int length, String text, AttributeSet attr)
               throws BadLocationException {
         StringBuilder sb = new StringBuilder();
         sb.append(fb.getDocument().getText(0, fb.getDocument().getLength()));
         sb.replace(offset, offset + length, text);
         if (!containsOnlyNumbers(sb.toString()))
            return;
         fb.replace(offset, length, text, attr);
      }

You will need to make changes to your containsOnlyNumbers method to account for these changes.

Edit 1
And this can be easily done using toto's regex:

  private boolean containsOnlyNumbers(String text) {
     Pattern pattern = Pattern.compile("\\d*(\\.\\d{0,2})?");
     Matcher matcher = pattern.matcher(text);
     boolean isMatch = matcher.matches();
     return isMatch;
  }



回答2:


it's better to use JFormattedTextField with NumberFormat java.text.NumberFormat#getCurrencyInstance()




回答3:


The following regex should work:

Pattern pattern = Pattern.compile("\\d*(\\.\\d{0,2})?");
Matcher matcher = pattern.matcher(text);
boolean isMatch = matcher.matches();
if (isMatch) 
  value = Double.parseDouble(text);

However it would also match the empty string, so you have to check for that separately, if you don't it. (I don't know if parseDouble returns 0 for an empty string.)

EDIT: the comment of @mkorbel about using java.swing.text.NumberFormatter with java.text.NumberFormat is a better solution than mine. You'll probably shrink your code by a factor ~100... that's what happens when you know your libraries.



来源:https://stackoverflow.com/questions/6548167/make-custom-jtextfield-jcurrencyfield

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!