JavaFX 11不可编辑组合框未正确显示组合项列表之外的值

JavaFX 11不可编辑组合框未正确显示组合项列表之外的值,java,javafx,combobox,javafx-11,Java,Javafx,Combobox,Javafx 11,我对jaxafx11ComboBox有一些问题(在javafx8中,它似乎可以正常工作) 对于不可编辑的组合,即在buttoncell(不在可编辑文本框中)中显示所选值,如果新值未包含在组合的项目列表中,则不显示任何值(buttoncell可能被视为“空”),只有一个例外: 如果以前的值为null(例如,通过键盘在弹出列表中取消选择以前的非null值),则新的非null值将正确显示 请参阅一个简单的代码来重现该问题。最初,组合值为null。按下按钮可在项目列表之外设置值。它显示为OK(正常)。然

我对jaxafx11
ComboBox
有一些问题(在javafx8中,它似乎可以正常工作)

对于不可编辑的组合,即在buttoncell(不在可编辑文本框中)中显示所选值,如果新值未包含在组合的项目列表中,则不显示任何值(buttoncell可能被视为“空”),只有一个例外:

如果以前的值为
null
(例如,通过键盘在弹出列表中取消选择以前的非null值),则新的非null值将正确显示

请参阅一个简单的代码来重现该问题。最初,组合值为
null
。按下按钮可在项目列表之外设置值。它显示为OK(正常)。然后从弹出窗口中选择一些值。再按一下按钮。现在,组合保持为空,尽管组合值已更改

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);
    }
}
导入javafx.application.application;
导入静态javafx.application.application.launch;
导入javafx.collections.FXCollections;
导入javafx.geometry.Insets;
导入javafx.scene.scene;
导入javafx.scene.control.Button;
导入javafx.scene.control.ComboBox;
导入javafx.scene.control.TextField;
导入javafx.scene.layout.VBox;
导入javafx.stage.stage;
公共类ComboTest扩展了应用程序{
私有组合框testCombo;
@覆盖公共无效开始(阶段primaryStage){
按钮btn=新按钮(“设置列表外的测试值”);
btn.设置动作(e->{
setValue(“列表外的测试值”);
});
testCombo=新组合框(FXCollections.observableArrayList(
选项1、选项2、选项3
));
setPrompText(“现在为空!”);
TextField valueTextField=新的TextField();
testCombo.valueProperty().addListener((ob、ov、nv)->{
valueTextField.setText(“组合值:+nv”);
});
VBox根=新的VBox(5);
根。设置填充(新插图(5));
root.getChildren().addAll(btn、testCombo、valueTextField);
场景=新场景(根,300,250);
primaryStage.setTitle(“测试组合”);
初级阶段。场景(场景);
primaryStage.show();
}
公共静态void main(字符串[]args){
发射(args);
}
}
我错过什么了吗?我找不到任何解决办法。我试图调试,但找不到答案。似乎先设置了正确的文本,然后又将其删除


(JDK 11.0.2、JavaFX 11.0.2、Netbeans 10)

对我来说似乎是个bug:由于某种原因,在选择包含的值时,设置未包含的值时,displayNode(即buttonCell的内容)不会更新。仅通过ComboBoxBaseSkin上的公共api访问displayNode即可触发正确的设置

要查看其更新,请将以下行添加到示例中,并在未显示未包含的值后单击该按钮:

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

}
公共静态类MyComboxSkin扩展了ComboxListViewSkin{
公共MyComboxSkin(组合框控件){
超级(控制);
}
@凌驾
受保护的无效布局子项(双x、双y、双w、双h){
超级布局(x,y,w,h);
//必须在调用super之前或之后包装在runlater中
runLater(this::getDisplayNode);
}
}
用法:

testCombo = new ComboBox<>(FXCollections.observableArrayList("Option 1", "Option 2", "Option 3")) {
    @Override
    protected Skin<?> createDefaultSkin() {
        return new MyComboBoxSkin<>(this);
    }
};
testCombo=new组合框(FXCollections.observableAryList(“选项1”、“选项2”、“选项3”)){
@凌驾
受保护的皮肤createDefaultSkin(){
返回新的MyComboxSkin(此);
}
};
注意:皮肤实现大量使用了多个布尔脏标志,在这种特殊情况下,这些标志似乎会破坏性地相互作用(不幸的是,我不知道具体是如何实现的)。使用Platform.runlater延迟访问似乎有效


更新


经过进一步的挖掘,它看起来像是由一个(不是我的措辞,尽管如此:)补丁引入的。汤姆提供的答案很好。

回答了我自己的问题。我找到了另一个可能的解决办法。虽然我不确定它是否“安全”(纽扣电池是否永远都不需要为空?)

要点是:使用自定义按钮单元格,覆盖
updateItem(T项,布尔空)
,并且(与标准单元格实现不同,不为
空=真
执行任何操作(返回),即不删除单元格-no
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);
    }
});
testCombo.setButtonCell(新的ListCell(){
@凌驾
受保护的void updateItem(字符串项,布尔值为空){
super.updateItem(项,空);
if(empty){return;}//这是解决方案:在empty=true时不要擦除!
//从默认外观中的解决方案复制的其他逻辑:
//请参阅ComboBoxListViewSkin.updateDisplayText
//(为简洁起见,省略了“节点的项目实例”的默认测试)
final StringConverter c=testCombo.getConverter();
最后一个字符串promptext=testCombo.getpromptext();
字符串s=item==null&&promptext!=null?promptext
:c==null?(item==null?null:item.toString()):c.toString(item);
setText(s);
设置图形(空);
}
});

hmm。。。听起来像是一个我认为早已修复的老bug…@kleopatra我试图在ListCell中调试updateItem(int oldIndex),我认为emtpy=true可能起源于此,但我无法获得逻辑…这有点奇怪:我看到了错误行为,但所有测试都通过了,一定是错过了