【问题标题】:JavaFx CheckBoxTreeItem<Path> selecting root item errorJavaFx CheckBoxTreeItem<Path> 选择根项目错误
【发布时间】:2018-03-03 16:17:41
【问题描述】:

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

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

这是我的第一个 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();
    }
}

更新:

感谢@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);
    }
}

【问题讨论】:

  • 您是否选择了 C:/ 节点?我根据您的建议更改了我的代码。但问题还在继续。顺便说一句,我发现了类似的东西;当我选择复选框时,程序会选择所有子节点。 c:/ 路径有很多孩子。所以我的申请需要很长时间。有没有办法解决这个问题?
  • 不相关:您的 isLeaf 实现是错误的(因为它违反了约束 isLeaf() == leafProperty().get() - 与文档示例相同,但仍然错误;)

标签: java javafx treeview fxml


【解决方案1】:

当您更改 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;
}

【讨论】:

  • 感谢大家的帮助,没关系!只有一个问题;如何获得 item.isIndeterminate() 状态?有可能
  • 这有点复杂。在这种情况下,您需要添加一个新的BooleanProperty,并在(取消)选择时向上遍历树项结构,对于每个祖先,让N 是子项的数量,S 是选定子项的数量。如果N==S 则将selected 设置为true,如果0 &lt; S &lt; N 将indeterminate 设置为true,如果S = 0 将selected 和indeterminate 都设置为false。
  • 谢谢fabian,我试试。非常感谢!
  • fabian 我更新了我的帖子。我添加了不确定的属性。但是有一个问题只是复选框样式。不确定的节点看起来未选中的节点。我怎样才能改变它的风格你能帮我吗?
  • 嗨fabian,有没有办法用你的方式设置独立(假)?因为当一个节点不确定时,复选框图标不显示。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-16
相关资源
最近更新 更多