JavaFX с TreeView элемент в CSS

Я хотел бы создать элемент, который выглядит и ведет себя так, как показано ниже.

enter image description here

есть темный фон и верхний список из 4-х элементов:" библиотеки"," анализ " и так далее. когда мы нажимаем на один из них, список расширяется, и этот элемент и его дети получают светлый темный backgorund. Дополнительно выбранный элемент из дочернего списка получает другой шрифт (полужирный и белый). Кроме того, в то время мог быть расширен только один пункт.

Итак, я выяснил что это поведение TreeView с соответствующими примененными стилями. Я получаю его, работая со следующим кодом:

    TreeView<TabMenuElement> treeView = new TreeView<>(treeRoot);
    treeView.setCellFactory(tv -> new TreeCell<TabMenuElement>() {
        @Override
        public void updateItem(TabMenuElement item, boolean empty) {
            super.updateItem(item, empty);
            setDisclosureNode(null);

            if (empty) {
                setText("");
                setGraphic(null);
            } else {
                setText(item.getName()); // appropriate text for item
                if (item.getIv() != null) {
                    setGraphic(item.getIv());
                }
            }
        }
    });
    treeView.setShowRoot(false);

TabMenuElement имеют метод getIV для получения ImageView (icon), если он определен для этого элемента и getName для получения текста для отображения. Пока что это выглядит так

enter image description here

поэтому у меня есть следующие проблемы:

  1. как изменить шрифт только на выбранном элементе в TreeView?
  2. как настроить фон на выбранном поддерево?
  3. как заставить это самое большее одно поддерево может быть расширено
  4. как установить больший размер для элементов верхнего уровня?

2 ответов


как изменить шрифт только на выбранном элементе в TreeView?

в файле css просто определите шрифт для .tree-cell:selected:

.tree-cell:selected {
    -fx-font-weight: bold ;
}

как настроить фон на выбранном под-дереве?

это немного сложнее. Вы хотите, чтобы фон любого развернутого узла и любого узла, чей родитель не является корнем, был другим (это не так, как вы это сформулировали, но я думаю, что это логически эквивалентно). Развернутый узел уже имеет псевдокласс CSS. Для "родитель не является корнем", вам нужно определить свой собственный псевдокласс:

PseudoClass subElementPseudoClass = PseudoClass.getPseudoClass("sub-tree-item");

теперь наблюдать treeItem свойство ячейки и обновляет состояние псевдокласса при его изменении:

treeView.setCellFactory(tv -> {
    TreeCell<TabMenuElement> cell = new TreeCell<TabMenuElement>() {
        @Override
        public void updateItem(TabMenuElement item, boolean empty) {
            super.updateItem(item, empty);
            setDisclosureNode(null);

            if (empty) {
                setText("");
                setGraphic(null);
            } else {
                setText(item.getName()); // appropriate text for item
                if (item.getIv() != null) {
                    setGraphic(item.getIv());
                }
            }
        }
    };

    cell.treeItemProperty().addListener((obs, oldTreeItem, newTreeItem) -> {
        cell.pseudoClassStateChanged(subElementPseudoClass,
            newTreeItem != null && newTreeItem.getParent() != cell.getTreeView().getRoot());
    }

    return cell ;
});

теперь в вашем файле CSS вы можете сделать

.tree-cell:expanded, .tree-cell:sub-tree-item {
    -fx-background-color: ... ;
}

как заставить, чтобы самое большее одно поддерево могло быть расширено

добавить следующее ChangeListener для каждого TreeItemС расширенной свойства:

ChangeListener<Boolean> expandedListener = (obs, wasExpanded, isNowExpanded) -> {
    if (isNowExpanded) {
        ReadOnlyProperty<?> expandedProperty = (ReadOnlyProperty<?>) obs ;
        Object itemThatWasJustExpanded = expandedProperty.getBean();
        for (TreeItem<TabMenuElement> item : treeView.getRoot().getChildren()) {
            if (item != itemThatWasJustExpanded) {
                item.setExpanded(false);
            }
        }
    }
};

TreeItem<TabMenuElement> biblioteka = new TreeItem<>(...);
biblioteka.expandedProperty().addListener(expandedListener);
TreeItem<TabMenuElement> analiza = new TreeItem<>(...);
analiza.expandedProperty().addListener(expandedListener);
// etc, for all "top-level" items.

как установить больший размер для элементов верхнего уровня?

.tree-cell {
    -fx-padding: 0.75em 0em 0.75em 0em ;
}
.tree-cell:sub-tree-item {
    -fx-padding: 0.25em ;
}

(или измените размер шрифта или аналогичный.)

вот пример:

import javafx.application.Application;
import javafx.beans.property.ReadOnlyProperty;
import javafx.beans.value.ChangeListener;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class StyledUniqueExpandingTree extends Application {

    @Override
    public void start(Stage primaryStage) {
        TreeView<String> tree = new TreeView<>();
        tree.setShowRoot(false);
        TreeItem<String> root = new TreeItem<>("");
        tree.setRoot(root);

        ChangeListener<Boolean> expandedListener = (obs, wasExpanded, isNowExpanded) -> {
            if (isNowExpanded) {
                ReadOnlyProperty<?> expandedProperty = (ReadOnlyProperty<?>) obs ;
                Object itemThatWasJustExpanded = expandedProperty.getBean();
                for (TreeItem<String> item : tree.getRoot().getChildren()) {
                    if (item != itemThatWasJustExpanded) {
                        item.setExpanded(false);
                    }
                }
            }
        };

        for (int i=1; i<=4; i++) {
            TreeItem<String> item = new TreeItem<>("Top level "+i);
            item.expandedProperty().addListener(expandedListener);
            root.getChildren().add(item);
            for (int j=1; j<=4; j++) {
                TreeItem<String> subItem = new TreeItem<>("Sub item "+i+":"+j);
                item.getChildren().add(subItem);
            }
        }

        PseudoClass subElementPseudoClass = PseudoClass.getPseudoClass("sub-tree-item");

        tree.setCellFactory(tv -> {
            TreeCell<String> cell = new TreeCell<String>() {
                @Override
                public void updateItem(String item, boolean empty) {
                    super.updateItem(item, empty);
                    setDisclosureNode(null);

                    if (empty) {
                        setText("");
                        setGraphic(null);
                    } else {
                        setText(item); // appropriate text for item
                    }
                }

            };
            cell.treeItemProperty().addListener((obs, oldTreeItem, newTreeItem) -> {
                cell.pseudoClassStateChanged(subElementPseudoClass,
                        newTreeItem != null && newTreeItem.getParent() != cell.getTreeView().getRoot());
            });
            return cell ;
        });

        BorderPane uiRoot = new BorderPane(tree);
        Scene scene = new Scene(uiRoot, 250, 400);
        scene.getStylesheets().add("styled-unique-expanded-tree.css");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

со стилизованным-уникальным-расширенным-деревом.css:

.tree-view, .tree-cell {
    -fx-background-color: black ;
    -fx-text-fill: white ;
}
.tree-cell:expanded, .tree-cell:sub-tree-item {
    -fx-background-color: #404040 ;
}
.tree-cell:selected {
    -fx-font-weight: bold ;
}
.tree-cell {
    -fx-padding: 0.75em 0em 0.75em 0em ;
}
.tree-cell:sub-tree-item {
    -fx-padding: 0.25em ;
}

Я сам понял, как развернуть на одном клике treeview:

    treeView.setCellFactory(tv -> new TreeCell<TabMenuElement>() {
        @Override
        public void updateItem(TabMenuElement item, boolean empty) {
            super.updateItem(item, empty);
            setDisclosureNode(null);

            if (empty) {
                setText("");
                setGraphic(null);
            } else {
                setText(item.getName()); // appropriate text for item
                setOnMouseClicked(event -> {
                    if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 1) {
                        TreeItem<TabMenuElement> ti = treeItemProperty().get();
                        ti.expandedProperty().set(true);
                    }
                    event.consume();
                });

                if (item.getIv() != null) {
                    setGraphic(item.getIv());
                } else {

                }
            }
        }
    });

специально часть с setOnMouseClicked.

также имея тот же отступ для всех узлов можно сделать в этом случае, установив в

.tree-cell:sub-tree-item{
     -fx-indent: 22;
}

Ну, это для моего случая, когда значки для элемента верхнего уровня имеют ширину 22. Тогда у меня есть только один подуровень, и там я установил тот же идентификатор. Если вы хотите иметь только в целом дереве тот же отступ без icsons, достаточно установить indet 0 для всех .клетка дерева.