How to create a numeric localized (decimal separator) TextField? - javafx

I want to create a numeric TextField which will be localized.
For example, in Excel, if you are in english, if you type a number with the KeyPad, the decimal separator key will enter a dot '.'
But if you are in French, the decimal separator will be a comma ','
Therefore in Excel, the TextField is smart enough to detect in which Locale you're on, and to adapt the decimal separator you actually wanted to print in the TextField.
Right now in JavaFX, I haven't found a way to do that. I was thinking of listening to text modification and to replace any occurrence of dot with a comma if I detect I'm in French. But is this assumption true?

I would recommend subclassing TextField and overriding the insertText and replace* methods. This will handle text entry before the text is updated; listening to text modifications and fixing them will mean you temporarily have "invalid" text in your text field's text property, which could create problems in the rest of your application.
Note that you can create a java.text.DecimalFormatSymbols object for the default (user) Locale (or a specified Locale, if you need), and call getDecimalSeparator() to find the correct decimal separator character. (Similarly, you can call getGroupingSeparator().)
So, for proof of concept, this is nowhere near robust enough for production:
public class NumericTextField extends TextField {
#Override
public void insertText(int index, String text) {
String fixedText = fixText(text);
StringBuilder newText = new StringBuilder(getText().substring(0, index));
newText.append(fixedText);
if (index < getText().length()) {
newText.append(getText().substring(index));
}
if (isValid(newText)) {
super.insertText(index, fixedText);
}
}
#Override
public void replaceText(int start, int end, String text) {
String fixedText = fixText(text);
StringBuilder newText = new StringBuilder(getText().substring(0, start));
newText.append(fixedText);
newText.append(getText().substring(end));
if (isValid(newText)) {
super.insertText(start, end, fixedText);
}
}
#Override
public void replaceText(IndexRange range, String text) {
replaceText(range.getStart(), range.getEnd(), text);
}
private String fixText(String text) {
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance();
return text.replaceAll("\\.", Character.toString(symbols.getDecimalSeparator()));
}
private boolean isValid(String text) {
// check that the text is a valid numeric representation (or partial representation)
}
}

In the events of a TextField, "setOnKeyPressed" and "setOnKeyReleased", the code of the key pressed can be obtained from the keyEvent and there it can be seen that the key code of the "." pressed from the main keyboard corresponds to the constant "PERIOD", while the keystroke code of "." pressed from the numeric keypad corresponds to the constant "DECIMAL".
From this concept, I share the closest thing I have achieved in this regard, where if I press the "." From the numeric keyboard, according to my specific need, I replace it with the character ",".
totalAmountText.setOnKeyReleased((keyEvent) -> {
if (keyEvent.getCode() == DECIMAL) {
int pos = totalAmountText.getCaretPosition();
totalAmountText.setText(totalAmountText.getText().replace(".", ","));
totalAmountText.positionCaret(pos);
}
});

I am attaching a new version of the code, which takes best practices from the code of the first answer:
totalAmountText.setOnKeyReleased((keyEvent) -> {
if (keyEvent.getCode() == DECIMAL) {
int pos = totalAmountText.getCaretPosition();
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance();
totalAmountText.setText(totalAmountText.getText().replace(".",
Character.toString(symbols.getDecimalSeparator())));
totalAmountText.positionCaret(pos);
}
});

Related

TextField that accepts only positive numbers - JavaFX

I'm trying to create a TextField in JavaFX (using Scene Builder) that accepts only positive numbers.
I'm trying actually to make a TextField for a 'Credit Card Number' which accepts 13 - 16 numeric values, not including the minus sign at the beginning, and which can accept 0 at the beginning of the text.
Also, since it's a 'Credit Card Number' TextField, I'm looking for a way where I can insert automatic space after each 4 letters/numbers inserted to it.
Note: I know how to create a TextField that accepts only numbers but it also accepts minus sign at the beginning and doesn't accept the 0 as the first input.
Here is what I have been using so my TextField only accepts digits and it works like a charm as below,
public static void digitsTxtFld(TextField field) {
field.setTextFormatter(new TextFormatter<Integer>(change -> {
String newText = change.getControlNewText();
if (newText.matches("\\d*")) {
return change;
}
return null;
}));
}
And to set a character limit I use this,
public static void setTextLimit(TextArea textArea, int length) {
textArea.setTextFormatter(new TextFormatter<>(change -> {
String string = change.getControlNewText();
if (string.length() > length) {
textArea.positionCaret(string.length());
return change;
} else {
return null;
}
}));
}
Edit:
Apparently one cannot have both text formatters as the second one you call will replace the first and your field will only run with one at a time. So I made a joint formatter as follows:
public static void setTextLimitToDigitFld(TextField field, int length) {
field.setTextFormatter(new TextFormatter<Integer>(change -> {
String newText = change.getControlNewText();
if (newText.matches("\\d*") && newText.length() > length) {
return change;
}
return null;
}));
}

How do I print the content of a TextArea?

So currently I'm trying to make a print feature for my Notepad application. I already have a kind of working Print feature, but it prints the full TextArea not only the string that is written into it.
I already tried to make it just print a string, but the PrintJob is not able to handle it, because it needs the actual TextArea, where the Text is written into.
My current Print Stuff:
public void doPrint() {
String toPrint = writeArea.getText();
printSetup(writeArea, Main.primaryStage);
}
private final Label jobStatus = new Label();
private void printSetup(Node node, Stage owner)
{
// Create the PrinterJob
PrinterJob job = PrinterJob.createPrinterJob();
if (job == null)
{
return;
}
// Show the print setup dialog
boolean proceed = job.showPrintDialog(owner);
if (proceed)
{
print(job, node);
}
}
private void print(PrinterJob job, Node node)
{
// Set the Job Status Message
jobStatus.textProperty().bind(job.jobStatusProperty().asString());
// Print the node
boolean printed = job.printPage(node);
if (printed)
{
job.endJob();
}
}
What I want to have:
A print that only shows the String, just like any other notepad application does if you try to print something
What I currently get:
The full textarea with frame.
As I mentioned in the comment you can wrap it into a Text, but then the first line for some reason isn't displayed correctly.
The solution would be to use a Label instead like:
printSetup(new Label(toPrint), Main.primaryStage);

Convert user input value into string in QML

I have my function checkPinCode() that will check the user entered pincode with the actual pincode.
Here I am facing a problem to get the user entered input value into string. Since the text field has the pincode as string. So i need to convert the user entered input also to a string during the comparison. How can I convert my pincodeSetting.value to a string. And I am new to QML. It would be nice if some one helps.
function checkPinCode() {
var pinOk = (pinCodeEdit.text === NTModelDBCpp.systemSettings.pincodeSetting.value)
if (false === pinOk) {
pinCodeEdit.text = "";
invalidPinText.visible = true;
}
return pinOk;
move this function(checkPinCode) in your c++ class and do your work in your c++ code
bool NTModelDBCpp::checkPinCode(int pincode){
return pincode === value;
}
and use in qml.the convertion will done automatically.
if(NTModelDBCpp.checkPinCode(yourText){
}
define the checkPinCode as static method in your .h c++ file

Qt keyboard enum to QString

I have a piece of code like this :
if(keyEvent->key()==Qt::Key_S && keyEvent->modifiers()==Qt::AltModifier)
{
// my code
}
I want to replace Qt::Key_S and Qt::AltModifier by two strings "KEY_S" and "ALT" which I intend to read from a file. I have no idea how to do it. I have tried QKeySequence, which is not working. Can anyone help?
If you get the Qt::Key value in a QKeyEvent then just use QKeyEvent::text() :
QString stringKey = event->text();
But as it is stated in the doc :
Return values when modifier keys such as Shift, Control, Alt, and Meta
are pressed differ among platforms and could return an empty string.
So you might want to handle them one by one, just by giving them the string you want to associate :
if (event->key() == Qt::Key_Alt)
QString stringKey = "ALT";
You can use Qt meta object system to get real names of enum keys as strings at runtime:
void keyPressEvent(QKeyEvent* e) {
int enum_index = qt_getQtMetaObject()->indexOfEnumerator("Key");
const char* string =
qt_getQtMetaObject()->enumerator(enum_index).valueToKey(e->key());
qDebug() << string;
}
Note that valueToKey may return null pointer if there is no corresponding key.

How to deal with Number precision in Actionscript?

I have BigDecimal objects serialized with BlazeDS to Actionscript. Once they hit Actionscript as Number objects, they have values like:
140475.32 turns into 140475.31999999999998
How do I deal with this? The problem is that if I use a NumberFormatter with precision of 2, then the value is truncated to 140475.31. Any ideas?
This is my generic solution for the problem (I have blogged about this here):
var toFixed:Function = function(number:Number, factor:int) {
return Math.round(number * factor)/factor;
}
For example:
trace(toFixed(0.12345678, 10)); //0.1
Multiply 0.12345678 by 10; that gives us 1.2345678.
When we round 1.2345678, we get 1.0,
and finally, 1.0 divided by 10 equals 0.1.
Another example:
trace(toFixed(1.7302394309234435, 10000)); //1.7302
Multiply 1.7302394309234435 by 10000; that gives us 17302.394309234435.
When we round 17302.394309234435 we get 17302,
and finally, 17302 divided by 10000 equals 1.7302.
Edit
Based on the anonymous answer below, there is a nice simplification for the parameter on the method that makes the precision much more intuitive. e.g:
var setPrecision:Function = function(number:Number, precision:int) {
precision = Math.pow(10, precision);
return Math.round(number * precision)/precision;
}
var number:Number = 10.98813311;
trace(setPrecision(number,1)); //Result is 10.9
trace(setPrecision(number,2)); //Result is 10.98
trace(setPrecision(number,3)); //Result is 10.988 and so on
N.B. I added this here just in case anyone sees this as the answer and doesn't scroll down...
Just a slight variation on Frasers Function, for anyone who is interested.
function setPrecision(number:Number, precision:int) {
precision = Math.pow(10, precision);
return (Math.round(number * precision)/precision);
}
So to use:
var number:Number = 10.98813311;
trace(setPrecision(number,1)); //Result is 10.9
trace(setPrecision(number,2)); //Result is 10.98
trace(setPrecision(number,3)); //Result is 10.988 and so on
i've used Number.toFixed(precision) in ActionScript 3 to do this: http://livedocs.adobe.com/flex/3/langref/Number.html#toFixed%28%29
it handles rounding properly and specifies the number of digits after the decimal to display - unlike Number.toPrecision() that limits the total number of digits to display regardless of the position of the decimal.
var roundDown:Number = 1.434;
// will print 1.43
trace(roundDown.toFixed(2));
var roundUp:Number = 1.436;
// will print 1.44
trace(roundUp.toFixed(2));
I converted the Java of BigDecimal to ActionScript.
We had no choices since we compute for financial application.
http://code.google.com/p/bigdecimal/
You can use property: rounding = "nearest"
In NumberFormatter, rounding have 4 values which you can choice: rounding="none|up|down|nearest". I think with your situation, you can chose rounding = "nearest".
-- chary --
I discovered that BlazeDS supports serializing Java BigDecimal objects to ActionScript Strings as well. So if you don't need the ActionScript data to be Numbers (you are not doing any math on the Flex / ActionScript side) then the String mapping works well (no rounding weirdness). See this link for the BlazeDS mapping options: http://livedocs.adobe.com/blazeds/1/blazeds_devguide/help.html?content=serialize_data_2.html
GraniteDS 2.2 has BigDecimal, BigInteger and Long implementations in ActionScript3, serialization options between Java / Flex for these types, and even code generation tools options in order to generate AS3 big numbers variables for the corresponding Java ones.
See more here: http://www.graniteds.org/confluence/display/DOC22/2.+Big+Number+Implementations.
guys, just check the solution:
protected function button1_clickHandler(event:MouseEvent):void
{
var formatter:NumberFormatter = new NumberFormatter();
formatter.precision = 2;
formatter.rounding = NumberBaseRoundType.NEAREST;
var a:Number = 14.31999999999998;
trace(formatter.format(a)); //14.32
}
I ported the IBM ICU implementation of BigDecimal for the Actionscript client. Someone else has published their nearly identical version here as a google code project. Our version adds some convenience methods for doing comparisons.
You can extend the Blaze AMF endpoint to add serialization support for BigDecimal. Please note that the code in the other answer seems incomplete, and in our experience it fails to work in production.
AMF3 assumes that duplicate objects, traits and strings are sent by reference. The object reference tables need to be kept in sync while serializing, or the client will loose sync of these tables during deserialization and start throwing class cast errors, or corrupting the data in fields that don't match, but cast ok...
Here is the corrected code:
public void writeObject(final Object o) throws IOException {
if (o instanceof BigDecimal) {
write(kObjectType);
if(!byReference(o)){ // if not previously sent
String s = ((BigDecimal)o).toString();
TraitsInfo ti = new TraitsInfo("java.math.BigDecimal",false,true,0);
writeObjectTraits(ti); // will send traits by reference
writeUTF(s);
writeObjectEnd(); // for your AmfTrace to be correctly indented
}
} else {
super.writeObject(o);
}
}
There is another way to send a typed object, which does not require Externalizable on the client. The client will set the textValue property on the object instead:
TraitsInfo ti = new TraitsInfo("java.math.BigDecimal",false,false,1);
ti.addProperty("textValue");
writeObjectTraits(ti);
writeObjectProperty("textValue",s);
In either case, your Actionscript class will need this tag:
[RemoteClass(alias="java.math.BigDecimal")]
The Actionscript class also needs a text property to match the one you chose to send that will initialize the BigDecimal value, or in the case of the Externalizable object, a couple of methods like this:
public function writeExternal(output:IDataOutput):void {
output.writeUTF(this.toString());
}
public function readExternal(input:IDataInput):void {
var s:String = input.readUTF();
setValueFromString(s);
}
This code only concerns data going from server to client. To deserialize in the other direction from client to server, we chose to extend AbstractProxy, and use a wrapper class to temporarily store the string value of the BigDecimal before the actual object is created, due to the fact that you cannot instantiate a BigDecimal and then assign the value, as the design of Blaze/LCDS expects should be the case with all objects.
Here's the proxy object to circumvent the default handling:
public class BigNumberProxy extends AbstractProxy {
public BigNumberProxy() {
this(null);
}
public BigNumberProxy(Object defaultInstance) {
super(defaultInstance);
this.setExternalizable(true);
if (defaultInstance != null)
alias = getClassName(defaultInstance);
}
protected String getClassName(Object instance) {
return((BigNumberWrapper)instance).getClassName();
}
public Object createInstance(String className) {
BigNumberWrapper w = new BigNumberWrapper();
w.setClassName(className);
return w;
}
public Object instanceComplete(Object instance) {
String desiredClassName = ((BigNumberWrapper)instance).getClassName();
if(desiredClassName.equals("java.math.BigDecimal"))
return new BigDecimal(((BigNumberWrapper)instance).stringValue);
return null;
}
public String getAlias(Object instance) {
return((BigNumberWrapper)instance).getClassName();
}
}
This statement will have to execute somewhere in your application, to tie the proxy object to the class you want to control. We use a static method:
PropertyProxyRegistry.getRegistry().register(
java.math.BigDecimal.class, new BigNumberProxy());
Our wrapper class looks like this:
public class BigNumberWrapper implements Externalizable {
String stringValue;
String className;
public void readExternal(ObjectInput arg0) throws IOException, ClassNotFoundException {
stringValue = arg0.readUTF();
}
public void writeExternal(ObjectOutput arg0) throws IOException {
arg0.writeUTF(stringValue);
}
public String getStringValue() {
return stringValue;
}
public void setStringValue(String stringValue) {
this.stringValue = stringValue;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
We were able to reuse one of the available BigDecimal.as classes on the web and extended blazeds by sublassing from AMF3Output, you'll need to specify your own endpoint class in the flex xml files, in that custom endpoint you can insert your own serializer that instantiates an AMF3Output subclass.
public class EnhancedAMF3Output extends Amf3Output {
public EnhancedAMF3Output(final SerializationContext context) {
super(context);
}
public void writeObject(final Object o) throws IOException {
if (o instanceof BigDecimal) {
write(kObjectType);
writeUInt29(7); // write U290-traits-ext (first 3 bits set)
writeStringWithoutType("java.math.BigDecimal");
writeAMFString(((BigDecimal)o).toString());
} else {
super.writeObject(o);
}
}
}
as simple as that! then you have native BigDecimal support using blazeds, wooohoo!
Make sure your BigDecimal as3 class implements IExternalizable
cheers, jb
Surprisingly the round function in MS Excel gives us different values then you have presented above.
For example in Excel
Round(143,355;2) = 143,36
So my workaround for Excel round is like:
public function setPrecision(number:Number, precision:int):Number {
precision = Math.pow(10, precision);
const excelFactor : Number = 0.00000001;
number += excelFactor;
return (Math.round(number * precision)/precision);
}
If you know the precision you need beforehand, you could store the numbers scaled so that the smallest amount you need is a whole value. For example, store the numbers as cents rather than dollars.
If that's not an option, how about something like this:
function printTwoDecimals(x)
{
printWithNoDecimals(x);
print(".");
var scaled = Math.round(x * 100);
printWithNoDecimals(scaled % 100);
}
(With however you print with no decimals stuck in there.)
This won't work for really big numbers, though, because you can still lose precision.
You may vote and watch the enhancement request in the Flash PLayer Jira bug tracking system at https://bugs.adobe.com/jira/browse/FP-3315
And meanwhile use the Number.toFixed() work-around see :
(http://livedocs.adobe.com/flex/3/langref/Number.html#toFixed%28%29)
or use the open source implementations out there : (http://code.google.com/p/bigdecimal/) or (http://www.fxcomps.com/money.html)
As for the serialization efforts, well, it will be small if you use Blazeds or LCDS as they do support Java BigDecimal serialization (to String) cf. (http://livedocs.adobe.com/livecycle/es/sdkHelp/programmer/lcds/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=serialize_data_3.html)
It seems more like a transport problem, the number being correct but the scale ignored. If the number has to be stored as a BigDecimal on the server you may want to convert it server side to a less ambiguous format (Number, Double, Float) before sending it.

Resources