JavaFX 11 не редактируемый ComboBox не отображает значения вне списка комбинированных элементов должным образом - PullRequest
2 голосов
/ 27 марта 2019

У меня проблемы с JaxaFX 11 ComboBox (кажется, что в JavaFX 8 все работает нормально).

Для не редактируемый комбо , т.е. отображение выбранногозначение в buttoncell (не в редактируемом текстовом поле), значение не отображается (buttoncell, вероятно, считается «пустым»), если новое значение не включено в список элементов комбо , только с одним исключением:

Если предыдущее значение null (например, я отменяю выбор предыдущего ненулевого значения с помощью клавиатуры в всплывающем списке), новое ненулевое значение отображается правильно.

См. Простой код для воспроизведения проблемы.Первоначально комбо-значение null.Нажмите кнопку, чтобы установить значение вне списка элементов.Отображается ОК.Затем выберите значение из всплывающего окна.Попробуйте еще раз нажать кнопку.Теперь комбо остается пустым, хотя значение комбо было изменено.

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);
    }
}

Я что-то упустил?Я не смог найти никакого обходного пути.Я попытался отладить, но не смог найти ответ.Кажется, сначала задан правильный текст, но затем он снова стирается.

(JDK 11.0.2, JavaFX 11.0.2, Netbeans 10)

Ответы [ 2 ]

2 голосов
/ 27 марта 2019

Выглядит для меня как ошибка: по какой-то причине displayNode (то есть содержимое buttonCell) не обновляется при установке незаполненного значения, когда выбрано содержащее значение.Простой доступ к displayNode через его общедоступный API-интерфейс на ComboBoxBaseSkin вызывает правильную настройку.

Чтобы увидеть его обновление, добавьте следующие строки в ваш пример и нажмите эту кнопку, когда не отображается значение без ограничения:

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

Чтобы обойти проблему, мы можем расширить комбо.Скин и принудительное обновление на каждом этапе макета:

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);
    }

}

использование:

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

Примечание: реализация скина интенсивно использует несколько логических грязных флагов, которые, по-видимому, разрушительно взаимодействуют вэто конкретный случай (не понимаю, как именно, к сожалению).Задержка доступа с помощью Platform.runlater, кажется, работает.


Обновление

после некоторого дальнейшего копания выглядит как регрессия , представленная lazy-dirty (не моя формулировка, хотя понравится :) исправить. Пользовательская реализация , предоставленная Томом, прекрасно работает.

1 голос
/ 28 марта 2019

Отвечая на мой собственный вопрос. Я нашел другой возможный обходной путь. Кажется, что это работает, хотя я не уверен, что это «безопасно» (разве ячейка кнопки никогда не должна быть пустой?).

Суть в том, чтобы использовать ячейку пользовательской кнопки , переопределить updateItem(T item, boolean empty) и (в отличие от стандартных реализаций ячеек), ничего не делать (возвращать) для empty = true, т.е. не стирайте ячейку - нет setText(null).

Что позади? Кажется, проблема не в том, что текст ячейки кнопки установлен неправильно, а в том, что он позже стирается какой-то «логикой пустой ячейки» ...

Вы можете добавить этот код в мой образец:

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);
    }
});
...