-
Bug
-
Resolution: Fixed
-
P3
-
jfx17
ADDITIONAL SYSTEM INFORMATION :
Linux 4.18.0-305.19.1.el8_4.x86_64 #1 SMP Wed Sep 15 19:12:32 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
openjdk version "11.0.13" 2021-10-19 LTS
OpenJDK Runtime Environment 18.9 (build 11.0.13+8-LTS)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.13+8-LTS, mixed mode, sharing)
OpenJFX 13.0.1
OpenJFX 17.0.1
A DESCRIPTION OF THE PROBLEM :
We encountered an unexpected difference in ComboBox behavior between OpenJFX 13.0.1 and OpenJFX 17.0.1:
We have a ComboBox that has its #valueProperty bidirectionally bound to a model property. When that model property is changed, we also update the contents of the ComboBox's item list. (Think: user selects a physical unit, e.g. "V", and the ComboBox is populated with e.g. "mV", "V", "kV", etc.).
In OpenJFX 13, the previously selected ComboBox value remains selected after the item list contents have been changed.
In OpenJFX 17, the previously selected ComboBox value is lost during the item list content change, and is then null.
The behaviour change seems to be connected to the ComboBoxes selected__Index__ : If a change listener is registered on the #selectedIndexProperty of the ComboBox's SelectionModel, the behaviour works as intended (selection remains unchanged). Debugging showed that the selected index was indeed correctly restored to the index of the previously selected item, however, this index update is never evaluated to also restore the ComboBox value.
Relevant code is ComboBox.class, line 611 - 621, especially 617 .
Also please note that the order of binding and listener setup is unfortunately critical already in OpenJFX 13.0.1 : The bidirectional binding must be established __after__ the listener that triggers the item list update. If it is establised __before__ that listener, the selected value is lost always both in 13.0.1 and 17.0.1 .
In any case, losing the selected value when the item list content is changed is very unexpected and seems like a bug, given that the ComboBox Javadoc states "If the value property is set to a non-null object, and subsequently the items list is cleared, the value property IS NOT nulled out": If the value remains unchanged when the item list is cleared, it should probably also remain unchanged if the item list contents change.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
- Start the app
- Observe the initial state after programmatic changes to the selected value:
--- expected: "D" is selected in the ComboBox
--- actual 13.0.1: "D" is selected in the ComboBox
--- actual 17.0.1: null is selected in the ComboBox
- Depending on certain small changes to the code (marked with NOTE 1 and NOTE 2), the behavior can be broken also in 13.0.1, or made to start working in 17.0.1 . Those changes are very small (registration (order) of
listeners/bindings), but strongly affect the outcome. For which changes lead to which result, see comments marked with "RESULTS" .
- Output for 13/17 is included below.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
ComboBox value remains unchanged = "D" is selected in the ComboBox
ACTUAL -
13.0.1: "D" is selected in the ComboBox <= expected
17.0.1: null is selected in the ComboBox
---------- BEGIN SOURCE ----------
public static class ComboBoxSetAllDifference13vs17App extends Application {
/**
* What does this do? <br />
* - Creates a ComboBox backed by an ObservableList (ComboBox#setItems). The backing list is initialized with
* three strings. <br />
* - There is a property for a selected value, which is bidirectionally bound to the selected item of the
* ComboBox. <br />
* - When the property for the selected value is changed, the contents of the backing list/ComboBox are updated.
* The selected value is contained in those contents. <br />
* - We programmatically change the selected value twice. <br />
* <p>
* What to try? <br />
* - Start the app <br />
* - Observe the initial state after programmatic changes to the selected value:<br />
* --- expected: "D" is selected in the ComboBox <br />
* --- actual 13.0.1: "D" is selected in the ComboBox <br />
* --- actual 17.0.1: null is selected in the ComboBox <br />
* - Depending on certain small changes to the code (marked with NOTE 1 and NOTE 2), the behavior can be broken
* also in 13.0.1, or made to start working in 17.0.1 . Those changes are very small (registration (order) of
* listeners/bindings), but strongly affect the outcome. For which changes lead to which result, see "RESULTS"
* below. <br />
*/
@Override
public void start(final Stage stage) throws Exception {
final ObservableList<String> comboBoxItemsList = FXCollections.observableArrayList();
final ObjectProperty<String> selectedValue = new SimpleObjectProperty<>();
final List<String> stringsABC = List.of("A", "B", "C");
final List<String> stringsDEF = List.of("D", "E", "F");
final ComboBox<String> comboBox = new ComboBox<>();
comboBox.setItems(comboBoxItemsList);
// listeners only for debugging output
initSelectionListeners(comboBox, "comboBoxWithSetItems");
// NOTE 1a: If bidi binding is registered HERE (before adding listeners), it fails in fx 13.0.1 as well (selected value: null)
// comboBox.valueProperty().bindBidirectional(selectedValue);
final BooleanProperty valueAdjusting = new SimpleBooleanProperty();
selectedValue.addListener(obs -> {
System.out.println("selectedValue property inval listener " + selectedValue.get());
});
selectedValue.addListener((obs, oldV, newV) -> {
System.out.println(
"selectedValue property change listener newV=" + newV + " valueAdjusting="
+ valueAdjusting.get());
if (valueAdjusting.get() == true) {
// avoid infinite loop / stack overflow
System.out.println("ignoring value update during item update");
} else {
if ("D".equals(newV) || "A".equals(newV)) {
valueAdjusting.set(true);
System.out.println("valueListener: triggering item update");
final List<String> newContent = "A".equals(newV) ? stringsABC : stringsDEF;
comboBoxItemsList.setAll(newContent);
System.out.println("valueListener: value after item update: " + comboBox.getValue());
System.out.println(
"valueListener: selection after item update: "
+ comboBox.getSelectionModel().getSelectedItem());
valueAdjusting.set(false);
}
}
});
// NOTE 1b: If bidi binding is registered HERE, it works in fx 13.0.1 but fails in fx 17.0.1 as described felow
comboBox.valueProperty().bindBidirectional(selectedValue);
selectedValue.set("A");
selectedValue.set("D");
System.out.println("Selected value: " + selectedValue.get());
// RESULTS :
// fx 13.0.1 :
// - with early bidi binding registration (NOTE 1a):
// ---- with change listener on selectedIndex (NOTE 2): Selected value: D
// ---- without change listener on selectedIndex: Selected value: null
// - with late bidi binding registration (NOTE 1b):
// ---- with change listener on selectedIndex (NOTE 2): Selected value: D
// ---- without change listener on selectedIndex: Selected value: D
//
// fx 17.0.1 :
// - with early bidi binding registration (NOTE 1a):
// ---- with change listener on selectedIndex (NOTE 2): Selected value: D
// ---- without change listener on selectedIndex: Selected value: null
// - with late bidi binding registration (NOTE 1b):
// ---- with change listener on selectedIndex (NOTE 2): Selected value: D
// ---- without change listener on selectedIndex: Selected value: null
//
final StackPane mainRoot = new StackPane(new VBox(comboBox));
final Scene scene = new Scene(mainRoot);
stage.setTitle("ComboBoxSetAllDifference13vs17App");
stage.setScene(scene);
stage.sizeToScene();
stage.show();
}
private static void initSelectionListeners(final ComboBox<?> comboBox, String string) {
comboBox.valueProperty().addListener((obs, old, newV) -> {
System.out.println(string + " -- " + "new selected value: " + newV);
});
comboBox.getSelectionModel().selectedItemProperty().addListener((obs, old, newItem) -> {
System.out.println(string + " -- " + "new selected item: " + newItem);
});
// NOTE 2: when this listener is used, it also works in 17.0.1 !!!! -> property becomes valid because of change evaluation!
// comboBox.getSelectionModel().selectedIndexProperty().addListener((obs, old, newIndex) -> {
// System.out.println(string + " -- " + "new selected index: " + newIndex);
// });
// invalidation listener does not make a difference!
comboBox.getSelectionModel().selectedIndexProperty().addListener(obs -> {
System.out.println(string + " -- " + "selected index invalidated");
});
}
public static void main(final String[] args) {
ComboBoxSetAllDifference13vs17App.launch(args);
}
}
// Default output fx 13.0.1 :
// selectedValue property inval listener A
// selectedValue property change listener newV=A valueAdjusting=false
// valueListener: triggering item update
// valueListener: value after item update: null
// valueListener: selection after item update: null
// comboBoxWithSetItems -- new selected item: A
// comboBoxWithSetItems -- selected index invalidated
// comboBoxWithSetItems -- new selected value: A
// selectedValue property inval listener D
// selectedValue property change listener newV=D valueAdjusting=false
// valueListener: triggering item update
// valueListener: value after item update: A
// valueListener: selection after item update: A
// comboBoxWithSetItems -- new selected item: D
// comboBoxWithSetItems -- new selected value: D
// Selected value: D
// Default output fx 17.0.1 :
// selectedValue property inval listener A
// comboBoxWithSetItems -- new selected item: A
// comboBoxWithSetItems -- new selected value: A
// selectedValue property change listener newV=A valueAdjusting=false
// valueListener: triggering item update
// comboBoxWithSetItems -- selected index invalidated
// valueListener: value after item update: A
// valueListener: selection after item update: A
// selectedValue property inval listener D
// comboBoxWithSetItems -- selected index invalidated
// comboBoxWithSetItems -- new selected item: D
// comboBoxWithSetItems -- new selected value: D
// selectedValue property change listener newV=D valueAdjusting=false
// valueListener: triggering item update
// comboBoxWithSetItems -- selected index invalidated
// selectedValue property inval listener null
// selectedValue property change listener newV=null valueAdjusting=true
// ignoring value update during item update
// comboBoxWithSetItems -- selected index invalidated
// comboBoxWithSetItems -- new selected item: null
// comboBoxWithSetItems -- new selected value: null
// valueListener: value after item update: null
// valueListener: selection after item update: null
// Selected value: null
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Register a change listener on the #selectedIndexProperty of the ComboBox's SelectionModel to enforce eager evaluation of the property, which means that the restoration of the index is correctly picked up to also restore the selection.
FREQUENCY : always
Linux 4.18.0-305.19.1.el8_4.x86_64 #1 SMP Wed Sep 15 19:12:32 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
openjdk version "11.0.13" 2021-10-19 LTS
OpenJDK Runtime Environment 18.9 (build 11.0.13+8-LTS)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.13+8-LTS, mixed mode, sharing)
OpenJFX 13.0.1
OpenJFX 17.0.1
A DESCRIPTION OF THE PROBLEM :
We encountered an unexpected difference in ComboBox behavior between OpenJFX 13.0.1 and OpenJFX 17.0.1:
We have a ComboBox that has its #valueProperty bidirectionally bound to a model property. When that model property is changed, we also update the contents of the ComboBox's item list. (Think: user selects a physical unit, e.g. "V", and the ComboBox is populated with e.g. "mV", "V", "kV", etc.).
In OpenJFX 13, the previously selected ComboBox value remains selected after the item list contents have been changed.
In OpenJFX 17, the previously selected ComboBox value is lost during the item list content change, and is then null.
The behaviour change seems to be connected to the ComboBoxes selected__Index__ : If a change listener is registered on the #selectedIndexProperty of the ComboBox's SelectionModel, the behaviour works as intended (selection remains unchanged). Debugging showed that the selected index was indeed correctly restored to the index of the previously selected item, however, this index update is never evaluated to also restore the ComboBox value.
Relevant code is ComboBox.class, line 611 - 621, especially 617 .
Also please note that the order of binding and listener setup is unfortunately critical already in OpenJFX 13.0.1 : The bidirectional binding must be established __after__ the listener that triggers the item list update. If it is establised __before__ that listener, the selected value is lost always both in 13.0.1 and 17.0.1 .
In any case, losing the selected value when the item list content is changed is very unexpected and seems like a bug, given that the ComboBox Javadoc states "If the value property is set to a non-null object, and subsequently the items list is cleared, the value property IS NOT nulled out": If the value remains unchanged when the item list is cleared, it should probably also remain unchanged if the item list contents change.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
- Start the app
- Observe the initial state after programmatic changes to the selected value:
--- expected: "D" is selected in the ComboBox
--- actual 13.0.1: "D" is selected in the ComboBox
--- actual 17.0.1: null is selected in the ComboBox
- Depending on certain small changes to the code (marked with NOTE 1 and NOTE 2), the behavior can be broken also in 13.0.1, or made to start working in 17.0.1 . Those changes are very small (registration (order) of
listeners/bindings), but strongly affect the outcome. For which changes lead to which result, see comments marked with "RESULTS" .
- Output for 13/17 is included below.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
ComboBox value remains unchanged = "D" is selected in the ComboBox
ACTUAL -
13.0.1: "D" is selected in the ComboBox <= expected
17.0.1: null is selected in the ComboBox
---------- BEGIN SOURCE ----------
public static class ComboBoxSetAllDifference13vs17App extends Application {
/**
* What does this do? <br />
* - Creates a ComboBox backed by an ObservableList (ComboBox#setItems). The backing list is initialized with
* three strings. <br />
* - There is a property for a selected value, which is bidirectionally bound to the selected item of the
* ComboBox. <br />
* - When the property for the selected value is changed, the contents of the backing list/ComboBox are updated.
* The selected value is contained in those contents. <br />
* - We programmatically change the selected value twice. <br />
* <p>
* What to try? <br />
* - Start the app <br />
* - Observe the initial state after programmatic changes to the selected value:<br />
* --- expected: "D" is selected in the ComboBox <br />
* --- actual 13.0.1: "D" is selected in the ComboBox <br />
* --- actual 17.0.1: null is selected in the ComboBox <br />
* - Depending on certain small changes to the code (marked with NOTE 1 and NOTE 2), the behavior can be broken
* also in 13.0.1, or made to start working in 17.0.1 . Those changes are very small (registration (order) of
* listeners/bindings), but strongly affect the outcome. For which changes lead to which result, see "RESULTS"
* below. <br />
*/
@Override
public void start(final Stage stage) throws Exception {
final ObservableList<String> comboBoxItemsList = FXCollections.observableArrayList();
final ObjectProperty<String> selectedValue = new SimpleObjectProperty<>();
final List<String> stringsABC = List.of("A", "B", "C");
final List<String> stringsDEF = List.of("D", "E", "F");
final ComboBox<String> comboBox = new ComboBox<>();
comboBox.setItems(comboBoxItemsList);
// listeners only for debugging output
initSelectionListeners(comboBox, "comboBoxWithSetItems");
// NOTE 1a: If bidi binding is registered HERE (before adding listeners), it fails in fx 13.0.1 as well (selected value: null)
// comboBox.valueProperty().bindBidirectional(selectedValue);
final BooleanProperty valueAdjusting = new SimpleBooleanProperty();
selectedValue.addListener(obs -> {
System.out.println("selectedValue property inval listener " + selectedValue.get());
});
selectedValue.addListener((obs, oldV, newV) -> {
System.out.println(
"selectedValue property change listener newV=" + newV + " valueAdjusting="
+ valueAdjusting.get());
if (valueAdjusting.get() == true) {
// avoid infinite loop / stack overflow
System.out.println("ignoring value update during item update");
} else {
if ("D".equals(newV) || "A".equals(newV)) {
valueAdjusting.set(true);
System.out.println("valueListener: triggering item update");
final List<String> newContent = "A".equals(newV) ? stringsABC : stringsDEF;
comboBoxItemsList.setAll(newContent);
System.out.println("valueListener: value after item update: " + comboBox.getValue());
System.out.println(
"valueListener: selection after item update: "
+ comboBox.getSelectionModel().getSelectedItem());
valueAdjusting.set(false);
}
}
});
// NOTE 1b: If bidi binding is registered HERE, it works in fx 13.0.1 but fails in fx 17.0.1 as described felow
comboBox.valueProperty().bindBidirectional(selectedValue);
selectedValue.set("A");
selectedValue.set("D");
System.out.println("Selected value: " + selectedValue.get());
// RESULTS :
// fx 13.0.1 :
// - with early bidi binding registration (NOTE 1a):
// ---- with change listener on selectedIndex (NOTE 2): Selected value: D
// ---- without change listener on selectedIndex: Selected value: null
// - with late bidi binding registration (NOTE 1b):
// ---- with change listener on selectedIndex (NOTE 2): Selected value: D
// ---- without change listener on selectedIndex: Selected value: D
//
// fx 17.0.1 :
// - with early bidi binding registration (NOTE 1a):
// ---- with change listener on selectedIndex (NOTE 2): Selected value: D
// ---- without change listener on selectedIndex: Selected value: null
// - with late bidi binding registration (NOTE 1b):
// ---- with change listener on selectedIndex (NOTE 2): Selected value: D
// ---- without change listener on selectedIndex: Selected value: null
//
final StackPane mainRoot = new StackPane(new VBox(comboBox));
final Scene scene = new Scene(mainRoot);
stage.setTitle("ComboBoxSetAllDifference13vs17App");
stage.setScene(scene);
stage.sizeToScene();
stage.show();
}
private static void initSelectionListeners(final ComboBox<?> comboBox, String string) {
comboBox.valueProperty().addListener((obs, old, newV) -> {
System.out.println(string + " -- " + "new selected value: " + newV);
});
comboBox.getSelectionModel().selectedItemProperty().addListener((obs, old, newItem) -> {
System.out.println(string + " -- " + "new selected item: " + newItem);
});
// NOTE 2: when this listener is used, it also works in 17.0.1 !!!! -> property becomes valid because of change evaluation!
// comboBox.getSelectionModel().selectedIndexProperty().addListener((obs, old, newIndex) -> {
// System.out.println(string + " -- " + "new selected index: " + newIndex);
// });
// invalidation listener does not make a difference!
comboBox.getSelectionModel().selectedIndexProperty().addListener(obs -> {
System.out.println(string + " -- " + "selected index invalidated");
});
}
public static void main(final String[] args) {
ComboBoxSetAllDifference13vs17App.launch(args);
}
}
// Default output fx 13.0.1 :
// selectedValue property inval listener A
// selectedValue property change listener newV=A valueAdjusting=false
// valueListener: triggering item update
// valueListener: value after item update: null
// valueListener: selection after item update: null
// comboBoxWithSetItems -- new selected item: A
// comboBoxWithSetItems -- selected index invalidated
// comboBoxWithSetItems -- new selected value: A
// selectedValue property inval listener D
// selectedValue property change listener newV=D valueAdjusting=false
// valueListener: triggering item update
// valueListener: value after item update: A
// valueListener: selection after item update: A
// comboBoxWithSetItems -- new selected item: D
// comboBoxWithSetItems -- new selected value: D
// Selected value: D
// Default output fx 17.0.1 :
// selectedValue property inval listener A
// comboBoxWithSetItems -- new selected item: A
// comboBoxWithSetItems -- new selected value: A
// selectedValue property change listener newV=A valueAdjusting=false
// valueListener: triggering item update
// comboBoxWithSetItems -- selected index invalidated
// valueListener: value after item update: A
// valueListener: selection after item update: A
// selectedValue property inval listener D
// comboBoxWithSetItems -- selected index invalidated
// comboBoxWithSetItems -- new selected item: D
// comboBoxWithSetItems -- new selected value: D
// selectedValue property change listener newV=D valueAdjusting=false
// valueListener: triggering item update
// comboBoxWithSetItems -- selected index invalidated
// selectedValue property inval listener null
// selectedValue property change listener newV=null valueAdjusting=true
// ignoring value update during item update
// comboBoxWithSetItems -- selected index invalidated
// comboBoxWithSetItems -- new selected item: null
// comboBoxWithSetItems -- new selected value: null
// valueListener: value after item update: null
// valueListener: selection after item update: null
// Selected value: null
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Register a change listener on the #selectedIndexProperty of the ComboBox's SelectionModel to enforce eager evaluation of the property, which means that the restoration of the index is correctly picked up to also restore the selection.
FREQUENCY : always
- duplicates
-
JDK-8279139 ComboBox loses selected value on second interactive item change via setAll
- Closed
- relates to
-
JDK-8263942 ComboBox: action incorrectly triggered after items.setAll
- Open