2016-01-21 129 views
0

我試圖在JavaFX中實現TreeView的搜索功能。我想在用戶點擊回車鍵時突出顯示所有匹配項。所以我在TreeItem和我的TreeCell s updateItem上加了一個boolean isHighlighted,我檢查是否項isHighlighted,如果是這樣我申請了某個CSS。一切工作正常的項目/細胞搜索時不可見 - 當我滾動到他們,他們正確地突出顯示。問題是:如何在搜索時「重新繪製」可見的TreeCell,以便它們反映它們的項目isHighlighted?我的控制器目前沒有任何參考TreeCellsTreeView創建。JavaFX:如何突出顯示TreeView中的某些項目

回答

1

此答案基於this one,但適用於TreeView而不是TableView,並更新爲使用JavaFX 8功能(極大地減少了所需的代碼量)。

對此的一個策略是維護與搜索相匹配的ObservableSetTreeItems(這對於您可能想要的其他功能有時會有用)。使用CSS PseudoClass和外部CSS文件來突出顯示所需的單元格。您可以在單元格工廠中創建一個BooleanBinding,該單元工廠綁定到單元格的treeItemPropertyObservableSet,如果集合包含單元格的當前樹項目,則評估爲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文件樹亮點,search.css只是必須包含一個風格突出顯示單元:

.tree-cell:search-match { 
    -fx-background: yellow ; 
} 
+0

這工作近乎完美。我不得不更換一個事件處理,這樣它會突出TreeCells:? 'Platform.runLater(() - > { 事件處理程序<超表示ScrollEvent> E = myTreeView.getChildrenUnmodifiable()得到(0).getOnScroll() ; myTreeView.getChildrenUnmodifiable()得到(0).setOnScroll。((事件) - > { e.handle(事件); 搜索(s.SearchTextField.getText()); } }) ; });' – spilot

+0

無論如何,非常感謝。您的解決方案不僅工作得很好,而且還很優雅。 – spilot

+0

我真的不明白爲什麼你需要額外的黑客,或者它真的做了什麼。只要您在進行新搜索時更新'ObservableSet',樹就會自動更新。 –

相關問題