JavaFX:如何突出显示 TreeView 中的某些项目
JavaFX: How to highlight certain Items in a TreeView
我正在尝试在 JavaFX 中实现 TreeView
的搜索功能。我想在用户按下回车键时突出显示所有匹配项。所以我在我的 TreeItem
中添加了一个 boolean isHighlighted
,在我的 TreeCell
s updateItem
中,我检查项目是否 isHighlighted
,如果是,我应用某个 CSS。在搜索时不可见的 items/cells 一切正常——当我滚动到它们时,它们被正确突出显示。问题是:如何 "repaint" TreeCells 在搜索时可见,以便它们反映它们的项目是否 isHighlighted
?我的控制器目前没有对 TreeView
创建的 TreeCells
的任何引用。
此答案基于 this one,但适用于 TreeView
而不是 TableView
,并更新为使用 JavaFX 8 功能(大大减少了所需的代码量)。
一个策略是维护 ObservableSet
of TreeItems
匹配搜索(这有时对您可能想要的其他功能很有用)。使用 CSS PseudoClass
和外部 CSS 文件来突出显示所需的单元格。您可以在绑定到单元格的 treeItemProperty
和 ObservableSet
的单元格工厂中创建一个 BooleanBinding
,如果集合包含单元格的当前树项,则评估为 true
。然后只需使用绑定注册一个侦听器,并在单元格更改时更新其伪类状态。
这是一个 SSCCE。它包含一棵树,其项目为 Integer
值。当您在搜索框中键入时,它将更新搜索,匹配那些值是输入值的倍数的值。
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
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 TreeWithSearchAndHighlight extends Application {
@Override
public void start(Stage primaryStage) {
TreeView<Integer> tree = new TreeView<>(createRandomTree(100));
// keep track of items that match our search:
ObservableSet<TreeItem<Integer>> searchMatches = FXCollections.observableSet(new HashSet<>());
// cell factory returns an instance of TreeCell implementation defined below.
// pass the cell implementation a reference to the set of search matches
tree.setCellFactory(tv -> new SearchHighlightingTreeCell(searchMatches));
// search text field:
TextField textField = new TextField();
// allow only numeric input:
textField.setTextFormatter(new TextFormatter<Integer>(change ->
change.getControlNewText().matches("\d*")
? change
: null));
// when the text changes, update the search matches:
textField.textProperty().addListener((obs, oldText, newText) -> {
// clear search:
searchMatches.clear();
// if no text, or 0, just exit:
if (newText.isEmpty()) {
return ;
}
int searchValue = Integer.parseInt(newText);
if (searchValue == 0) {
return ;
}
// search for matching nodes and put them in searchMatches:
Set<TreeItem<Integer>> matches = new HashSet<>();
searchMatchingItems(tree.getRoot(), matches, searchValue);
searchMatches.addAll(matches);
});
BorderPane root = new BorderPane(tree, textField, null, null, null);
BorderPane.setMargin(textField, new Insets(5));
BorderPane.setMargin(tree, new Insets(5));
Scene scene = new Scene(root, 600, 600);
// stylesheet sets style for cells matching search by using the selector
// .tree-cell:search-match
// (specified in the initalization of the Pseudoclass at the top of the code)
scene.getStylesheets().add("tree-highlight-search.css");
primaryStage.setScene(scene);
primaryStage.show();
}
// find all tree items whose value is a multiple of the search value:
private void searchMatchingItems(TreeItem<Integer> searchNode, Set<TreeItem<Integer>> matches, int searchValue) {
if (searchNode.getValue() % searchValue == 0) {
matches.add(searchNode);
}
for (TreeItem<Integer> child : searchNode.getChildren()) {
searchMatchingItems(child, matches, searchValue);
}
}
// build a random tree with numNodes nodes (all nodes expanded):
private TreeItem<Integer> createRandomTree(int numNodes) {
List<TreeItem<Integer>> items = new ArrayList<>();
TreeItem<Integer> root = new TreeItem<>(1);
root.setExpanded(true);
items.add(root);
Random rng = new Random();
for (int i = 2 ; i <= numNodes ; i++) {
TreeItem<Integer> item = new TreeItem<>(i);
item.setExpanded(true);
TreeItem<Integer> parent = items.get(rng.nextInt(items.size()));
parent.getChildren().add(item);
items.add(item);
}
return root ;
}
public static class SearchHighlightingTreeCell extends TreeCell<Integer> {
// must keep reference to binding to prevent premature garbage collection:
private BooleanBinding matchesSearch ;
public SearchHighlightingTreeCell(ObservableSet<TreeItem<Integer>> searchMatches) {
// pseudoclass for highlighting state
// css can set style with selector
// .tree-cell:search-match { ... }
PseudoClass searchMatch = PseudoClass.getPseudoClass("search-match");
// initialize binding. Evaluates to true if searchMatches
// contains the current treeItem
// note the binding observes both the treeItemProperty and searchMatches,
// so it updates if either one changes:
matchesSearch = Bindings.createBooleanBinding(() ->
searchMatches.contains(getTreeItem()),
treeItemProperty(), searchMatches);
// update the pseudoclass state if the binding value changes:
matchesSearch.addListener((obs, didMatchSearch, nowMatchesSearch) ->
pseudoClassStateChanged(searchMatch, nowMatchesSearch));
}
// update the text when the item displayed changes:
@Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? null : "Item "+item);
}
}
public static void main(String[] args) {
launch(args);
}
}
CSS 文件 tree-highlight-search.css 只需包含突出显示单元格的样式:
.tree-cell:search-match {
-fx-background: yellow ;
}
我正在尝试在 JavaFX 中实现 TreeView
的搜索功能。我想在用户按下回车键时突出显示所有匹配项。所以我在我的 TreeItem
中添加了一个 boolean isHighlighted
,在我的 TreeCell
s updateItem
中,我检查项目是否 isHighlighted
,如果是,我应用某个 CSS。在搜索时不可见的 items/cells 一切正常——当我滚动到它们时,它们被正确突出显示。问题是:如何 "repaint" TreeCells 在搜索时可见,以便它们反映它们的项目是否 isHighlighted
?我的控制器目前没有对 TreeView
创建的 TreeCells
的任何引用。
此答案基于 this one,但适用于 TreeView
而不是 TableView
,并更新为使用 JavaFX 8 功能(大大减少了所需的代码量)。
一个策略是维护 ObservableSet
of TreeItems
匹配搜索(这有时对您可能想要的其他功能很有用)。使用 CSS PseudoClass
和外部 CSS 文件来突出显示所需的单元格。您可以在绑定到单元格的 treeItemProperty
和 ObservableSet
的单元格工厂中创建一个 BooleanBinding
,如果集合包含单元格的当前树项,则评估为 true
。然后只需使用绑定注册一个侦听器,并在单元格更改时更新其伪类状态。
这是一个 SSCCE。它包含一棵树,其项目为 Integer
值。当您在搜索框中键入时,它将更新搜索,匹配那些值是输入值的倍数的值。
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
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 TreeWithSearchAndHighlight extends Application {
@Override
public void start(Stage primaryStage) {
TreeView<Integer> tree = new TreeView<>(createRandomTree(100));
// keep track of items that match our search:
ObservableSet<TreeItem<Integer>> searchMatches = FXCollections.observableSet(new HashSet<>());
// cell factory returns an instance of TreeCell implementation defined below.
// pass the cell implementation a reference to the set of search matches
tree.setCellFactory(tv -> new SearchHighlightingTreeCell(searchMatches));
// search text field:
TextField textField = new TextField();
// allow only numeric input:
textField.setTextFormatter(new TextFormatter<Integer>(change ->
change.getControlNewText().matches("\d*")
? change
: null));
// when the text changes, update the search matches:
textField.textProperty().addListener((obs, oldText, newText) -> {
// clear search:
searchMatches.clear();
// if no text, or 0, just exit:
if (newText.isEmpty()) {
return ;
}
int searchValue = Integer.parseInt(newText);
if (searchValue == 0) {
return ;
}
// search for matching nodes and put them in searchMatches:
Set<TreeItem<Integer>> matches = new HashSet<>();
searchMatchingItems(tree.getRoot(), matches, searchValue);
searchMatches.addAll(matches);
});
BorderPane root = new BorderPane(tree, textField, null, null, null);
BorderPane.setMargin(textField, new Insets(5));
BorderPane.setMargin(tree, new Insets(5));
Scene scene = new Scene(root, 600, 600);
// stylesheet sets style for cells matching search by using the selector
// .tree-cell:search-match
// (specified in the initalization of the Pseudoclass at the top of the code)
scene.getStylesheets().add("tree-highlight-search.css");
primaryStage.setScene(scene);
primaryStage.show();
}
// find all tree items whose value is a multiple of the search value:
private void searchMatchingItems(TreeItem<Integer> searchNode, Set<TreeItem<Integer>> matches, int searchValue) {
if (searchNode.getValue() % searchValue == 0) {
matches.add(searchNode);
}
for (TreeItem<Integer> child : searchNode.getChildren()) {
searchMatchingItems(child, matches, searchValue);
}
}
// build a random tree with numNodes nodes (all nodes expanded):
private TreeItem<Integer> createRandomTree(int numNodes) {
List<TreeItem<Integer>> items = new ArrayList<>();
TreeItem<Integer> root = new TreeItem<>(1);
root.setExpanded(true);
items.add(root);
Random rng = new Random();
for (int i = 2 ; i <= numNodes ; i++) {
TreeItem<Integer> item = new TreeItem<>(i);
item.setExpanded(true);
TreeItem<Integer> parent = items.get(rng.nextInt(items.size()));
parent.getChildren().add(item);
items.add(item);
}
return root ;
}
public static class SearchHighlightingTreeCell extends TreeCell<Integer> {
// must keep reference to binding to prevent premature garbage collection:
private BooleanBinding matchesSearch ;
public SearchHighlightingTreeCell(ObservableSet<TreeItem<Integer>> searchMatches) {
// pseudoclass for highlighting state
// css can set style with selector
// .tree-cell:search-match { ... }
PseudoClass searchMatch = PseudoClass.getPseudoClass("search-match");
// initialize binding. Evaluates to true if searchMatches
// contains the current treeItem
// note the binding observes both the treeItemProperty and searchMatches,
// so it updates if either one changes:
matchesSearch = Bindings.createBooleanBinding(() ->
searchMatches.contains(getTreeItem()),
treeItemProperty(), searchMatches);
// update the pseudoclass state if the binding value changes:
matchesSearch.addListener((obs, didMatchSearch, nowMatchesSearch) ->
pseudoClassStateChanged(searchMatch, nowMatchesSearch));
}
// update the text when the item displayed changes:
@Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? null : "Item "+item);
}
}
public static void main(String[] args) {
launch(args);
}
}
CSS 文件 tree-highlight-search.css 只需包含突出显示单元格的样式:
.tree-cell:search-match {
-fx-background: yellow ;
}