The solutions (1, 2) out there for filtering a JTree's nodes weren't suitable for me, so I rolled by own FilteredTreeModel
class which wraps a JTree
's underlying TreeModel
and applies a string filter to the node names.
The tree model recurses over the tree nodes, from the root to the leaf nodes, checking if the nodes toString()
value contains the filter value:
FilteredTreeModel.java
package org.adrianwalker.filteredjtree; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; public final class FilteredTreeModel implements TreeModel { private TreeModel treeModel; private String filter; public FilteredTreeModel(final TreeModel treeModel) { this.treeModel = treeModel; this.filter = ""; } public TreeModel getTreeModel() { return treeModel; } public void setFilter(final String filter) { this.filter = filter; } private boolean recursiveMatch(final Object node, final String filter) { boolean matches = node.toString().contains(filter); int childCount = treeModel.getChildCount(node); for (int i = 0; i < childCount; i++) { Object child = treeModel.getChild(node, i); matches |= recursiveMatch(child, filter); } return matches; } @Override public Object getRoot() { return treeModel.getRoot(); } @Override public Object getChild(final Object parent, final int index) { int count = 0; int childCount = treeModel.getChildCount(parent); for (int i = 0; i < childCount; i++) { Object child = treeModel.getChild(parent, i); if (recursiveMatch(child, filter)) { if (count == index) { return child; } count++; } } return null; } @Override public int getChildCount(final Object parent) { int count = 0; int childCount = treeModel.getChildCount(parent); for (int i = 0; i < childCount; i++) { Object child = treeModel.getChild(parent, i); if (recursiveMatch(child, filter)) { count++; } } return count; } @Override public boolean isLeaf(final Object node) { return treeModel.isLeaf(node); } @Override public void valueForPathChanged(final TreePath path, final Object newValue) { treeModel.valueForPathChanged(path, newValue); } @Override public int getIndexOfChild(final Object parent, final Object childToFind) { int childCount = treeModel.getChildCount(parent); for (int i = 0; i < childCount; i++) { Object child = treeModel.getChild(parent, i); if (recursiveMatch(child, filter)) { if (childToFind.equals(child)) { return i; } } } return -1; } @Override public void addTreeModelListener(final TreeModelListener l) { treeModel.addTreeModelListener(l); } @Override public void removeTreeModelListener(final TreeModelListener l) { treeModel.removeTreeModelListener(l); } }
Example Application
JTree before and after filtering
Below is a simple example of how the FilteredTreeModel
can be used. The tree is filtered using the value of the text field as characters are typed.
FilteredJTreeExample.java
package org.adrianwalker.filteredjtree; import java.awt.Container; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; public final class FilteredJTreeExample { public static void main(final String[] args) { javax.swing.SwingUtilities.invokeLater(new Runnable() { @Override public void run() { createAndShowGUI(); } }); } private static void createAndShowGUI() { JFrame frame = new JFrame("Filtered JTree Demo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); addComponentsToPane(frame.getContentPane()); frame.pack(); frame.setVisible(true); } private static void addComponentsToPane(final Container pane) { pane.setLayout(new GridBagLayout()); JTree tree = createTree(pane); JTextField filter = createFilterField(pane); filter.getDocument().addDocumentListener(createDocumentListener(tree, filter)); } private static JTree createTree(final Container pane) { DefaultMutableTreeNode root = new DefaultMutableTreeNode("JTree"); FilteredTreeModel model = new FilteredTreeModel(new DefaultTreeModel(root)); JTree tree = new JTree(model); JScrollPane scrollPane = new JScrollPane(tree); GridBagConstraints c = new GridBagConstraints(); c.weightx = 1; c.weighty = 1; c.fill = GridBagConstraints.BOTH; c.gridx = 0; c.gridy = 1; pane.add(scrollPane, c); createTreeNodes(root); expandTree(tree); return tree; } private static JTextField createFilterField(final Container pane) { JTextField filter = new JTextField(); GridBagConstraints c = new GridBagConstraints(); c.weightx = 0; c.weighty = 0; c.fill = GridBagConstraints.HORIZONTAL; c.gridx = 0; c.gridy = 0; pane.add(filter, c); return filter; } private static DocumentListener createDocumentListener(final JTree tree, final JTextField filter) { return new DocumentListener() { @Override public void insertUpdate(final DocumentEvent e) { applyFilter(); } @Override public void removeUpdate(final DocumentEvent e) { applyFilter(); } @Override public void changedUpdate(final DocumentEvent e) { applyFilter(); } public void applyFilter() { FilteredTreeModel filteredModel = (FilteredTreeModel) tree.getModel(); filteredModel.setFilter(filter.getText()); DefaultTreeModel treeModel = (DefaultTreeModel) filteredModel.getTreeModel(); treeModel.reload(); expandTree(tree); } }; } private static void expandTree(final JTree tree) { for (int i = 0; i < tree.getRowCount(); i++) { tree.expandRow(i); } } private static void createTreeNodes(final DefaultMutableTreeNode node) { DefaultMutableTreeNode ab = new DefaultMutableTreeNode("ab"); DefaultMutableTreeNode cd = new DefaultMutableTreeNode("cd"); DefaultMutableTreeNode ef = new DefaultMutableTreeNode("ef"); DefaultMutableTreeNode gh = new DefaultMutableTreeNode("gh"); DefaultMutableTreeNode ij = new DefaultMutableTreeNode("ij"); DefaultMutableTreeNode kl = new DefaultMutableTreeNode("kl"); DefaultMutableTreeNode mn = new DefaultMutableTreeNode("mn"); DefaultMutableTreeNode op = new DefaultMutableTreeNode("op"); DefaultMutableTreeNode qr = new DefaultMutableTreeNode("qr"); DefaultMutableTreeNode st = new DefaultMutableTreeNode("st"); DefaultMutableTreeNode uv = new DefaultMutableTreeNode("uv"); DefaultMutableTreeNode wx = new DefaultMutableTreeNode("wx"); DefaultMutableTreeNode yz = new DefaultMutableTreeNode("yz"); node.add(ab); node.add(cd); ab.add(ef); ab.add(gh); cd.add(ij); cd.add(kl); ef.add(mn); ef.add(op); gh.add(qr); gh.add(st); ij.add(uv); ij.add(wx); node.add(yz); } }
Source Code
- Filtered JTree - filtered-jtree.zip
Usage
Build and install the Filtered JTree project, using 'mvn clean install
'.
Run the FilteredJTreeExample
class, and enter characters in the filter text field.