JavaFx CheckBoxTreeItem 选择根项目错误

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaFx CheckBoxTreeItem 选择根项目错误相关的知识,希望对你有一定的参考价值。

我正在尝试开发文件复制应用程序。我已经在文件系统上创建了一个包含当前目录的复选框树项。

但是当我选择第一个节点(c:/ directory)时,需要很长时间。如何轻松快速地选择所有目录?

这是我的第一个FXML加载类:

@Override
public void initialize(URL location, ResourceBundle resources) {

    TreeView pathTree = new MyFileTreeView().getMyFilePathTree();
    vBoxFileTree.getChildren().add(pathTree);
}

这是我的treeView组件:

public class MyFileTreeView {

    private TreeView<Path> filePathTree;
    private List<Path> rootDirectories;
    private Logger logger = Logger.getLogger(MyFileTreeView.class);

    public MyFileTreeView() {

        rootDirectories = new ArrayList<>();

        Iterable<Path> roots = FileSystems.getDefault().getRootDirectories();
        for (Path root : roots) {
            rootDirectories.add(root);
        }
    }

    public TreeView getMyFilePathTree() {

        if (filePathTree == null) {

            filePathTree = new TreeView<>(getRootItem());
            filePathTree.setPrefHeight(600.0d);
            filePathTree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
            filePathTree.setCellFactory((TreeView<Path> t) -> new TreeCellImpl());
            filePathTree.setShowRoot(false);
        }

        return filePathTree;
    }

    private TreeItem getRootItem() {

        TreeItem rootItem = new TreeItem();

        for (Path path : rootDirectories) {
            MyFileTreeItem item = new MyFileTreeItem(path);
            item.setIndependent(false);
            rootItem.getChildren().add(item);
            logger.info(path.toString() + " directory has been added to fileTree!");
        }

        return rootItem;
    }
}

这是树项目:

public class MyFileTreeItem extends CheckBoxTreeItem<Path> {

    private boolean isLeaf;
    private boolean isFirstTimeChildren = true;
    private boolean isFirstTimeLeaf = true;

    public MyFileTreeItem(Path path) {
        super(path);
    }

    @Override
    public boolean isLeaf() {

        if (isFirstTimeLeaf) {

            isFirstTimeLeaf = false;
            Path path = getValue();
            isLeaf = Files.isRegularFile(path);
        }
        return isLeaf;
    }

    @Override
    public ObservableList<TreeItem<Path>> getChildren() {

        if (isFirstTimeChildren) {
            isFirstTimeChildren = false;
            super.getChildren().setAll(buildChildren(this));
        }

        return super.getChildren();
    }

    private ObservableList<TreeItem<Path>> buildChildren(CheckBoxTreeItem<Path> treeItem) {

        Path path = treeItem.getValue();
        if ((path != null) && (Files.isDirectory(path))) {

            try (Stream<Path> pathStream = Files.list(path)) {
                return pathStream
                        .map(p -> new MyFileTreeItem(p))
                        .collect(Collectors.toCollection(() ->
                                FXCollections.observableArrayList()));
            } catch (IOException e) {
            }
        }

        return FXCollections.emptyObservableList();
    }
}

error image

更新:

由于@fabian,我添加了不确定的属性

public class FileTreeItem extends TreeItem<Path> {

    private boolean isLeaf;
    private boolean isFirstTimeChildren = true;
    private boolean isFirstTimeLeaf = true;

    private BooleanProperty selected;
    private BooleanProperty indeterminate;

    public FileTreeItem(Path path) {
        this(path, false, false);
    }

    protected FileTreeItem(Path path, boolean selected, boolean indeterminate) {

        super(path);
        this.selected = new SimpleBooleanProperty(selected);
        this.indeterminate = new SimpleBooleanProperty(indeterminate);
        this.selected.addListener((o, oldValue, newValue) -> {

            if (!isLeaf() && !isFirstTimeChildren) {

                if (!isIndeterminate()) {
                    for (TreeItem<Path> ti : getChildren()) {
                        ((FileTreeItem) ti).setSelected(newValue);
                    }
                }

                if (isIndeterminate() && newValue) {
                    setIndeterminate(false);
                    for (TreeItem<Path> ti : getChildren()) {
                        ((FileTreeItem) ti).setSelected(newValue);
                    }
                }
            }

            if (!newValue) {

                if (getParent() instanceof FileTreeItem) {
                    FileTreeItem parent = (FileTreeItem) getParent();
                    parent.setIndeterminate(true);
                    parent.setSelected(false);
                }

            } else {

                if (getParent() instanceof FileTreeItem) {

                    boolean allChildSelected = true;
                    FileTreeItem parent = (FileTreeItem) getParent();
                    for (TreeItem<Path> child : parent.getChildren()) {
                        if (!((FileTreeItem) child).isSelected()) {
                            allChildSelected = false;
                            break;
                        }
                    }

                    if (allChildSelected && !parent.isSelected()) {
                        setIndeterminate(false);
                        parent.setIndeterminate(false);
                        parent.setSelected(true);
                    }
                }
            }
        });
    }

    @Override
    public boolean isLeaf() {

        if (isFirstTimeLeaf) {
            isFirstTimeLeaf = false;
            Path path = getValue();
            isLeaf = Files.isRegularFile(path);
        }
        return isLeaf;
    }

    @Override
    public ObservableList<TreeItem<Path>> getChildren() {

        if (isFirstTimeChildren) {
            isFirstTimeChildren = false;
            super.getChildren().setAll(buildChildren(this));
        }

        return super.getChildren();
    }

    private List<TreeItem<Path>> buildChildren(FileTreeItem treeItem) {

        Path path = treeItem.getValue();
        if ((path != null) && (Files.isDirectory(path))) {

            final boolean select = treeItem.isSelected();
            boolean indeterminate = treeItem.isIndeterminate();

            try (Stream<Path> pathStream = Files.list(path)) {

                List<TreeItem<Path>> res = new ArrayList<>();
                pathStream
                        .map(p -> new FileTreeItem(p, select, indeterminate))
                        .forEach(res::add);
                return res;
            } catch (IOException e) {
            }
        }

        return Collections.emptyList();
    }

    public boolean isSelected() {
        return selected.get();
    }

    public BooleanProperty selectedProperty() {
        return selected;
    }

    public void setSelected(boolean value) {
        selected.set(value);
    }

    public boolean isIndeterminate() {
        return indeterminate.get();
    }

    public BooleanProperty indeterminateProperty() {
        return indeterminate;
    }

    public void setIndeterminate(boolean indeterminate) {
        this.indeterminate.set(indeterminate);
    }
}
答案

当您更改CheckBoxTreeItem的选定状态时,子项的状态将设置为相同的值。这意味着调用了getChildren方法,并且对所有孩子也设置了selected属性。这样,您可以有效地对目录的所有内容进行深度优先遍历。

因此,您需要直接扩展TreeItem并实现所需的属性。您需要确保更新selected属性时,只有在已调用getChildren时才迭代子项:

public class FileTreeItem extends TreeItem<Path> {

    private boolean isLeaf;
    private boolean isFirstTimeChildren = true;
    private boolean isFirstTimeLeaf = true;

    private final BooleanProperty selected;
    private final BooleanProperty indeterminate;

    protected FileTreeItem(Path path, boolean selected) {
        super(path);
        this.indeterminate = new SimpleBooleanProperty();
        this.selected = new SimpleBooleanProperty(selected);
        this.selected.addListener((o, oldValue, newValue) -> {
            if (!updating) {
                if (!isLeaf() && !isFirstTimeChildren) {
                    // propagate selection to children if they were created yet
                    for (TreeItem<Path> ti : getChildren()) {
                        FileTreeItem fti = (FileTreeItem) ti;
                        fti.setSelected(newValue);
                    }
                }

                // update ancestors
                TreeItem<Path> parent = getParent();
                while ((parent instanceof FileTreeItem)
                        && updateAncestorState((FileTreeItem) parent)) {
                    parent = parent.getParent();
                }
            }
        });
    }

    /**
     * flag preventing circular calls during update.
     */
    private boolean updating;

    protected static boolean updateAncestorState(FileTreeItem item) {
        List<TreeItem<Path>> children = item.getChildren();

        boolean hasUnselected = false;
        boolean hasSelected = false;
        for (Iterator<TreeItem<Path>> it = children.iterator();!(hasSelected && hasUnselected) && it.hasNext();) {
            TreeItem<Path> ti = it.next();
            FileTreeItem child = (FileTreeItem) ti;
            if (child.isSelected()) {
                hasSelected = true;
            } else {
                hasUnselected = true;
                if (child.isIndeterminate()) {
                    hasSelected = true;
                }
            }
        }

        item.updating = true;
        boolean changed = false;

        if (hasUnselected) {
            if (item.isSelected() || item.isIndeterminate() != hasSelected) {
                changed = true;
                item.setSelected(false);
                item.setIndeterminate(hasSelected);
            }
        } else {
            if (!item.isSelected()) {
                changed = true;
                item.setSelected(true);
            }
            item.setIndeterminate(false);
        }
        item.updating = false;

        return changed;
    }

    public FileTreeItem(Path path) {
        this(path, false);
    }

    @Override
    public boolean isLeaf() {
        if (isFirstTimeLeaf) {
            isFirstTimeLeaf = false;
            Path path = getValue();
            isLeaf = Files.isRegularFile(path);
        }
        return isLeaf;
    }

    @Override
    public ObservableList<TreeItem<Path>> getChildren() {

        if (isFirstTimeChildren) {
            isFirstTimeChildren = false;
            super.getChildren().setAll(buildChildren(this));
        }

        return super.getChildren();
    }

    private List<TreeItem<Path>> buildChildren(FileTreeItem treeItem) {
        Path path = treeItem.getValue();
        if ((path != null) && (Files.isDirectory(path))) {
            final boolean select = treeItem.isSelected();
            try (Stream<Path> pathStream = Files.list(path)) {
                List<TreeItem<Path>> res = new ArrayList<>();
                pathStream
                        .map(p -> new FileTreeItem(p, select))
                        .forEach(res::add);
                return res;
            } catch (IOException e) {
            }
        }

        return Collections.emptyList();
    }

    /* methods for selected & indeterminate properties */
}

编辑

显示indeterminate属性需要您实现自己的TreeCell

public class FileItemCheckBoxTreeCell extends TreeCell<Path> {

    private BooleanProperty oldSelectedProperty;
    private BooleanProperty oldIndeterminateProperty;

    private final CheckBox checkBox;
    private final StringConverter<TreeItem<Path>> converter;

    public FileItemCheckBoxTreeCell(StringConverter<TreeItem<Path>> converter) {
        if (converter == null) {
            throw new IllegalArgumentException();
        }
        this.converter = converter;
        this.checkBox = new CheckBox();
    }

    @Override
    protected void updateItem(Path item, boolean empty) {
        // clear old binding
        if (oldSelectedProperty != null) {
            checkBox.selectedProperty().unbindBidirectional(oldSelectedProperty);
            checkBox.indeterminateProperty().unbindBidirectional(oldIndeterminateProperty);
            oldSelectedProperty = null;
            oldIndeterminateProperty = null;
        }
        checkBox.indeterminateProperty().unbind();

        super.updateItem(item, empty);

        if (empty) {
            setGraphic(null);
            setText("");
        } else {
            TreeItem<Path> treeItem = getTreeItem();
            setText(converter.toString(treeItem));
            if (treeItem instanceof FileTreeItem) {
                setGraphic(checkBox);
                FileTreeItem fti = (FileTreeItem) treeItem;

                oldSelectedProperty = fti.selectedProperty();
                oldIndeterminateProperty = fti.indeterminateProperty();

                checkBox.selectedProperty().bindBidirectional(oldSelectedProperty);
                checkBox.indeterminateProperty().bindBidirectional(oldIndeterminateProperty);
            } else {
                setGraphic(null);
            }
        }
    }

    public static Callback<TreeView<Path>, TreeCell<Path>> forTreeView(StringConverter<TreeItem<Path>> converter) {
        if (converter == null) {
            throw new IllegalArgumentException();
        }
        return tv -> new FileItemCheckBoxTreeCell(converter);
    }

}
public TreeView getMyFilePathTree() {

    if (filePathTree == null) {

        filePathTree = new TreeView<>(getRootItem());
        filePathTree.setPrefHeight(600.0d);
         filePathTree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

        // tell cell factory to use the selected property for checkbox
        filePathTree.setCellFactory(FileItemCheckBoxTreeCell.forTreeView(new StringConverter<TreeItem<Path>>() {

            @Override
            public String toString(TreeItem<Path> object) {
                if (object == null) {
                    return "";
                }
                Path p = object.getValue();
                if (p == null) {
                    return "";
                }
                p = p.getFileName();
                return p == null ? object.getValue().toString() : p.toString();
            }

            @Override
            public TreeItem<Path> fromString(String string) {
                throw new UnsupportedOperationException();
            }

        }));
        filePathTree.setShowRoot(false);
    }

    return filePathTree;
}

以上是关于JavaFx CheckBoxTreeItem 选择根项目错误的主要内容,如果未能解决你的问题,请参考以下文章

JavaFX - 从其他组合框中删除所选项目

JavaFX:禁用使用键盘控制 RadioButtons

javaFX-使用gradle+jdk14创建javafx程序

JavaFX:禁用键盘控制RadioButtons

从 JavaFX TableView 中获取选定项目

在 CheckListView JavaFX 上实现全选