I want to format a number as the user enters the number in an edit field.
I am using below code to format the number as the user changes focus to another control, using the onfocus()
function:
public static String formatNumber(double number, int decimals, String digitGrouping){
Formatter f = new Formatter("en");
String rawNumber = f.formatNumber(number, decimals+1);
String rawIntString = rawNumber.substring(0, rawNumber.indexOf(".")); //Basically intString without digit grouping
StringBuffer intString = new StringBuffer();
StringBuffer decString = new StringBuffer(rawNumber.substring(rawNumber.indexOf(".")+1));
StringBuffer formattedNumber = new StringBuffer();
int workingVal = 0;
int newNum = 0;
boolean roundNext;
//Add digit grouping
int grouplen = 0;
int firstDigit;
if(rawIntString.charAt(0) == '-'){
firstDigit = 1;
}else{
firstDigit = 0;
}
for(int n=rawIntString.length()-1;n>=firstDigit;n--){
intString.insert(0, rawIntString.substring(n, n+1));
grouplen++;
if(grouplen == 3 && n>firstDigit){
intString.insert(0, digitGrouping);
grouplen = 0;
}
}
//First, check the last digit
workingVal = Integer.parseInt(String.valueOf(decString.charAt(decString.length()-1)));
if(workingVal>=5){
roundNext = true;
}else{
roundNext = false;
}
//Get the decimal values, round if needed, and add to formatted string buffer
for(int n=decString.length()-2;n>=0;n--){
workingVal = Integer.parseInt(String.valueOf(decString.charAt(n)));
if(roundNext == true){
newNum = workingVal + 1;
if(newNum == 10){
roundNext = true;
newNum = 0;
}else{
roundNext = false;
}
formattedNumber.insert(0, newNum);
}else{
formattedNumber.insert(0, workingVal);
}
}
//Now get the integer values, round if needed, and add to formatted string buffer
formattedNumber.insert(0, ".");
for(int n=intString.length()-1;n>=0;n--){
try{
workingVal = Integer.parseInt(String.valueOf(intString.charAt(n)));
}catch(Exception e){
formattedNumber.insert(0, intString.charAt(n));
continue;
}
if(roundNext == true){
newNum = workingVal + 1;
if(newNum == 10){
roundNext = true;
newNum = 0;
}else{
roundNext = false;
}
formattedNumber.insert(0, newNum);
}else{
formattedNumber.insert(0, workingVal);
}
}
//Just in case its a number like 9999.99999 (if it rounds right to the end
if(roundNext == true){
formattedNumber.insert(0, 1);
}
//re-add the minus sign if needed
if(firstDigit == 1) formattedNumber.insert(0, rawIntString.charAt(0));
if(digitGrouping.length() > 0){
if(formattedNumber.toString().indexOf(".") == -1){
//no decimal
if(formattedNumber.toString().indexOf(digitGrouping) > 3+firstDigit){
formattedNumber.insert(1+firstDigit, digitGrouping);
}
if(formattedNumber.toString().length() == 4+firstDigit){
formattedNumber.insert(1+firstDigit, digitGrouping);
}
}else{
//no decimal
if(formattedNumber.toString().indexOf(digitGrouping) > 3+firstDigit){
formattedNumber.insert(1+firstDigit, digitGrouping);
}
String intportion = formattedNumber.toString().substring(0, formattedNumber.toString().indexOf("."));
if(intportion.length() == 4+firstDigit){
formattedNumber.insert(1+firstDigit, digitGrouping);
}
}
}
//now remove trailing zeros
String tmp = formattedNumber.toString();
int newLength = tmp.length();
for(int n=tmp.length()-1;n>=0;n--){
if(tmp.substring(n, n+1).equalsIgnoreCase("0")){
newLength--;
}else{
if(tmp.substring(n, n+1).equalsIgnoreCase(".")) newLength--;
break;
}
}
formattedNumber.setLength(newLength);
return formattedNumber.toString();
}
This doesn't solve the problem of formatting the numbers as the user types, though.
The obvious solution to me is to override the paint method of the EditField so that its displayed as formatted text. The field's getText would return "12345" while it would display as "12 345" for example.
This is almost perfect for what you need. But you need to remember to handle the cursor drawing as well. You don't want the native cursor on the 5 but drawing on the 3.
EDIT
This should help you out. The important parts here are paint
and getFocusRect
. Please note that this is pretty rough still. It doesn't handle touch events (cursor won't move correctly), and there will be no line wrapping (must be handled by layout
and paint
methods).
public class FormattedEditField extends TextField
{
public FormattedEditField()
{
setFilter(TextFilter.get(TextFilter.NUMERIC));
}
protected String getFormattedText()
{
String text = getText();
if ((text == null) || (text.length() == 0))
{
return "";
}
else
{
double nr = Double.parseDouble(text);
return formatNumber(nr, 2, ",");
}
}
public synchronized void getFocusRect(XYRect rect)
{
// Tell Blackberry where to draw the cursor
Font font = getFont();
String formattedText = getFormattedText();
int length = formattedText.length();
if (length == 0)
{
rect.x = getTextOriginX();
rect.width = font.getAdvance('1');
rect.y = getTextOriginY();
rect.height = font.getHeight();
}
else
{
int cursorPosition = getCursorPosition();
int i;
for (i = 0; i <= cursorPosition; i++)
{
if (cursorPosition == length)
{
break;
}
if (formattedText.charAt(i) == ',')
{
cursorPosition++;
}
}
if (cursorPosition >= length)
{
rect.x = getTextOriginX() + font.getAdvance(formattedText);
rect.width = font.getAdvance('1');
}
else
{
rect.x = getTextOriginX() + font.getAdvance(formattedText.substring(0, cursorPosition));
rect.width = font.getAdvance(formattedText.charAt(cursorPosition));
}
rect.y = getTextOriginY();
rect.height = font.getHeight();
}
}
protected void drawFocus(Graphics graphics, boolean on)
{
}
protected synchronized void paint(Graphics graphics)
{
int x = getTextOriginX();
int y = getTextOriginY();
String text = getFormattedText();
if (isFocus())
{
XYRect rect = new XYRect();
getFocusRect(rect);
int colour = graphics.getColor();
graphics.setColor(0x207CFE);
graphics.fillRect(rect.x, rect.y, rect.width, rect.height);
graphics.setColor(colour);
graphics.drawText(text, x, y);
graphics.invert(rect);
}
else
{
graphics.drawText(text, x, y);
}
}
// Since we are overriding the painting, we should accommodate the different drawing flags
private int getTextOriginX()
{
int x = 0;
if ((getStyle() & DrawStyle.HCENTER) == DrawStyle.HCENTER)
{
x = (getWidth() - getFont().getAdvance(getFormattedText())) / 2;
}
else if ((getStyle() & DrawStyle.RIGHT) == DrawStyle.RIGHT)
{
x = getWidth() - getFont().getAdvance(getFormattedText());
}
return x;
}
// Since we are overriding the painting, we should accommodate the different drawing flags
private int getTextOriginY()
{
int y = 0;
if ((getStyle() & DrawStyle.VCENTER) == DrawStyle.VCENTER)
{
y = (getHeight() - getFont().getHeight()) / 2;
}
else if ((getStyle() & DrawStyle.BOTTOM) == DrawStyle.BOTTOM)
{
y = getHeight() - getFont().getHeight();
}
return y;
}
// This method is from http://supportforums.blackberry.com/t5/Java-Development/Format-a-decimal-number/m-p/763981#M142257
// I don't know how good it is, since I use my own method for this
private String formatNumber(double number, int decimals, String digitGrouping)
{
Formatter f = new Formatter("en");
String rawNumber = f.formatNumber(number, decimals + 1);
String rawIntString = rawNumber.substring(0, rawNumber.indexOf(".")); // Basically intString without digit grouping
StringBuffer intString = new StringBuffer();
StringBuffer decString = new StringBuffer(rawNumber.substring(rawNumber.indexOf(".") + 1));
StringBuffer formattedNumber = new StringBuffer();
int workingVal = 0;
int newNum = 0;
boolean roundNext;
// Add digit grouping
int grouplen = 0;
int firstDigit;
if (rawIntString.charAt(0) == '-')
{
firstDigit = 1;
}
else
{
firstDigit = 0;
}
for (int n = rawIntString.length() - 1; n >= firstDigit; n--)
{
intString.insert(0, rawIntString.substring(n, n + 1));
grouplen++;
if (grouplen == 3 && n > firstDigit)
{
intString.insert(0, digitGrouping);
grouplen = 0;
}
}
// First, check the last digit
workingVal = Integer.parseInt(String.valueOf(decString.charAt(decString.length() - 1)));
if (workingVal >= 5)
{
roundNext = true;
}
else
{
roundNext = false;
}
// Get the decimal values, round if needed, and add to formatted string buffer
for (int n = decString.length() - 2; n >= 0; n--)
{
workingVal = Integer.parseInt(String.valueOf(decString.charAt(n)));
if (roundNext == true)
{
newNum = workingVal + 1;
if (newNum == 10)
{
roundNext = true;
newNum = 0;
}
else
{
roundNext = false;
}
formattedNumber.insert(0, newNum);
}
else
{
formattedNumber.insert(0, workingVal);
}
}
// Now get the integer values, round if needed, and add to formatted string buffer
formattedNumber.insert(0, ".");
for (int n = intString.length() - 1; n >= 0; n--)
{
try
{
workingVal = Integer.parseInt(String.valueOf(intString.charAt(n)));
}
catch (Exception e)
{
formattedNumber.insert(0, intString.charAt(n));
continue;
}
if (roundNext == true)
{
newNum = workingVal + 1;
if (newNum == 10)
{
roundNext = true;
newNum = 0;
}
else
{
roundNext = false;
}
formattedNumber.insert(0, newNum);
}
else
{
formattedNumber.insert(0, workingVal);
}
}
// Just in case its a number like 9999.99999 (if it rounds right to the end
if (roundNext == true)
{
formattedNumber.insert(0, 1);
}
// re-add the minus sign if needed
if (firstDigit == 1)
formattedNumber.insert(0, rawIntString.charAt(0));
if (digitGrouping.length() > 0)
{
if (formattedNumber.toString().indexOf(".") == -1)
{
// no decimal
if (formattedNumber.toString().indexOf(digitGrouping) > 3 + firstDigit)
{
formattedNumber.insert(1 + firstDigit, digitGrouping);
}
if (formattedNumber.toString().length() == 4 + firstDigit)
{
formattedNumber.insert(1 + firstDigit, digitGrouping);
}
}
else
{
// no decimal
if (formattedNumber.toString().indexOf(digitGrouping) > 3 + firstDigit)
{
formattedNumber.insert(1 + firstDigit, digitGrouping);
}
String intportion = formattedNumber.toString().substring(0, formattedNumber.toString().indexOf("."));
if (intportion.length() == 4 + firstDigit)
{
formattedNumber.insert(1 + firstDigit, digitGrouping);
}
}
}
// now remove trailing zeros
String tmp = formattedNumber.toString();
int newLength = tmp.length();
for (int n = tmp.length() - 1; n >= 0; n--)
{
if (tmp.substring(n, n + 1).equalsIgnoreCase("0"))
{
newLength--;
}
else
{
if (tmp.substring(n, n + 1).equalsIgnoreCase("."))
newLength--;
break;
}
}
formattedNumber.setLength(newLength);
return formattedNumber.toString();
}
}