Close

Java Swing - JTree Filtering and Highlighting Example

[Updated: Mar 6, 2018, Created: Mar 6, 2018]

This example shows how to filter JTree nodes and highlighting the matched nodes.

Example

Creating JTree

public class TreeExampleMain {
  public static void main(String[] args) {
      TreeNode projectHierarchyTreeNode =
              TradingProjectDataService.instance.getProjectHierarchy();
      JTree tree = new JTree(projectHierarchyTreeNode);
      JTreeUtil.setTreeExpandedState(tree, true);
      TreeFilterDecorator filterDecorator = TreeFilterDecorator.decorate(tree, createUserObjectMatcher());
      tree.setCellRenderer(new TradingProjectTreeRenderer(() -> filterDecorator.getFilterField().getText()));
      JFrame frame = createFrame();
      frame.add(new JScrollPane(tree));
      frame.add(filterDecorator.getFilterField(), BorderLayout.NORTH);
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
  }

  private static BiPredicate<Object, String> createUserObjectMatcher() {
      return (userObject, textToFilter) -> {
          if (userObject instanceof ProjectParticipant) {
              ProjectParticipant pp = (ProjectParticipant) userObject;
              return pp.getName().toLowerCase().contains(textToFilter)
                      || pp.getRole().toLowerCase().contains(textToFilter);
          } else if (userObject instanceof Project) {
              Project project = (Project) userObject;
              return project.getName().toLowerCase().contains(textToFilter);
          } else {
              return userObject.toString().toLowerCase().contains(textToFilter);
          }
      };
  }

  private static JFrame createFrame() {
      JFrame frame = new JFrame("JTree Filtering example");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setSize(new Dimension(500, 400));
      return frame;
  }
}

Tree filtering decorator

public class TreeFilterDecorator {
  private final JTree tree;
  private DefaultMutableTreeNode originalRootNode;
  private BiPredicate<Object, String> userObjectMatcher;
  private JTextField filterField;

  public TreeFilterDecorator(JTree tree, BiPredicate<Object, String> userObjectMatcher) {
      this.tree = tree;
      this.originalRootNode = (DefaultMutableTreeNode) tree.getModel().getRoot();
      this.userObjectMatcher = userObjectMatcher;
  }

  public static TreeFilterDecorator decorate(JTree tree, BiPredicate<Object, String> userObjectMatcher) {
      TreeFilterDecorator tfd = new TreeFilterDecorator(tree, userObjectMatcher);
      tfd.init();
      return tfd;
  }

  public JTextField getFilterField() {
      return filterField;
  }

  private void init() {
      initFilterField();
  }

  private void initFilterField() {
      filterField = new JTextField(15);
      filterField.getDocument().addDocumentListener(new DocumentListener() {
          @Override
          public void insertUpdate(DocumentEvent e) {
              filterTree();
          }

          @Override
          public void removeUpdate(DocumentEvent e) {
              filterTree();
          }

          @Override
          public void changedUpdate(DocumentEvent e) {
              filterTree();
          }
      });
  }

  private void filterTree() {
      DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
      String text = filterField.getText().trim().toLowerCase();
      if (text.equals("") && tree.getModel().getRoot() != originalRootNode) {
          model.setRoot(originalRootNode);
          JTreeUtil.setTreeExpandedState(tree, true);
      } else {
          DefaultMutableTreeNode newRootNode = matchAndBuildNode(text, originalRootNode);
          model.setRoot(newRootNode);
          JTreeUtil.setTreeExpandedState(tree, true);
      }
  }

  private DefaultMutableTreeNode matchAndBuildNode(final String text, DefaultMutableTreeNode oldNode) {
      if (!oldNode.isRoot() && userObjectMatcher.test(oldNode.getUserObject(), text)) {
          return JTreeUtil.copyNode(oldNode);
      }
      DefaultMutableTreeNode newMatchedNode = oldNode.isRoot() ? new DefaultMutableTreeNode(oldNode
              .getUserObject()) : null;
      for (DefaultMutableTreeNode childOldNode : JTreeUtil.children(oldNode)) {
          DefaultMutableTreeNode newMatchedChildNode = matchAndBuildNode(text, childOldNode);
          if (newMatchedChildNode != null) {
              if (newMatchedNode == null) {
                  newMatchedNode = new DefaultMutableTreeNode(oldNode.getUserObject());
              }
              newMatchedNode.add(newMatchedChildNode);
          }
      }
      return newMatchedNode;
  }
}

Tree Renderer

public class TradingProjectTreeRenderer extends DefaultTreeCellRenderer {
  private static final String SPAN_FORMAT = "<span style='color:%s;'>%s</span>";
  private Supplier<String> filterTextSupplier;

  public TradingProjectTreeRenderer(Supplier<String> filterTextSupplier) {
      this.filterTextSupplier = filterTextSupplier;
  }

  @Override
  public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
                                                boolean leaf, int row, boolean hasFocus) {
      super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
      DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
      Object userObject = node.getUserObject();
      if (userObject instanceof ProjectParticipant) {
          ProjectParticipant pp = (ProjectParticipant) userObject;
          String text = String.format(SPAN_FORMAT, "rgb(0, 0, 150)",
                  renderFilterMatch(node, pp.getName()));
          text += " [" + String.format(SPAN_FORMAT, "rgb(90, 70, 0)",
                  renderFilterMatch(node, pp.getRole())) + "]";
          this.setText("<html>" + text + "</html>");
      } else if (userObject instanceof Project) {
          Project project = (Project) userObject;
          String text = String.format(SPAN_FORMAT, "rgb(0,70,0)",
                  renderFilterMatch(node, project.getName()));
          this.setText("<html>" + text + "</html>");
      } else {
          String text = String.format(SPAN_FORMAT, "rgb(120,0,0)",
                  renderFilterMatch(node, userObject.toString()));
          this.setText("<html>" + text + "</html>");
      }
      return this;
  }

  private String renderFilterMatch(DefaultMutableTreeNode node, String text) {
      if (node.isRoot()) {
          return text;
      }
      String textToFilter = filterTextSupplier.get();
      return HtmlHighlighter.highlightText(text, textToFilter);
  }
}

JTree Util

public class JTreeUtil {
  public static void setTreeExpandedState(JTree tree, boolean expanded) {
      DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getModel().getRoot();
      setNodeExpandedState(tree, node, expanded);
  }

  public static void setNodeExpandedState(JTree tree, DefaultMutableTreeNode node, boolean expanded) {
      for (DefaultMutableTreeNode treeNode : children(node)) {
          setNodeExpandedState(tree, treeNode, expanded);
      }
      if (!expanded && node.isRoot()) {
          return;
      }
      TreePath path = new TreePath(node.getPath());
      if (expanded) {
          tree.expandPath(path);
      } else {
          tree.collapsePath(path);
      }
  }

  public static DefaultMutableTreeNode copyNode(DefaultMutableTreeNode oldNode) {
      DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(oldNode.getUserObject());
      for (DefaultMutableTreeNode oldChildNode : JTreeUtil.children(oldNode)) {
          DefaultMutableTreeNode newChildNode = new DefaultMutableTreeNode(oldChildNode.getUserObject());
          newNode.add(newChildNode);
          if (!oldChildNode.isLeaf()) {
              copyChildrenTo(oldChildNode, newChildNode);
          }
      }
      return newNode;
  }

  public static void copyChildrenTo(DefaultMutableTreeNode from, DefaultMutableTreeNode to) {
      for (DefaultMutableTreeNode oldChildNode : JTreeUtil.children(from)) {
          DefaultMutableTreeNode newChildNode = new DefaultMutableTreeNode(oldChildNode.getUserObject());
          to.add(newChildNode);
          if (!oldChildNode.isLeaf()) {
              copyChildrenTo(oldChildNode, newChildNode);
          }
      }
  }

  @SuppressWarnings("unchecked")
  public static List<DefaultMutableTreeNode> children(DefaultMutableTreeNode node) {
      return Collections.list(node.children());
  }
}

Other classes are same as our last examples.

Output

Example Project

Dependencies and Technologies Used:

  • JDK 1.8
  • Maven 3.3.9

JTree Filtering and Highlighting Example Select All Download
  • jtree-filtering
    • src
      • main
        • java
          • com
            • logicbig
              • example
              • util

See Also