B4J Library jTextFormatter

In Javafx Version 8u40 a TextFormatter was introduced. This allows access to text input before any of the keypressed events are fired. Pasted text is also captured.

This makes it a lot easier to validate and format text input.

This wrapper is based on the example here: https://uwesander.de/?p=203

Documentation is here:
https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TextFormatter.html
https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TextFormatter.Change.html

The proposed change to the text is passed to a callback sub, within which you can validate and amend the change (or entire text) as required, then return the amended change object, or Null to reject the change.

Commented examples are in the attached project, which can be compiled to a library if required.

294
 

Attachments

  • TFT.zip
    5 KB · Views: 629
  • TextFormatterB4xlib.b4xlib
    2.1 KB · Views: 541
Last edited:

micro

Well-Known Member
Licensed User
Longtime User
Hi stevel05
thanks for this wrapper.
I added FocusChanged to TextField object definite as TextFormatter but not work
B4X:
Dim tf As TextFormatter
tf.Initialize(Me, "Numeric", txtrange1)
'And This work
........
........
Sub txtrange1_FocusChanged (HasFocus As Boolean)
    If HasFocus = False Then
        Dim txt As TextField = Sender
        Dim d As Double = txt.Text.Replace(DecimalSeparator, ".")
        txt.Text = NumberFormat2(d, 1, 3, 3, False).Replace(".", DecimalSeparator)
    End If
End Sub

After i tried
B4X:
Sub txtrange1_FocusChanged (HasFocus As Boolean)
    If HasFocus = False Then
        Dim tfg As TFChange
        tfg.Initialize
        tfg.SetObject(Sender)
        Dim d As Double = tfg.Text.Replace(DecimalSeparator, ".")
        Dim s As String = NumberFormat2(d, 1, 3, 3, False).Replace(".", DecimalSeparator)
        tfg.setText(s)
    End If
End Sub

I added this sub for to have always 3 decimals but not work.
Where am I wrong?
 

stevel05

Expert
Licensed User
Longtime User
The validation works in a callback as in the example, in your case you should have a sub:
B4X:
Sub Numeric_TextValidator(Change As TFChange) As TFChange
.
.
.
End Sub

In which you do your validation, not in the FocusChanged callback.
 

micro

Well-Known Member
Licensed User
Longtime User
The validation works in a callback as in the example, in your case you should have a sub:
B4X:
Sub Numeric_TextValidator(Change As TFChange) As TFChange
.
.
.
End Sub
In which you do your validation, not in the FocusChanged callback.
of course.
but I will check if the number of decimal places is right when I go to another object.
The validation works in a callback as in the example, in your case you should have a sub:
B4X:
Sub Numeric_TextValidator(Change As TFChange) As TFChange
.
.
.
End Sub
In which you do your validation, not in the FocusChanged callback.

Of course,
but I control the number of decimals when shift focus to another object
I can not control it when the text changes.
 

stevel05

Expert
Licensed User
Longtime User
The text formatter works for text input before the text is displayed. It won't help with after input validation.

You can set the text directly from the focuschanged sub if that's when you want to do your validation.
 

micro

Well-Known Member
Licensed User
Longtime User
You can set the text directly from the focuschanged sub if that's when you want to do your validation

As?
I tried but not work.
If it's a simple TextField not associated to your JTextFormatter work well otherwise no.
Thanks
 

Revisable5987

Member
Licensed User
WARNING: package com.sun.javafx.embed.swing.oldimpl not in javafx.swing
Waiting for debugger to connect...
Program started.
Error occurred on line: 31 (TextFormatter)
java.lang.RuntimeException: Class instance was not initialized (tfchange)
at anywheresoftware.b4a.debug.Debug.shouldDelegate(Debug.java:45)
at b4j.example.tfchange._setobject(tfchange.java:81)
at b4j.example.textformatter._textfilter_event(textformatter.java:137)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:632)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:237)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:167)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:91)
at anywheresoftware.b4a.shell.ShellBA.raiseEvent2(ShellBA.java:98)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:78)
at anywheresoftware.b4j.object.JavaObject$1.invoke(JavaObject.java:238)
at com.sun.proxy.$Proxy0.apply(Unknown Source)
at javafx.controls/javafx.scene.control.TextInputControl.selectRange(TextInputControl.java:1063)
at javafx.controls/com.sun.javafx.scene.control.behavior.TextFieldBehavior.lambda$new$1(TextFieldBehavior.java:85)
at javafx.base/javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:86)
at javafx.base/com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:360)
at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
at javafx.base/javafx.beans.property.ReadOnlyObjectPropertyBase.fireValueChangedEvent(ReadOnlyObjectPropertyBase.java:74)
at javafx.base/javafx.beans.property.ReadOnlyObjectWrapper.fireValueChangedEvent(ReadOnlyObjectWrapper.java:102)
at javafx.base/javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:113)
at javafx.base/javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:147)
at javafx.graphics/javafx.scene.Scene$KeyHandler.setFocusOwner(Scene.java:4058)
at javafx.graphics/javafx.scene.Scene$KeyHandler.requestFocus(Scene.java:4105)
at javafx.graphics/javafx.scene.Scene.requestFocus(Scene.java:2161)
at javafx.graphics/javafx.scene.Node.requestFocus(Node.java:8315)
at javafx.graphics/com.sun.javafx.scene.traversal.TopMostTraversalEngine.focusAndNotify(TopMostTraversalEngine.java:110)
at javafx.graphics/com.sun.javafx.scene.traversal.TopMostTraversalEngine.traverseToFirst(TopMostTraversalEngine.java:132)
at javafx.graphics/javafx.scene.Scene.focusInitial(Scene.java:2137)
at javafx.graphics/javafx.scene.Scene$ScenePulseListener.focusCleanup(Scene.java:2469)
at javafx.graphics/javafx.scene.Scene$ScenePulseListener.pulse(Scene.java:2490)
at javafx.graphics/com.sun.javafx.tk.Toolkit.lambda$runPulse$2(Toolkit.java:412)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
at javafx.graphics/com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:411)
at javafx.graphics/com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:438)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:563)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:543)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.pulseFromQueue(QuantumToolkit.java:536)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.lambda$runToolkit$11(QuantumToolkit.java:342)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
at java.base/java.lang.Thread.run(Thread.java:832)

Get this error trying to run the example in B4J

Had to add 'Change.Initialize' in:

B4X:
Private Sub TextFilter_Event(MethodName As String,Args() As Object) As Object
    Dim Change As TFChange
    Change.SetObject(Args(0))
    If Change.isContentChange = False Then Return Change.GetObject
    Change = CallSub2(mModule,mEventName & "_TextValidator",Change)
    If Change = Null Then Return Null
    Return Change.GetObject
End Sub
 

stevel05

Expert
Licensed User
Longtime User
That is correct, I'm not sure why it has happened now, but I've uploaded a new file.
 

Ilya G.

Active Member
Licensed User
Longtime User
Hi, is it possible to allow user enter only masked text like 8-707-***** or numbers 123,456,789 with grouping?
 

stevel05

Expert
Licensed User
Longtime User
You can match against any regex, see the example app.
 

Ilya G.

Active Member
Licensed User
Longtime User
I try to use this for grouping, but it works wrong

B4X:
Sub LowerCase_TextValidator(Change As TFChange) As TFChange
    Change.Text = NumberFormat2(Change.ControlNewText, 0, 0, 0, True)
    Return Change  
End Sub
 

stevel05

Expert
Licensed User
Longtime User
The text in Change.Text is the current change, a keystroke or pasted string. Therefore you are adding the formatted full text (Change.ControlNewText) to the current change.

This is an input validator, so wholesale changes to the output is not supported.

I don't think there is a simple solution for this, you would need to create an appropriate regex, or try searching the forum.
 

stevel05

Expert
Licensed User
Longtime User
Added B4xLib
 

Ilya G.

Active Member
Licensed User
Longtime User
Thank you very much, but formatting is needed exactly when you type to visually see the number of zeros
 

stevel05

Expert
Licensed User
Longtime User
I don't think there are any existing tools that will do exactly what you want for javafx. If you can find an example of a view library that does what you want on the internet, someone may be able to wrap it for you.
 

Jorge M A

Well-Known Member
Licensed User
Longtime User
but formatting is needed exactly when you type to visually see the number of zeros
If you can find an example
I found a little old, but working example, compiling against Java jdk1.8.0
 
Top