Close

Java Swing - JTree Filtering and Highlighting Example

[Last Updated: Jul 4, 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
                • TreeFilterDecorator.java
                • util

    See Also