Swing Filtered Tree Just Like in Eclipse

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 :

and here is a test class:

enjoy! Comments appreciated!


Posted in Swing | Tagged , , , , , , , | 3 Comments

3 Responses to Swing Filtered Tree Just Like in Eclipse

  1. praveen says:

    very nice, very much helpful .thank u lot friend

  2. Michael Bartl says:

    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;
    }
    }
    }

  3. pratikch says:

    Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

THREE_COLUMN_PAGE