问题
The caret of an editable ComboBox is missing after porting an application from JavaFX 2.2 to JavaFX 8. The ComboBox should be switched to editable on selecting an item. I tested it under Windows 8.1 with Oracle JDK 1.8 Update 102 and Update 112.
When the ComboBox lost focus and regained focus, the caret is visible.
It actually works on JavaFX 2.2 after changing the lambda to an interface implementation and removing the Platform.runLater
.
I included a SSCCE for testing:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TestEditableComboBox extends Application{
@Override
public void start(Stage primaryStage) throws Exception{
ComboBox<String> comboBox = new ComboBox<String>(FXCollections.observableArrayList("item 1",
"item 2",
"editable"));
comboBox.setMinWidth(100D);
comboBox.setMaxWidth(100D);
comboBox.valueProperty().addListener((observable,
oldValue,
newValue) -> {
if (newValue != null){
if ("editable".equals(newValue)){
// JavaFX 2.2: comboBox.setEditable(true);
Platform.runLater(() -> comboBox.setEditable(true));
}
else{
// JavaFX 2.2: comboBox.setEditable(true);
Platform.runLater(() -> {
comboBox.setEditable(false);
comboBox.getSelectionModel().select(newValue);
});
}
}
});
VBox vBox = new VBox(new Label("Broken caret"),
comboBox);
Scene scene = new Scene(vBox);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args){
Application.launch(args);
}
}
Anyone have an idea to work this around? Or is this a JavaFX 8 regression bug?
PS: Without the Platform.runLater
the ComboBox throws a java.lang.IndexOutOfBoundsException
because its model is modified while another modification is in progress.
回答1:
I found a workaround for this issue.
The internal implementation of ComboBox at class com.sun.javafx.scene.control.skin.ComboBoxPopupControl
have an specialized class called FakeFocusTextField
which extends javafx.scene.control.TextField
. An instance of this class is given back when calling ComboBox::getEditor
.
Surprisingly the FakeFocusTextField
class have a public method called setFakeFocus
which gives the focus to the text field. The requestFocus
method in this class gives the focus to its parent.
The workaround is to change the line of code which sets the comboBox editable from:
Platform.runLater(() -> comboBox.setEditable(true));
to
Platform.runLater(() -> {
comboBox.setEditable(true);
if (this.getEditor() instanceof FakeFocusTextField){
((FakeFocusTextField) this.getEditor()).setFakeFocus(true);
}
}
Unfortunately this workaround uses JavaFX classes outside of the JavaFX 8 API. It may break if the implementation is changed and might not work on future releases (like Java 9).
回答2:
I failed to find a better strategy than fireandfuel, however, I would add some extra guards around the non-standard API calls. Only attempting the "bodge" if the JavaFX version is 8, and surrounding with a try catch, in order to minimise the chance of failure with future versions of JavaFX.
Also of interest, if you call the setFakeFocus "bodge" to TWO ComboBoxes, it is possible for BOTH of them to appear to have selection. My fix for this, is to request focus in the normal way first before applying the "bodge".
Here's my full solution in Kotlin :
val javaFXVersion by lazy { System.getProperties().getProperty("javafx.runtime.version") }
fun ComboBox<*>.requestFocusWithCaret() {
requestFocus()
if (javaFXVersion.startsWith("8.")) {
try {
val theEditor = editor
if (theEditor is FakeFocusTextField) {
theEditor.setFakeFocus(true)
}
} catch (e: Exception) {
}
}
}
Which can then be called using :
myComboBox.requestFocusWithCaret()
来源:https://stackoverflow.com/questions/40239400/javafx-8-missing-caret-in-switch-editable-combobox