2

I have a JTree of DefaultMutableTreeNodes and I would like to filter them.

When I do a filter, I want to keep any node that matches my criteria or has children that match my criteria.

Here i included the code for your reference.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Enumeration;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.border.EmptyBorder;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeModel;

public class FilteredJTreeExample extends JFrame {

    private static final long serialVersionUID = 1L;
    private JPanel contentPane;
    private JTextField textField;
    private JTree tree;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    FilteredJTreeExample frame = new FilteredJTreeExample();
                    frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the frame.
     */
    public FilteredJTreeExample() {
        //setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 450, 300);
        contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        contentPane.setLayout(new BorderLayout(0, 0));
        setContentPane(contentPane);

        JPanel panel = new JPanel();
        contentPane.add(panel, BorderLayout.NORTH);
        GridBagLayout gbl_panel = new GridBagLayout();
        gbl_panel.columnWidths = new int[]{34, 116, 0};
        gbl_panel.rowHeights = new int[]{22, 0};
        gbl_panel.columnWeights = new double[]{0.0, 1.0, Double.MIN_VALUE};
        gbl_panel.rowWeights = new double[]{0.0, Double.MIN_VALUE};
        panel.setLayout(gbl_panel);

        JLabel lblFilter = new JLabel("Search:");
        GridBagConstraints gbc_lblFilter = new GridBagConstraints();
        gbc_lblFilter.anchor = GridBagConstraints.WEST;
        gbc_lblFilter.insets = new Insets(0, 0, 0, 5);
        gbc_lblFilter.gridx = 0;
        gbc_lblFilter.gridy = 0;
        panel.add(lblFilter, gbc_lblFilter);

        JScrollPane scrollPane = new JScrollPane();
        contentPane.add(scrollPane, BorderLayout.CENTER);

        tree = new JTree();
        tree.setEditable( true );
        tree.setShowsRootHandles( false );
        tree.setInvokesStopCellEditing(true);
        scrollPane.setViewportView(tree);

        textField = new JTextField();
        GridBagConstraints gbc_textField = new GridBagConstraints();
        gbc_textField.fill = GridBagConstraints.HORIZONTAL;
        gbc_textField.anchor = GridBagConstraints.NORTH;
        gbc_textField.gridx = 1;
        gbc_textField.gridy = 0;
        panel.add(textField, gbc_textField);
        textField.setColumns(10);
        textField.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                TreeModel model = tree.getModel();
                tree.setModel(null);
                tree.setModel(model);
            }
        });

        tree.setCellRenderer(new DefaultTreeCellRenderer() {
            private JLabel lblNull = new JLabel();

            @Override
            public Component getTreeCellRendererComponent(JTree tree, Object value,
                    boolean arg2, boolean arg3, boolean arg4, int arg5, boolean arg6) {

                Component c = super.getTreeCellRendererComponent(tree, value, arg2, arg3, arg4, arg5, arg6);

                DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
                if (matchesFilter(node)) {
                    c.setForeground(Color.BLACK);
                    return c;
                }
                else if (containsMatchingChild(node)) {
                    c.setForeground(Color.GRAY);
                    return c;
                }
                else {
                    return lblNull;
                }
            }

            private boolean matchesFilter(DefaultMutableTreeNode node) {
                return node.toString().contains(textField.getText());
            }

            private boolean containsMatchingChild(DefaultMutableTreeNode node) {
                Enumeration<DefaultMutableTreeNode> e = node.breadthFirstEnumeration();
                boolean isMatched = false;
                while (e.hasMoreElements()) {
                    DefaultMutableTreeNode nextElement = e.nextElement(); 
                    if (matchesFilter(nextElement)) {
                        isMatched = true;
                    }
                }
                return isMatched;
            }
        });
    }
}

Output:

enter image description here

When I enter the search text as "color" and click enter. It shows only JTree and Colors node.

Issues:

  1. I want to show the children of colors. i.e., blue, violet etc.,

  2. Another issue i found was after perform search operation, mouse scroll up not working whereas scroll down working fine. It can be reproduced by searching "e" and shrink the frame and do a mouse scroll down and up.

Note: I am using Ubuntu 14.04 LTS.

Any ideas?

Tamil
  • 1,193
  • 9
  • 24
  • Regarding 1., you could do `int r = 0; while (r < tree.getRowCount()) { tree.expandRow(r); r++; }` at the end of `actionPerformed`, to fully expand the (filtered) tree. I couldn't reproduce 2. ... – Marco13 Jul 14 '16 at 13:48
  • @Marco13 : I already have a expand all action which is not shown here. My problem is, i cannot match the children of parent which is matched criteria. Here i would like to show JTree > Colors > blue violet red yellow. – Tamil Jul 14 '16 at 14:18
  • @Marco13 : Regards issue-2: Enter "e" (do a enter button) and shrink the frame upto only 3 records displayed and do a scroll down which you can see another 3 records. But you scroll up, the previous records not shown. This will happen on mouse scroll (next to left click button). If we drag the scroll at right hand side, its working fine. – Tamil Jul 14 '16 at 14:22
  • Regarding the question about scrolling: You should check whether this also happens for a tree that is **not** filtered. If it does, then consider writing it as an own question, with a [MCVE] and details about the OS and Java version. I could not reproduce it as you described (Win7, Java 1.8.XX) – Marco13 Jul 14 '16 at 14:57
  • @Marco13 : Scrolling issue happens only after applying filtered action. – Tamil Jul 15 '16 at 05:23
  • 1
    A side note: Filtering a `JTree` is usually **really** difficult. In fact, it surprised me that your approach seems to work at all, and it may just be a "side effect" of the rendering component having a size of (0,0) - which may also be the reason for the line that still extends from the root node to the "invisible" nodes - and the details of the behavior may thus also depend on the L&F. I think that a proper *view*-based filtering is nearly impossible. (For example, with your current solution, you can still navigate through the **invisible** keys using the keyboard!) – Marco13 Jul 15 '16 at 09:03

2 Answers2

2

You will also need a method that includes a node when one of its parents (ancestors) contains the desired string. For example,

private boolean containsMatchingParent(DefaultMutableTreeNode node) 
{
    DefaultMutableTreeNode current = node;
    while (current != null)
    {
        if (matchesFilter(current))
        {
            return true;
        }
        current = (DefaultMutableTreeNode) current.getParent();
    }
    return false;
}

(To be used in the same way as containsMatchingChild).

Marco13
  • 53,703
  • 9
  • 80
  • 159
2

Change matchesFilter() to :

private boolean matchesFilter(DefaultMutableTreeNode node) 
{
    TreeNode parent = node;
    while ( parent != null )
    {
        if ( parent.toString().contains(textField.getText()))
        {
            return true;
        }
        parent = parent.getParent();

    }

    return false;
}

That will return the colors so you will be able to expand colors node.

SomeDude
  • 13,876
  • 5
  • 21
  • 44