Eclipse has a pretty nice widget. It’s basically a tree with a text field above it. As you type in the text field, the tree gets filtered.
Start typing some text :
…
…
As you can see, the tree shrinks and only nodes that match the search term are displayed. Also the parent nodes of any matched nodes are displayed. The matched nodes are highlighted in bold.
Here is my filtered tree in Swing :
..
…
As you can see it behaves almost the same as the filtered tree in Eclipse. The string matches are matched on the toString() of the UserObject in the tree nodes. The Eclipse filtered tree is a bit different in that it matches on strings inside the panel on the right. Mine does not do that however it would not be too hard to extend that to make the matches occur on some arbitrary object containing multiple strings. I am thinking that the super class of all panels that are displayed on the right could have some method like getPanelStrings().
Here is my code :
package filtertree;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Enumeration;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
/**
* Tree widget which allows the tree to be filtered on keystroke time. Only nodes who's
* toString matches the search field will remain in the tree or its parents.
*
* Copyright (c) Oliver.Watkins
*/
public class FilteredTree extends JPanel{
private String filteredText = "";
private DefaultTreeModel originalTreeModel;
private JScrollPane scrollpane = new JScrollPane();
private JTree tree = new JTree();
private DefaultMutableTreeNode originalRoot;
public FilteredTree(DefaultMutableTreeNode originalRoot){
this.originalRoot = originalRoot;
guiLayout();
}
private void guiLayout() {
tree.setCellRenderer(new Renderer());
final JTextField field = new JTextField(10);
field.addKeyListener(new KeyAdapter() {
public void keyTyped(KeyEvent ke) {
super.keyTyped(ke);
filterTree(field.getText() + ke.getKeyChar());
}
});
originalTreeModel = new DefaultTreeModel(originalRoot);
tree.setModel(originalTreeModel);
this.setLayout(new BorderLayout());
add(field, BorderLayout.NORTH);
add(scrollpane = new JScrollPane(tree), BorderLayout.CENTER);
originalRoot = (DefaultMutableTreeNode) originalTreeModel.getRoot();
}
/**
*
* @param text
*/
private void filterTree(String text) {
filteredText = text;
//get a copy
DefaultMutableTreeNode filteredRoot = copyNode(originalRoot);
if (text.trim().toString().equals("")) {
//reset with the original root
originalTreeModel.setRoot(originalRoot);
tree.setModel(originalTreeModel);
tree.updateUI();
scrollpane.getViewport().setView(tree);
for (int i = 0; i < tree.getRowCount(); i++) {
tree.expandRow(i);
}
return;
} else {
TreeNodeBuilder b = new TreeNodeBuilder(text);
filteredRoot = b.prune((DefaultMutableTreeNode) filteredRoot.getRoot());
originalTreeModel.setRoot(filteredRoot);
tree.setModel(originalTreeModel);
tree.updateUI();
scrollpane.getViewport().setView(tree);
}
for (int i = 0; i < tree.getRowCount(); i++) {
tree.expandRow(i);
}
}
/**
* Clone/Copy a tree node. TreeNodes in Swing don't support deep cloning.
*
* @param orig to be cloned
* @return cloned copy
*/
private DefaultMutableTreeNode copyNode(DefaultMutableTreeNode orig) {
DefaultMutableTreeNode newOne = new DefaultMutableTreeNode();
newOne.setUserObject(orig.getUserObject());
Enumeration enm = orig.children();
while(enm.hasMoreElements()){
DefaultMutableTreeNode child = enm.nextElement();
newOne.add(copyNode(child));
}
return newOne;
}
/**
* Renders bold any tree nodes who's toString() value starts with the filtered text
* we are filtering on.
*
* @author Oliver.Watkins
*/
public class Renderer extends DefaultTreeCellRenderer{
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded,
boolean leaf, int row, boolean hasfocus) {
Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasfocus);
if (c instanceof JLabel){
if (!filteredText.equals("") && value.toString().startsWith(filteredText)){
Font f = c.getFont();
f = new Font("Dialog", Font.BOLD, f.getSize());
c.setFont(f);
}else{
Font f = c.getFont();
f = new Font("Dialog", Font.PLAIN , f.getSize());
c.setFont(f);
}
}
return c;
}
}
public JTree getTree() {
return tree;
}
/**
* Class that prunes off all leaves which do not match the search string.
*
* @author Oliver.Watkins
*/
public class TreeNodeBuilder {
private String textToMatch;
public TreeNodeBuilder(String textToMatch) {
this.textToMatch = textToMatch;
}
public DefaultMutableTreeNode prune(DefaultMutableTreeNode root) {
boolean badLeaves = true;
//keep looping through until tree contains only leaves that match
while (badLeaves){
badLeaves = removeBadLeaves(root);
}
return root;
}
/**
*
* @param root
* @return boolean bad leaves were returned
*/
private boolean removeBadLeaves(DefaultMutableTreeNode root) {
//no bad leaves yet
boolean badLeaves = false;
//reference first leaf
DefaultMutableTreeNode leaf = root.getFirstLeaf();
//if leaf is root then its the only node
if (leaf.isRoot())
return false;
int leafCount = root.getLeafCount(); //this get method changes if in for loop so have to define outside of it
for (int i = 0; i < leafCount; i++) {
DefaultMutableTreeNode nextLeaf = leaf.getNextLeaf();
//if it does not start with the text then snip it off its parent
if (!leaf.getUserObject().toString().startsWith(textToMatch)) {
DefaultMutableTreeNode parent = (DefaultMutableTreeNode) leaf.getParent();
if (parent != null)
parent.remove(leaf);
badLeaves = true;
}
leaf = nextLeaf;
}
return badLeaves;
}
}
}
and here is a test class:
package filtertree;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
public class Main extends JFrame {
public static void main(String[] args) {
Main m = new Main();
}
public Main() {
DefaultMutableTreeNode n = new DefaultMutableTreeNode("animals");
{
DefaultMutableTreeNode n1 = new DefaultMutableTreeNode("bear");n.add(n1);
}
{
DefaultMutableTreeNode n1 = new DefaultMutableTreeNode("cat");n.add(n1);
}
{
DefaultMutableTreeNode n1 = new DefaultMutableTreeNode("boor");n.add(n1);
}
{
DefaultMutableTreeNode n1 = new DefaultMutableTreeNode("dog");n.add(n1);
{
DefaultMutableTreeNode n2 = new DefaultMutableTreeNode("billy");n1.add(n2);
}
{
DefaultMutableTreeNode n2 = new DefaultMutableTreeNode("cassie");n1.add(n2);
}
}
{
DefaultMutableTreeNode n1 = new DefaultMutableTreeNode("bat");n.add(n1);
}
{
DefaultMutableTreeNode n1 = new DefaultMutableTreeNode("crow");n.add(n1);
}
{
DefaultMutableTreeNode n1 = new DefaultMutableTreeNode("cow");n.add(n1);
{
DefaultMutableTreeNode n2 = new DefaultMutableTreeNode("carp");n1.add(n2);
}
{
DefaultMutableTreeNode n2 = new DefaultMutableTreeNode("boa constrictor");n1.add(n2);
{
DefaultMutableTreeNode n3 = new DefaultMutableTreeNode("cockatoo");n2.add(n3);
}
{
DefaultMutableTreeNode n3 = new DefaultMutableTreeNode("dragon");n2.add(n3);
}
{
DefaultMutableTreeNode n3 = new DefaultMutableTreeNode("adder");n2.add(n3);
}
}
{
DefaultMutableTreeNode n2 = new DefaultMutableTreeNode("alligator");n1.add(n2);
}
{
DefaultMutableTreeNode n2 = new DefaultMutableTreeNode("snake");n1.add(n2);
}
{
DefaultMutableTreeNode n2 = new DefaultMutableTreeNode("spider");n1.add(n2);
}
{
DefaultMutableTreeNode n2 = new DefaultMutableTreeNode("salamander");n1.add(n2);
{
DefaultMutableTreeNode n3 = new DefaultMutableTreeNode("mouse");n2.add(new DefaultMutableTreeNode("lala"));
}
{
DefaultMutableTreeNode n3 = new DefaultMutableTreeNode("shark");n2.add(n3);
}
{
DefaultMutableTreeNode n3 = new DefaultMutableTreeNode("llama");n2.add(n3);
}
}
{
DefaultMutableTreeNode n2 = new DefaultMutableTreeNode("ant");
n1.add(n2);
}
}
FilteredTree ftree = new FilteredTree(n);
getContentPane().add(ftree);
final JPanel rightPanel = new JPanel();
rightPanel.setMinimumSize(new Dimension(300, 300));
rightPanel.setPreferredSize(new Dimension(300, 300));
getContentPane().add(rightPanel, BorderLayout.EAST);
pack();
setVisible(true);
final JTree tree = ftree.getTree();
MouseListener ml = new MouseAdapter() {
public void mousePressed(MouseEvent e) {
int selRow = tree.getRowForLocation(e.getX(), e.getY());
TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
if (selRow != -1) {
if (e.getClickCount() == 1) {
rightPanel.removeAll();
rightPanel.add(new JLabel(""
+ selPath.getLastPathComponent()));
rightPanel.updateUI();
System.out.println("" + selPath.getLastPathComponent());
}
}
}
};
tree.addMouseListener(ml);
}
}
enjoy! Comments appreciated!










very nice, very much helpful .thank u lot friend
The following improves upon your code
It fixes a bug (keylistener is always late and doesn’t work with DEL key) and searches within the node and not only at the start.
—
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Font;
import java.util.Enumeration;
import javax.swing.JLabel;
import javax.swing.JPanel;
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.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
/**
* Tree widget which allows the tree to be filtered on keystroke time. Only nodes who's
* toString matches the search field will remain in the tree or its parents.
*
* Copyright (c) Oliver.Watkins
*/
public class FilteredTree extends JPanel
{
private static final long serialVersionUID = 1L;
private String filteredText = "";
private DefaultTreeModel originalTreeModel;
private JScrollPane scrollpane = new JScrollPane();
private final JTree tree = new JTree();
private DefaultMutableTreeNode originalRoot;
public FilteredTree( final DefaultMutableTreeNode originalRoot )
{
this.originalRoot = originalRoot;
guiLayout();
}
private void guiLayout()
{
this.tree.setCellRenderer( new Renderer() );
final JTextField field = new JTextField( 10 );
field.getDocument().addDocumentListener( new DocumentListener()
{
@Override
public void removeUpdate( final DocumentEvent e )
{
filterTree( field.getText() );
}
@Override
public void insertUpdate( final DocumentEvent e )
{
filterTree( field.getText() );
}
@Override
public void changedUpdate( final DocumentEvent e )
{
filterTree( field.getText() );
}
} );
this.originalTreeModel = new DefaultTreeModel( this.originalRoot );
this.tree.setModel( this.originalTreeModel );
setLayout( new BorderLayout() );
add( field, BorderLayout.NORTH );
add( this.scrollpane = new JScrollPane( this.tree ), BorderLayout.CENTER );
this.originalRoot = ( DefaultMutableTreeNode ) this.originalTreeModel.getRoot();
}
/**
*
* @param text
*/
private void filterTree( final String text )
{
this.filteredText = text;
//get a copy
DefaultMutableTreeNode filteredRoot = copyNode( this.originalRoot );
if ( text.trim().toString().equals( "" ) )
{
//reset with the original root
this.originalTreeModel.setRoot( this.originalRoot );
this.tree.setModel( this.originalTreeModel );
this.tree.updateUI();
this.scrollpane.getViewport().setView( this.tree );
for ( int i = 0; i < this.tree.getRowCount(); i++ )
{
this.tree.expandRow( i );
}
return;
}
else
{
final TreeNodeBuilder b = new TreeNodeBuilder( text );
filteredRoot = b.prune( ( DefaultMutableTreeNode ) filteredRoot.getRoot() );
this.originalTreeModel.setRoot( filteredRoot );
this.tree.setModel( this.originalTreeModel );
this.tree.updateUI();
this.scrollpane.getViewport().setView( this.tree );
}
for ( int i = 0; i 0 )
{
Font f = c.getFont();
f = new Font( "Dialog", Font.BOLD, f.getSize() );
c.setFont( f );
}
else
{
Font f = c.getFont();
f = new Font( "Dialog", Font.PLAIN, f.getSize() );
c.setFont( f );
}
}
return c;
}
}
public JTree getTree()
{
return this.tree;
}
/**
* Class that prunes off all leaves which do not match the search string.
*
* @author Oliver.Watkins
*/
public class TreeNodeBuilder
{
private final String textToMatch;
public TreeNodeBuilder( final String textToMatch )
{
this.textToMatch = textToMatch;
}
public DefaultMutableTreeNode prune( final DefaultMutableTreeNode root )
{
boolean badLeaves = true;
//keep looping through until tree contains only leaves that match
while ( badLeaves )
{
badLeaves = removeBadLeaves( root );
}
return root;
}
/**
*
* @param root
* @return boolean bad leaves were returned
*/
private boolean removeBadLeaves( final DefaultMutableTreeNode root )
{
//no bad leaves yet
boolean badLeaves = false;
//reference first leaf
DefaultMutableTreeNode leaf = root.getFirstLeaf();
//if leaf is root then its the only node
if ( leaf.isRoot() )
{
return false;
}
final int leafCount = root.getLeafCount(); //this get method changes if in for loop so have to define outside of it
for ( int i = 0; i < leafCount; i++ )
{
final DefaultMutableTreeNode nextLeaf = leaf.getNextLeaf();
//if it does not start with the text then snip it off its parent
if ( leaf.getUserObject().toString().indexOf( this.textToMatch ) == -1 )
{
final DefaultMutableTreeNode parent =
( DefaultMutableTreeNode ) leaf.getParent();
if ( parent != null )
{
parent.remove( leaf );
}
badLeaves = true;
}
leaf = nextLeaf;
}
return badLeaves;
}
}
}