【问题标题】:Apply css on specific rows on javaFX在 javaFX 上的特定行上应用 css
【发布时间】:2016-12-28 22:36:50
【问题描述】:

我在我的项目中使用 TreeTableView,当用户选择一行时,我想做一些特定的事情:

我希望该行具有不同的背景颜色,但我也希望其子项和父项也具有这种颜色。

我找到了一种访问每一行和每个子项的方法,但我只是不知道如何指定此背景颜色。我尝试使用我的自定义 TreeTableCells 并在我的 updateItem 方法中添加样式来执行此操作,但每次选择项目时都不会调用此方法。

所以我想尝试在我的 treetableview 中添加监听器,这似乎是一个更好的主意,但实际上我无法访问这些行来赋予它们任何样式。

【问题讨论】:

标签: javafx


【解决方案1】:

这里的基本策略是:

  1. 为您要突出显示的条件创建CSS PseudoClass instances(在下面的示例中,我为“选定的子项”创建了一个,为“选定的父项”创建了一个)
  2. 使用rowFactory 为表创建行。这些行应该在updateItem 方法中更新它们的伪类状态,并且如果所选项目发生变化,还应该更新它们的pseudoclass 状态。您可以在表格的选定项目上放置一个侦听器来执行其中的第二项。
  3. 使用您在第一步中定义的伪类,在外部 CSS 文件中添加 CSS 以按照您想要的方式设置行的样式。

这是一个 SSCCE:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener.Change;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableRow;
import javafx.scene.control.TreeTableView;
import javafx.stage.Stage;

public class TreeTableViewHighlightSelectedPath extends Application {

    private PseudoClass childOfSelected = PseudoClass.getPseudoClass("child-of-selected");
    private PseudoClass parentOfSelected = PseudoClass.getPseudoClass("parent-of-selected");

    @Override
    public void start(Stage primaryStage) {
        TreeTableView<Item> table = new TreeTableView<>(createRandomTree(50));

        table.setRowFactory(ttv -> {
            TreeTableRow<Item> row = new TreeTableRow<Item>() {
                @Override
                protected void updateItem(Item item, boolean empty) {
                    super.updateItem(item, empty);
                    if (empty) {
                        pseudoClassStateChanged(parentOfSelected, false);
                        pseudoClassStateChanged(childOfSelected, false);
                    } else {
                        updateState(this);
                    }
                }
            };

            table.getSelectionModel().getSelectedItems().addListener(
                    (Change<? extends TreeItem<Item>> c) -> updateState(row));

            return row ;
        });

        table.getColumns().add(column("Item", Item::nameProperty));
        table.getColumns().add(column("Value", Item::valueProperty));

        Scene scene = new Scene(table, 800, 800);
        scene.getStylesheets().add("table-row-highlight.css");
        primaryStage.setScene(scene);
        primaryStage.show();

    }

    private <T> void updateState(TreeTableRow<T> row) {
        TreeTableView<T> table = row.getTreeTableView() ;
        TreeItem<T> item = row.getTreeItem();

        // if item is selected, just use default "selected" highlight,
        // and set "child-of-selected" and "parent-of-selected" to false:
        if (item == null || table.getSelectionModel().getSelectedItems().contains(item)) {
            row.pseudoClassStateChanged(childOfSelected, false);
            row.pseudoClassStateChanged(parentOfSelected, false);
            return ;
        }

        // check to see if item is parent of any selected item:
        for (TreeItem<T> selectedItem : table.getSelectionModel().getSelectedItems()) {
            for (TreeItem<T> parent = selectedItem.getParent(); parent != null ; parent = parent.getParent()) {
                if (parent == item) {
                    row.pseudoClassStateChanged(parentOfSelected, true);
                    row.pseudoClassStateChanged(childOfSelected, false);
                    return ;
                }
            }
        }

        // check to see if item is child of any selected item:
        for (TreeItem<T> ancestor = item.getParent() ; ancestor != null ; ancestor = ancestor.getParent()) {
            if (table.getSelectionModel().getSelectedItems().contains(ancestor)) {
                row.pseudoClassStateChanged(childOfSelected, true);
                row.pseudoClassStateChanged(parentOfSelected, false);
                return ;
            }
        }

        // if we got this far, clear both pseudoclasses:

        row.pseudoClassStateChanged(childOfSelected, false);
        row.pseudoClassStateChanged(parentOfSelected, false);

    }

    private <S,T> TreeTableColumn<S,T> column(String title, Function<S, ObservableValue<T>> property) {
        TreeTableColumn<S,T> column = new TreeTableColumn<>(title);
        column.setCellValueFactory(cellData -> property.apply(cellData.getValue().getValue()));
        return column ;
    }

    private TreeItem<Item> createRandomTree(int numNodes) {
        Random rng = new Random();
        TreeItem<Item> root = new TreeItem<>(new Item("Item 1", rng.nextInt(1000)));
        root.setExpanded(true);
        List<TreeItem<Item>> items = new ArrayList<>();
        items.add(root);

        for (int i = 2 ; i <= numNodes; i++) {
            Item item = new Item("Item "+i, rng.nextInt(1000));
            TreeItem<Item> treeItem = new TreeItem<>(item);
            treeItem.setExpanded(true);
            items.get(rng.nextInt(items.size())).getChildren().add(treeItem);
            items.add(treeItem);
        }

        return root ;
    }

    public static class Item {
        private StringProperty name = new SimpleStringProperty();
        private IntegerProperty value = new SimpleIntegerProperty();

        public Item(String name, int value) {
            setName(name);
            setValue(value);
        }

        public final StringProperty nameProperty() {
            return this.name;
        }


        public final java.lang.String getName() {
            return this.nameProperty().get();
        }


        public final void setName(final java.lang.String name) {
            this.nameProperty().set(name);
        }


        public final IntegerProperty valueProperty() {
            return this.value;
        }


        public final int getValue() {
            return this.valueProperty().get();
        }


        public final void setValue(final int value) {
            this.valueProperty().set(value);
        }

        @Override
        public String toString() {
            return String.format("%s (%d)", getName(), getValue());
        }

    }

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

和 CSS 文件(table-row-highlight.css):

.tree-table-row-cell:child-of-selected {
    -fx-background: green ;
}
.tree-table-row-cell:parent-of-selected {
    -fx-background: salmon ;
}

这给出以下内容:

此版本突出显示树中所选项目的所有后代节点和所有祖先节点。如果您只想突出显示直接子行和父行,可以简化 updateState() 方法。

【讨论】:

  • 非常感谢,我会根据我的代码调整它!这正是我想做的事情!
  • @WilliamEstupina 不客气。如果回答了您的问题,请将答案标记为正确;如果他们有类似的问题,它会让其他人更容易找到它。
  • 我试过了,但我只是不知道如何标记它...我只是将其重命名为已解决?
  • @WilliamEstupina 点击答案旁边的复选标记。
  • 多亏了你,我才能让它工作。但是只剩下一个问题:它在选择更改时与侦听器配合得很好,但是当我展开我的表格然后取消展开它时我遇到了问题。一些空行正在着色。但是当我再次选择另一个项目时,这会停止错误。所以我想在进行或取消 ax 扩展时触发 updateState 函数。他们有什么简单的方法可以让 TreeTableViews 的扩展监听器吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-24
  • 2019-08-01
  • 2021-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多