JavaFX 11 uneditable ComboBox not displaying values outside combo items list properly

…衆ロ難τιáo~ 提交于 2021-02-16 20:13:08

问题


I have troubles with JaxaFX 11 ComboBox (it seems that in JavaFX 8 it works OK).

For uneditable combo, i.e. displaying the selected value in buttoncell (not in editable textbox), no value is displayed (buttoncell probably considered "empty"), if the new value is not included in the combo's items list, with one exception only:

If the previous value is null (I e.g. deselect the previous non null value by keyboard in popup list), the new non null value is displayed correctly.

See a simple code to reproduce the problem. Initially, the combo value is null. Press the button to set a value outside items list. It is displayed OK. Then choose some value from the popup. Try again to press the button. Now the combo stays empty, although the combo value was changed.

import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class ComboTest extends Application {
    private ComboBox<String> testCombo;

    @Override public void start(Stage primaryStage) {
        Button btn = new Button("Set test value outside list");
        btn.setOnAction(e -> {
            testCombo.setValue("test value outside list");
        });

        testCombo = new ComboBox<>(FXCollections.observableArrayList(
                "Option 1", "Option 2", "Option 3"
        ));
        testCombo.setPromptText("null now!");

        TextField valueTextField = new TextField();
        testCombo.valueProperty().addListener((ob, ov, nv) -> {
            valueTextField.setText("combo value: " + nv);
        });

        VBox root = new VBox(5);
        root.setPadding(new Insets(5));
        root.getChildren().addAll(btn, testCombo, valueTextField);

        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Test Combo");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Am I missing something? I was not able to find any workaround. I tried to debug, but could not find the answer. Is seems first the correct text is set, but then it is erased again.

(JDK 11.0.2, JavaFX 11.0.2, Netbeans 10)


回答1:


Looks like a bug for me: for some reason the displayNode (that is the content of the buttonCell) is not updated on setting an uncontained value while a contained value is selected. Merely accessing the displayNode via its public api on ComboBoxBaseSkin triggers the correct setting.

To see it updating, add the following lines to your example and click that Button after the uncontained value is not showing:

Button display = new Button("getDisplayNode");
display.setOnAction(e -> {
    ((ComboBoxBaseSkin) testCombo.getSkin()).getDisplayNode();
});

To hack around the issue, we can extend the combo's skin and force the update in each layout pass:

public static class MyComboBoxSkin<T> extends ComboBoxListViewSkin<T> {

    public MyComboBoxSkin(ComboBox<T> control) {
        super(control);
    }

    @Override
    protected void layoutChildren(double x, double y, double w, double h) {
        super.layoutChildren(x, y, w, h);
        // must be wrapped inside a runlater, either before or after calling super
        Platform.runLater(this::getDisplayNode);
    }

}

usage:

testCombo = new ComboBox<>(FXCollections.observableArrayList("Option 1", "Option 2", "Option 3")) {
    @Override
    protected Skin<?> createDefaultSkin() {
        return new MyComboBoxSkin<>(this);
    }
};

Note: the skin implementation makes heavy use of multiple boolean dirty flags, which seem to interact destructively in this particular case (don't understand how exactly, unfortunately). Delaying the access with a Platform.runlater seems to work.


Update

after some further digging, it looks like being a regression introduced by a lazy-dirty (not my wording, like it though :) fix. The custom cell implementation provided by Tom works nicely.




回答2:


Answering my own question. I have found another possible workaround. It seems to work, although I am not sure it is "safe" (does button cell never need to be empty?).

The point is: using a custom button cell, override updateItem(T item, boolean empty) and (unlike in standard cell implementations), do nothing (return) for empty = true, i.e. do not erase the cell - no setText(null).

What is behind? It seems that the problem is not that the button cell text is not properly set, but that it is erased later by some "empty cell logic"...

You can add this code to my sample:

testCombo.setButtonCell(new ListCell<>() {
    @Override
    protected void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);
        if (empty) {return;} // this is the solution: DO NOT ERASE ON empty=true!
        // further logic copied from the solution in the default skin: 
        // see ComboBoxListViewSkin.updateDisplayText
        // (default testing for "item instanceof Node" omitted for brevity)
        final StringConverter<String> c = testCombo.getConverter();
        final String promptText = testCombo.getPromptText();
        String s = item == null && promptText != null ? promptText
                : c == null ? (item == null ? null : item.toString()) : c.toString(item);
        setText(s);
        setGraphic(null);
    }
});


来源:https://stackoverflow.com/questions/55378762/javafx-11-uneditable-combobox-not-displaying-values-outside-combo-items-list-pro

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!