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) == '开发者_如何学Python.')
{
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?
it's better to use JFormattedTextField
with NumberFormat
java.text.NumberFormat#getCurrencyInstance()
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;
}
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.
精彩评论