33
44import javafx .scene .control .Spinner ;
55import javafx .scene .control .SpinnerValueFactory ;
6+ import javafx .scene .control .TextField ;
7+ import javafx .scene .control .TextFormatter ;
68import javafx .util .StringConverter ;
79
8- import java .util .function .Function ;
10+ import java .text .NumberFormat ;
11+ import java .text .ParsePosition ;
12+ import java .util .Optional ;
913
1014/**
1115 * Utility methods to set up spinners in a safe way.
@@ -19,43 +23,52 @@ private Spinners() {
1923 /**
2024 * Makes the spinner editable.
2125 * Ensures that the values will be committed when focus is lost.
22- * Ensures that the only values that the spinner can accept are parable into whatever number type they represent.
23- * @param <T> The number type of the spinner
26+ *
27+ * @param defaultValue The value to use if nothing is entered
28+ * @param <T> The number type of the spinner
2429 */
2530 @ SuppressWarnings ("PMD.IfElseStmtsMustUseBraces" )
26- public static <T extends Number > void makeEditableSafely (Spinner <T > spinner , Function < String , T > parseToNumber ) {
31+ public static <T extends Number > void makeEditableSafely (Spinner <T > spinner , NumberFormat format , T defaultValue ) {
2732 spinner .setEditable (true );
28- spinner .focusedProperty ().addListener ((s , ov , nv ) -> {
29- if (nv ) return ;
30- commitEditorText (spinner );
33+
34+ TextField editor = spinner .getEditor ();
35+
36+ // Override the converter used to interpret editor values. If there's an error, we want to set the default
37+ // value instead of throwing an exception.
38+ StringConverter <T > oldConverter = spinner .getValueFactory ().getConverter ();
39+ spinner .getValueFactory ().setConverter (new StringConverter <T >() {
40+ @ Override
41+ public String toString (T t ) {
42+ return oldConverter .toString (t );
43+ }
44+
45+ @ Override
46+ public T fromString (String s ) {
47+ return Optional .ofNullable (oldConverter .fromString (s )).orElse (defaultValue );
48+ }
3149 });
32- // Ensure the value entered is only a number
33- spinner .getEditor ().textProperty ().addListener ((observable , oldValue , newValue ) -> {
34- if ("" .equals (newValue )) {
35- spinner .getEditor ().setText ("0" );
36- } else try {
37- T value = parseToNumber .apply (newValue );
38- spinner .getEditor ().setText (value .toString ());
39- } catch (NumberFormatException e ) {
40- spinner .getEditor ().setText (oldValue );
50+
51+ // Commit the spinner value when the editor loses focus
52+ editor .focusedProperty ().addListener ((s , ov , focused ) -> {
53+ if (!focused ) {
54+ SpinnerValueFactory <T > valueFactory = spinner .getValueFactory ();
55+ StringConverter <T > converter = valueFactory .getConverter ();
56+ valueFactory .setValue (converter .fromString (editor .getText ()));
4157 }
4258 });
43- }
4459
45- /**
46- *
47- * @see <a href="http://stackoverflow.com/questions/32340476/manually-typing-in-text-in-javafx-spinner-is-not-updating-the-value-unless-user">Stack Overflow Post</a>
48- */
49- private static <T > void commitEditorText (Spinner <T > spinner ) {
50- if (!spinner .isEditable ()) return ;
51- String text = spinner .getEditor ().getText ();
52- SpinnerValueFactory <T > valueFactory = spinner .getValueFactory ();
53- if (valueFactory != null ) {
54- StringConverter <T > converter = valueFactory .getConverter ();
55- if (converter != null ) {
56- T value = converter .fromString (text );
57- valueFactory .setValue (value );
60+ // Filter out invalid editor changes
61+ editor .setTextFormatter (new TextFormatter <T >(change -> {
62+ if (change .isContentChange () && !change .getControlNewText ().isEmpty ()) {
63+ ParsePosition position = new ParsePosition (0 );
64+ format .parse (change .getControlNewText (), position );
65+
66+ if (position .getIndex () == 0 || position .getIndex () < change .getControlNewText ().length ()) {
67+ return null ;
68+ }
5869 }
59- }
70+
71+ return change ;
72+ }));
6073 }
6174}
0 commit comments