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
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
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
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?