JavaFX improvements in Java SE 8u40

Learn about JavaFX 8u40's support for accessibility, standard and custom dialogs, a spinner control, and text formatting

1 2 3 4 Page 4
Page 4 of 4

A: A value formatter is specified as an instance of a StringConverter<T> subclass. A change formatter is specified as an instance of a class that implements the java.util.function.UnaryOperator<T> functional interface with TextFormatter.Change passed to T. A Change instance contains the state representing a change in the content or selection for a TextInputControl subclass object. You override UnaryOperator's T apply(T t) method to obtain and take action based on what has changed. The Change instance is returned to commit the change; null is returned to reject the change.

After creating value and change formatters, pass them to an appropriate TextFormatter constructor:

  • TextFormatter(StringConverter<V> valueConverter) (install a value formatter)
  • TextFormatter(UnaryOperator<TextFormatter.Change> filter) (install a change formatter)
  • TextFormatter(StringConverter<V> valueConverter, V defaultValue, UnaryOperator<TextFormatter.Change> filter) (install value and change formatters)

You install the TextFormatter object by calling TextInputControl's void setTextFormatter(TextFormatter<?> value) method.

Q: Can you provide an application that demonstrates value formatting?

A: Check out Listing 8.

Listing 8. TextFormatterDemo.java (version 1)

import javafx.application.Application;

import javafx.geometry.Insets;

import javafx.scene.Scene;

import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;

import javafx.scene.layout.HBox;

import javafx.stage.Stage;

import javafx.util.StringConverter;

public class TextFormatterDemo extends Application
{
   @Override
   public void start(Stage primaryStage)
   {
      Label lblAge = new Label("Age");
      TextField txtAge = new TextField("");
      StringConverter<Integer> formatter;
      formatter = new StringConverter<Integer>()
                  {
                     @Override
                     public Integer fromString(String string)
                     {
                        System.out.println("fromString(): string = " + string);
                        return Integer.parseInt(string);
                     }

                     @Override
                     public String toString(Integer object)
                     {
                        System.out.println("toString(): object = " + object);
                        if (object == null)
                           return "0";
                        System.out.println("object.tostring = " + 
                                           object.toString());
                        return object.toString();
                     }
                  };
      txtAge.setTextFormatter(new TextFormatter<Integer>(formatter));
      HBox hboxForm = new HBox(10);
      hboxForm.setPadding(new Insets(10, 10, 10, 10));
      hboxForm.getChildren().addAll(lblAge, txtAge);
      Scene scene = new Scene(hboxForm);
      primaryStage.setScene(scene);
      primaryStage.setResizable(false);
      primaryStage.setTitle("TextFormatterDemo");
      primaryStage.show();
   }
}

Listing 8 describes an application that presents a text field for entering an age. It registers a value formatter with this text field to take appropriate action when the user enters non-digits (although the minus sign can be entered once -- I'll leave it as an exercise to ensure that the minus sign isn't entered).

The best way to understand how this application works is to try it out. First, compile Listing 8 as follows:

javac TextFormatterDemo.java

Next, run the application as follows:

java TextFormatterDemo

Figure 21 shows the resulting user interface.

Figure 21: A default value of 0 is displayed. (Obviously, a living person can never be zero years old.)

A default value of 0 is displayed. (Obviously, a living person can never be zero years old.)

Additionally, the following messages are output to the standard output stream:

toString(): object = null
toString(): object = null

These messages indicate that the value formatter's toString() method is called to convert the initial default value, which is null when you call TextFormatter(StringConverter<V> valueConverter) to install the value formatter but can be a non-null reference when you call TextFormatter(StringConverter<V> valueConverter, V defaultValue), to a string.

Next, replace the highlighted 0 with 10 and press Enter. You should observe the following messages on the standard output stream:

fromString(): string = 10
toString(): object = 10
object.tostring = 10

The fromString() method is called to convert the entered string to an integer. If an illegal value had been entered, fromString()'s Integer.parseInt(string) method would have thrown NumberFormatException and the text formatter would have replaced what was entered with the previous legal value. This would have necessitated converting that value to a string. Either way, toString() is called to perform the conversion.

If you were to type 10a instead of 10 and press Enter, you would observe the text field reverting to 0 and the following messages:

fromString(): string = 10a
toString(): object = null

fromString()'s parseInt() call results in NumberFormatException being thrown. This causes toString() to be called with the previously valid value, which happens to be null because there is no default. When null is seen, toString() returns "0" as the default, which is why you initially see 0 in the text field and also after entering 10a.

I've created a second version of this application (see the code archive) that demonstrates a default value. I've excerpted the relevant code below:

txtAge.setTextFormatter(new TextFormatter<Integer>(formatter, 1));

This code installs a value formatter with a default value of 1.

Q: Can you provide an application that demonstrates change formatting?

A: Check out Listing 9.

Listing 9. TextFormatterDemo.java (version 3)

import java.util.function.UnaryOperator;

import javafx.application.Application;

import javafx.geometry.Insets;

import javafx.scene.Scene;

import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;

import javafx.scene.layout.HBox;

import javafx.stage.Stage;

public class TextFormatterDemo extends Application
{
   @Override
   public void start(Stage primaryStage)
   {
      Label lblPhone = new Label("Phone");
      TextField txtPhone = new TextField("555-5555");
      UnaryOperator<TextFormatter.Change> filter;
      filter = new UnaryOperator<TextFormatter.Change>()
               {
                  @Override
                  public TextFormatter.Change apply(TextFormatter.Change change)
                  {
                     System.out.println(change);
                     String text = change.getText();
                     for (int i = 0; i < text.length(); i++)
                        if (!Character.isDigit(text.charAt(i)))
                          return null;
                     return change;
                  }
               };
      txtPhone.setTextFormatter(new TextFormatter<String>(filter));
      HBox hboxForm = new HBox(10);
      hboxForm.setPadding(new Insets(10, 10, 10, 10));
      hboxForm.getChildren().addAll(lblPhone, txtPhone);
      Scene scene = new Scene(hboxForm);
      primaryStage.setScene(scene);
      primaryStage.setResizable(false);
      primaryStage.setTitle("TextFormatterDemo");
      primaryStage.show();
   }
}

Listing 9: TextFormatterDemo.java (version 3)

Listing 9 describes an application that presents a text field for entering a phone number. It registers a change formatter with this text field to take appropriate action when the user enters non-digits. Because UnaryOperator is a functional interface, I could have used a lambda instead of instantiating an anonymous class that implements an interface. I decided on the latter approach for clarity. If you would like a lambda example, check out Java 8 U40 TextFormatter (JavaFX) to restrict user input only for decimal number.

Whenever you press a key while the text field has the focus, the apply() method is called. It outputs the change argument to the standard output stream, obtains the text that has changed, and scans it for a non-digit. If a non-digit is seen, null is returned to abort the change. Otherwise, change is returned to accept the change.

Compile Listing 9 (javac TextFormatterDemo.java) and run the resulting application (java TextFormatterDemo). Figure 22 shows the resulting user interface.

Figure 22: A default value of 555-5555 is displayed

A default value of 555-5555 is displayed

If you type a non-digit apart from the backspace or Delete, nothing will happen.

Q: Can you provide an application that combines value and change formatting?

A: Check out Listing 10.

Listing 10. TextFormatterDemo.java (version 4)

import java.util.function.UnaryOperator;

import javafx.application.Application;

import javafx.geometry.Insets;

import javafx.scene.Scene;

import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;

import javafx.scene.layout.HBox;

import javafx.stage.Stage;

import javafx.util.StringConverter;

public class TextFormatterDemo extends Application
{
   @Override
   public void start(Stage primaryStage)
   {
      Label lblPhone = new Label("Phone");
      TextField txtPhone = new TextField("555-5555");
      StringConverter<String> formatter;
      formatter = new StringConverter<String>()
                  {
                     @Override
                     public String fromString(String string)
                     {
                        System.out.println("fromString(): string = " + string);
                        if (string.length() == 8)
                           return string;
                        else
                        if (string.length() == 7 && string.indexOf('-') == -1)
                           return string.substring(0, 3) + "-" + 
                                  string.substring(3);
                        else
                           return "333-3333";
                     }

                     @Override
                     public String toString(String object)
                     {
                        System.out.println("toString(): object = " + object);
                        if (object == null)   // only null when called from 
                           return "777-7777"; // TextFormatter constructor 
                                              // without default
                        System.out.println("object.tostring = " + 
                                           object.toString());
                        return object;
                     }
                  };
      UnaryOperator<TextFormatter.Change> filter;
      filter = new UnaryOperator<TextFormatter.Change>()
               {
                  @Override
                  public TextFormatter.Change apply(TextFormatter.Change change)
                  {
                     System.out.println(change);
                     String text = change.getText();
                     for (int i = 0; i < text.length(); i++)
                        if (!Character.isDigit(text.charAt(i)))
                          return null;
                     return change;
                  }
               };
      txtPhone.setTextFormatter(new TextFormatter<String>(formatter, "555-5555",
                                                          filter));
      HBox hboxForm = new HBox(10);
      hboxForm.setPadding(new Insets(10, 10, 10, 10));
      hboxForm.getChildren().addAll(lblPhone, txtPhone);
      Scene scene = new Scene(hboxForm);
      primaryStage.setScene(scene);
      primaryStage.setResizable(false);
      primaryStage.setTitle("TextFormatterDemo");
      primaryStage.show();
   }
}

As with Listing 9, Listing 10 describes an application that presents a text field for entering a phone number. As well as preserving Listing 9's change formatter, Listing 10 adds a value formatter. The value formatter ensures that the seven-digit phone number remains hyphenated when Enter is pressed.

Compile Listing 10 (javac TextFormatterDemo.java) and run the resulting application (java TextFormatterDemo). You should observe the same user interface as shown in Figure 22.

With 555-5555 highlighted, press Delete and then Enter. You should observe 333-3333 in the text field. Press the backspace key once. You should now observe 333-333. Press Enter and the erroneous number is still displayed. You will have to switch to another application and return to this application to see the value replaced by 333-3333.

Erase the contents of the text field and enter 1234567. Press Enter and you should observe 123-4567 in the text field.

What's next?

Next time, I'll present the infrastructure for populating a checkerboard with multiple checkers and dragging them around the board, making sure to center a dragged checker on its target square.

download
Get the source code for this post's applications. Created by Jeff Friesen for JavaWorld

The following software was used to develop the post's code:

  • 64-bit JDK 8u40

The post's code was tested on the following platform(s):

  • JVM on 64-bit Windows 7 SP1

This story, "JavaFX improvements in Java SE 8u40" was originally published by JavaWorld.

Related:

Copyright © 2015 IDG Communications, Inc.

1 2 3 4 Page 4
Page 4 of 4
How to choose a low-code development platform