无法弄清楚为什么 TreeView 在 JavaFX 中构建为镜像

Mic*_*ims 1 treeview javafx

我在 JavaFX 中的 TreeView 上遇到了一个奇怪的问题。

我使用它的应用程序根据基于 Web 的数据构建分支,因此需要一些时间来填充 TreeView。但随着它的填充,分支显示在顶部底部,但底部只是文本,而不是实际的 TreeItems。

如果有人想亲自尝试的话,我创建了最少的可重复生成的代码。我将最少的代码发布到 GitHub,您可以从这里克隆它

此屏幕截图也将显示该问题。

以下是重现该问题的类:

主要的:

public class Main extends Application {

    public static void main(String[] args) {
        launch(args);
    }
    
    private TreeItem<TreeNode> root;
    private Random random = new Random(System.currentTimeMillis());

    @Override
    public void start(Stage stage) throws Exception {
        root = new TreeItem<>(new TreeNode("Root", false));
        TreeView<TreeNode> treeView = new TreeView<>(root);
        treeView.setCellFactory(param -> new CellFactory());
        treeView.setShowRoot(false);
        ScrollPane scrollPane = new ScrollPane(treeView);
        double width = 500;
        double height = 800;
        AnchorPane ap = new AnchorPane(scrollPane);
        Scene scene = new Scene(ap);
        stage.setScene(scene);
        stage.setWidth(width);
        stage.setHeight(height);
        stage.setOnCloseRequest(e->{
            Platform.exit();
            System.exit(0);
        });
        treeView.setPrefWidth(width);
        treeView.setPrefHeight(height);
        ap.setPrefWidth(width);
        ap.setPrefHeight(height);
        AnchorPane.setRightAnchor(scrollPane, 0.0);
        AnchorPane.setLeftAnchor(scrollPane, 0.0);
        AnchorPane.setTopAnchor(scrollPane, 30.0);
        AnchorPane.setBottomAnchor(scrollPane, 0.0);
        stage.show();
        new Thread(buildTree()).start();
    }

    private String getName() {
        int low = 100;
        int high = 9999;
        return "Node(" + random.nextInt(low, high) + ")";
    }

    private Runnable buildTree() {
        return ()->{
            for (int x = 0; x < 100; x++) {
                boolean checkBoxNode = x % 2 == 0;
                root.getChildren().add(new TreeItem<>(new TreeNode(getName(), checkBoxNode)));
                sleep(500);
            }
        };
    }

    private void sleep(long time) {
        try {
            TimeUnit.MILLISECONDS.sleep(time);
        }
        catch (InterruptedException ignored) {
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

细胞工厂:

public class CellFactory extends TreeCell<TreeNode> {
    private CheckBox checkBox;

    public CellFactory() {
        checkBox = new CheckBox();
        checkBox.setOnAction(event -> {
            TreeNode item = getItem();
            if (item != null) {
                item.setSelected(checkBox.isSelected());
            }
        });

        itemProperty().addListener(((observable, oldValue, treeNode) -> {
            if (treeNode != null) {
                checkBox.setVisible(treeNode.isCheckBoxNode());
            }
        }));
    }

    @Override
    protected void updateItem(TreeNode treeNode, boolean empty) {
        super.updateItem(treeNode, empty);
        if (empty || treeNode == null) {
            setGraphic(null);
        }
        else {
            checkBox.setSelected(treeNode.isSelected());
            setText(treeNode.toString());
            setGraphic(checkBox);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

树节点:

public class TreeNode {
    private String name;
    private boolean selected = true;
    private boolean checkBoxNode;

    public TreeNode(String name, boolean checkBoxNode) {
        this.name = name;
        this.checkBoxNode = checkBoxNode;
    }

    public boolean isSelected() {
        return selected;
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
    }

    public boolean isCheckBoxNode() {
        return checkBoxNode;
    }

    @Override
    public String toString() {
        return name;
    }
}
Run Code Online (Sandbox Code Playgroud)

有谁知道为什么会发生这种情况?

jew*_*sea 5

您的代码中的错误导致了观察到的问题

单元格被重复使用,并且必须在调用中正确更新updateItem

当单元格不为空时,您可以设置单元格的文本。但是,如果之前为空的单元格更新为空值,则不会取消设置文本(将其设置为空)。

请参阅下面的固定代码:

protected void updateItem(TreeNode treeNode, boolean empty) {
    super.updateItem(treeNode, empty);
    if (empty || treeNode == null) {
        setText(null);  // <-- text for previous associated 
                        //     item needs to be cleared for empty cells.
        setGraphic(null);       
    } else {
        checkBox.setSelected(treeNode.isSelected());
        setText(treeNode.toString());  // <--- text has been set here.
        setGraphic(checkBox);
    }
}
Run Code Online (Sandbox Code Playgroud)

示例中不相关的问题或令人困惑的事情

您永远不应该从另一个线程访问或修改与活动场景图关联的元素,您应该使用它Platform.runLater在 JavaFX 线程上添加新的 TreeItems。这不是问题的根源,但这是一个应该解决的问题。

你的 CellFactory 的名字很混乱,它不是一个单元工厂,它是一个 Cell(你可以看到,因为它扩展了 TreeCell)。工厂是生成新单元格的 lambda 的完整回调。

TreeNode 类的名称令人困惑,它不是 JavaFX 节点。是的,它是数据模型中树的一个节点,但鉴于上下文,它对我来说相当混乱。对于其他人来说可能没问题,我不知道。

常问问题

我不完全理解为什么以前的 TreeItem 中的文本会显示在 TreeView 的底部。

单元的创建和放置是内部TreeView实现的一部分。您不必了解内部细节是如何工作的,除非您想更改TreeViewJavaFX 库中的内部实现(您不想这样做)。理解这一点的唯一方法是检查代码并从实现中对设计进行逆向工程。

您只需确保在支持单元格的项目发生更改时正确更新单元格视觉效果。

我猜想,TreeView 在内部可能会移动单元格;有时创建新的,有时重用现有的。但它如何管理细胞并不重要;重要的是它如何管理细胞。只要正确更新单元格,就会显示正确的值。

您的问题示例中发生的奇怪行为是由于代码中的错误而不是 JavaFXTreeView实现中的错误造成的。

另外,您会使用哪些类名来代替CellFactoryTreeNode

可能与您的问题域相关(我不知道)。如果您想要一些非常通用的名称,那么:

  • CellFactory->CustomTreeCellCheckManagementTreeCell
  • TreeNode-> TreeItemData