ComboBox 占位符未随机显示

ComboBox placeholder not showing randomly

我有一个 ComboBox,每次您在另一个组合框中选择特定项目时都会显示。

我有一个 Label 作为占位符,直到加载组合框的内容。它应该显示在组合框的弹出菜单中。大多数时候,它工作正常。但有时,就像每五次或第十次一样,没有显示占位符。相反,只有一个白色单元格,直到内容加载完毕。

在下面的代码中,我设法重现了问题。但这并不完全相同。在我的原始代码中,当我重新加载组合框时,白色单元格可能随时发生,但在我的 MCVE 中,它只在第一次加载组合框时发生(我几乎可以肯定)。

所以当你重现问题时,启动应用程序并在第一个组合框中选择"bar"并检查占位符,如果正常,请重新启动应用程序并重试。如果在先选择 "foo" 然后选择 "bar" 或直接选择 "bar" 时找不到任何区别。

郑重声明,如果组合框仅显示在第一个组合框中的特定选项上,我无法重现该问题,因此这可能会导致问题。但是设计需要那样。

import java.util.Arrays;
import java.util.List;

import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.concurrent.Worker.State;
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 MCVE extends Application {

    @Override
    public void start(Stage stage) {

        VBox root = new VBox();
        root.setPrefSize(200, 200);

        ComboBox<String> selector = new ComboBox<String>();
        selector.getItems().addAll("foo", "bar");

        ComboBox<String> comboBox = new ComboBox<String>();

        root.getChildren().add(selector);

        selector.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> {
            if (newValue.equals("bar")) {
                root.getChildren().add(comboBox);
                comboBox.setEditable(true);

                 // Mock loading task.
                Task<List<String>> task = new Task<List<String>>() {
                    @Override
                    public List<String> call() throws Exception {

                        Thread.sleep(3000);
                        return Arrays.asList("One", "Two", "Three");
                    }
                };

                task.setOnSucceeded(e -> {
                    comboBox.getItems().addAll(
                            task.getValue());
                });

                Label loadLabel = new Label("loading");
                comboBox.setPlaceholder(loadLabel);

                task.stateProperty().addListener(
                        (o, oldState, newState) -> {
                            if (newState == State.RUNNING) {
                                // Do nothing in this MVCE.
                            } else {
                                comboBox.setPlaceholder(null);
                            }
                        });

                new Thread(task).start();

            } else {
                if (root.getChildren().contains(comboBox)) {
                    // This is done to simulate the functionality of the real application, 
                    //where choosing another option would remove the combo box and show something else.
                    comboBox.getItems().clear();
                    root.getChildren().remove(comboBox);    
                }
            }
        });

        Scene scene = new Scene(root);      
        stage.setScene(scene);
        stage.show();
    }

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

另一个问题:加载弹出列表的内容时,列表保持占位符的大小,直到您再次隐藏和显示弹出列表。我希望弹出窗口立即展开。这可能吗?

编辑:Java 版本 1.8.0_60。 OS:windows 8.1 企业。

我无法重现占位符的问题。您使用的 Java 版本是什么?

问题的第二部分。 强制组合框重绘。只需在添加项目之前隐藏它,然后在显示时再次显示它。

            task.setOnSucceeded(e -> {
                boolean isShowing = false;
                if (comboBox.isShowing()) {
                     comboBox.hide();
                     isShowing = true;
                }
                comboBox.getItems().addAll(
                        task.getValue());
                if (isShowing) comboBox.show();
            });

您看不到 "loading" 标签的原因是您在任务的状态侦听器中实施的逻辑:

    Label loadLabel = new Label("loading");
    comboBox.setPlaceholder(loadLabel);

    task.stateProperty().addListener(
            (o, oldState, newState) -> {
                if (newState == State.RUNNING) {
                    // Do nothing in this MVCE.
                } else {
                    comboBox.setPlaceholder(null);
                }
            });

    new Thread(task).start();

根据Javadocs for Worker,一个Task会从READY状态开始,然后过渡到SCHEDULED状态,然后再进入RUNNING 状态。状态转换图类似于:

因此,当它从 READY 过渡到 SCHEDULED 时,您的侦听器会将占位符设置为 null,并且永远不会将其改回。只需将侦听器更改为

    task.stateProperty().addListener(
            (o, oldState, newState) -> {
                if (newState == State.RUNNING) {
                    comboBox.setPlaceholder(loadLabel);
                } else {
                    comboBox.setPlaceholder(null);
                }
            });