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 3
Page 3 of 4

Listing 5 uses the java.util.Pair<K, V> class to conveniently store a pair of items: the search text and the Boolean case-sensitive-search value. A result converter has been installed to convert the dialog's search text field and case-sensitive search check box values to a Pair<K, V> object that is returned from the call() method. This conversion happens only when the Search button is clicked; it doesn't happen when Cancel is clicked.

Compile Listing 5 as follows:

javac SearchDialog.java

Run the resulting application as follows:

java SearchDialog

Figure 18 reveals the resulting dialog.

Figure 18: The search dialog defaults to no search text and a case-insensitive search

The search dialog defaults to no search text and a case-insensitive search

Suppose you enter JavaFX in the text field and check the check box. After clicking Search, you should observe the following output on the standard output stream:

Search text = JavaFX, Case-sensitive search = true

Q: Can you provide me with more information about JavaFX's support for standard and custom dialogs?

A: Check out JavaFX Dialogs for additional standard and custom dialog examples (including a custom login dialog), and to learn about dialog styling and setting the dialog owner and modality.

Spinner control

Q: What is a spinner?

A: A spinner is a single-line text field control that lets the user select a number or an object value from an ordered sequence of such values. Spinners typically provide a pair of tiny arrow buttons for stepping through the elements of the sequence. The keyboard's up arrow/down arrow keys also cycle through the elements. The user may also be allowed to type a (legal) value directly into the spinner. Although combo boxes provide similar functionality, spinners are sometimes preferred because they don't require a drop-down list that can obscure important data, and also because they allow for features such as wrapping from the maximum value back to the minimum value (e.g., from the largest positive integer to 0).

Q: How is a spinner implemented?

A: A spinner is implemented as an instance of the javafx.scene.control.Spinner control class. This class relies on the abstract javafx.scene.control.SpinnerValueFactory class to provide the control's model (range of user-selectable values of a specific type). Currently, the only supported models are defined by its nested DoubleSpinnerValueFactory, IntegerSpinnerValueFactory, and ListSpinnerValueFactory classes.

Q: How do I create a spinner?

A: You create a spinner by calling one of Spinner's constructors. For example, Spinner(int min, int max, int initialValue) creates a spinner for selecting one of the integer values from min through max. The initially selected value is identified by initialValue. If this value isn't in the min/max range, min's value becomes the initially selected value.

Spinner(int min, int max, int initialValue) is a convenience constructor that installs an instance of the IntegerSpinnerValueFactory class with these values as the model. If you prefer to work directly with IntegerSpinnerValueFactory, you can instantiate this class and pass its reference to the Spinner(SpinnerValueFactory<T> valueFactory) constructor. Alternatively, you can create an empty spinner via the Spinner() constructor and invoke Spinner's void setValueFactory(SpinnerValueFactory<T> value) method to install this factory object.

Q: Can you provide a simple example of the integer and double precision floating-point spinners?

A: Check out Listing 6.

Listing 6. SpinnerDemo.java (version 1)

import javafx.application.Application;

import javafx.geometry.Insets;

import javafx.scene.Scene;

import javafx.scene.control.Label;
import javafx.scene.control.Spinner;

import javafx.scene.layout.GridPane;

import javafx.stage.Stage;

public class SpinnerDemo extends Application
{
   @Override
   public void start(Stage primaryStage)
   {
      Spinner<Integer> ispinner = new Spinner<>(1, 10, 2);
      Spinner<Double> dspinner = new Spinner<>(1.5, 3.5, 1.5, 0.5);

      GridPane grid = new GridPane();
      grid.setHgap(10);
      grid.setVgap(10);
      grid.setPadding(new Insets(10));

      grid.add(new Label("Integer Spinner"), 0, 0);
      grid.add(ispinner, 1, 0);
      grid.add(new Label("Double Spinner"), 0, 1);
      grid.add(dspinner, 1, 1);

      Scene scene = new Scene(grid, 350, 100);
      primaryStage.setTitle("SpinnerDemo");
      primaryStage.setScene(scene);
      primaryStage.show();
   }
}

Listing 6's start() method first creates an integer spinner via the aforementioned constructor. It then creates a double precision floating-point spinner via the Spinner(double min, double max, double initialValue, double amountToStepBy) constructor. This constructor receives the units in which to increment or decrement the spinner via the value passed to amountToStepBy, which happens to be 0.5.

Continuing, start() creates and configures a grid pane container and populates the 2-row-by-2-column grid with these spinners and associated labels. It then creates the scene based on the grid and configures/shows the stage.

Compile Listing 6 as follows:

javac SpinnerDemo.java

Run the resulting application as follows:

java SpinnerDemo

Figure 19 reveals the resulting user interface.

Figure 19: Click each spinner's arrows to increment/decrement through the range of values

Click each spinner's arrows to increment/decrement through the range of values

Q: I want to make the previous spinners editable. How do I accomplish this task?

A: Call Spinner's void setEditable(boolean value) method, passing true to value. I've created a second version of SpinnerDemo to demonstrate. See this article's code archive for the source code.

Q: When I make the previous spinners editable, type illegal characters (such as letters) into the text field, and press the Enter key, an exception is thrown. The exception is java.lang.NumberFormatException for the integer-based spinner and java.lang.RuntimeException (wrapping java.text.ParseException) for the double precision floating-point spinner. How can I prevent this exception from being thrown?

A: You can prevent this exception from being thrown by installing an instance of a subclass of the abstract javafx.util.StringConverter<T> class (where T is the type being converted to or from String) as the SpinnerValueFactory converter. This object would catch the exception being thrown and take action.

Spinner uses a javafx.scene.control.TextField object as an editor for obtaining user input. Because a text field can store any character, inappropriate characters such as letters in a numeric context can be entered. After the user presses Enter, the input is passed to the SpinnerValueFactory converter's T fromString(String string) method. For the integer or double precision floating-point factories, T is either Integer or Double. Converting from a string with illegal characters to a number results in the exception being thrown from fromString(). Obtain a reference to the current converter and install a new converter whose fromString() method invokes the other converter's fromString() method in a try statement with an appropriate catch block. Listing 7 presents an application that accomplishes this task.

Listing 7. SpinnerDemo.java (version 3)

import javafx.application.Application;

import javafx.geometry.Insets;

import javafx.scene.Scene;

import javafx.scene.control.Label;
import javafx.scene.control.Spinner;

import javafx.scene.layout.GridPane;

import javafx.stage.Stage;

import javafx.util.StringConverter;

public class SpinnerDemo extends Application
{
    @Override
    public void start(Stage primaryStage)
    {
        Spinner<Integer> ispinner = new Spinner<>(1, 10, 2);
        ispinner.setEditable(true);
        StringConverter<Integer> sci = ispinner.getValueFactory().getConverter();
        StringConverter<Integer> sci2 = new StringConverter<Integer>()
                                        {
                                           @Override
                                           public Integer fromString(String value)
                                           {
                                              try
                                              {
                                                 return sci.fromString(value);
                                              }
                                              catch (NumberFormatException nfe)
                                              {
                                                 System.out.println("Bad integer: " + 
                                                                    value);
                                                 return 0;
                                              }
                                           }

                                           @Override
                                           public String toString(Integer value)
                                           {
                                              return sci.toString(value);
                                           }
                                        };
        ispinner.getValueFactory().setConverter(sci2);

        Spinner<Double> dspinner = new Spinner<>(1.5, 3.5, 1.5, 0.5);
        dspinner.setEditable(true);
        StringConverter<Double> scd = dspinner.getValueFactory().getConverter();
        StringConverter<Double> scd2 = new StringConverter<Double>()
                                       {
                                          @Override
                                          public Double fromString(String value)
                                          {
                                             try
                                             {
                                                return scd.fromString(value);
                                             }
                                             catch (RuntimeException re)
                                             {
                                                System.out.println("Bad double: " + 
                                                                   value);
                                                System.out.println("Cause: " + 
                                                                   re.getCause());
                                                return 0.0;
                                             }
                                          }

                                          @Override
                                          public String toString(Double value)
                                          {
                                             return scd.toString(value);
                                          }
                                       };
        dspinner.getValueFactory().setConverter(scd2);

        GridPane grid = new GridPane();
        grid.setHgap(10);
        grid.setVgap(10);
        grid.setPadding(new Insets(10));

        grid.add(new Label("Integer Spinner"), 0, 0);
        grid.add(ispinner, 1, 0);
        grid.add(new Label("Double Spinner"), 0, 1);
        grid.add(dspinner, 1, 1);

        Scene scene = new Scene(grid, 350, 100);
        primaryStage.setTitle("SpinnerDemo");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

Compile Listing 7 (javac SpinnerDemo.java) and run the resulting application (java SpinnerDemo). You should observe error messages similar to those shown below when you enter illegal characters into a spinner's text field:

Bad integer: a2
Bad double: b1.5
Cause: java.text.ParseException: Unparseable number: "b1.5"

Q: I need a string-based spinner. How do I obtain one?

A: You can obtain a string-based spinner via the Spinner(ObservableList<T> items) constructor. For example, the following code fragment shows you how to create a spinner for selecting a weekday name:

List<String> weekDays = Arrays.asList("Monday", "Tuesday", "Wednesday",
                                      "Thursday", "Friday", "Saturday",
                                      "Sunday");
ObservableList<String> obsWeekDays = 
   FXCollections.observableList(weekDays);
Spinner<String> sspinner = new Spinner<>(obsWeekDays);

I excerpted this code fragment from a fourth version of the SpinnerDemo application (see this article's code archive for the source code). When you run that application, you'll see the spinner shown in Figure 20.

Figure 20: Attempting to enter text into the editor that doesn't exactly match one of the weekday names results in java.lang.UnsupportedOperationException being thrown

Attempting to enter text into the editor that doesn't exactly match one of the weekday names results in java.lang.UnsupportedOperationException being thrown.

Q: Can you provide me with more information about JavaFX's support for spinners?

A: Check out the JavaFX 8 documentation on Spinner and SpinnerValueFactory for more information about this control and its model. Also, you might want to run a Google search to find out how others are using this control.

Text formatting

Q: How does JavaFX support text formatting?

A: JavaFX supports text formatting by providing the javafx.scene.control.TextFormatter<V> class with its nested Change class. Furthermore, the abstract javafx.scene.control.TextInputControl class (the parent class of TextField and javafx.scene.control.TextArea) has been given a textFormatter property so that any subclass automatically supports text formatting.

Q: What kinds of text formatting are supported?

A: TextFormatter supports two kinds of text formatting: value and change. A value formatter is called when you press the Enter key after entering the text. A change formatter is called for every text-deletion, text-addition, and text-replacement change for the focused text-input control. These formatters can be used separately or together.

Q: How are value and change formatters specified?

Related:
1 2 3 4 Page 3
Page 3 of 4