Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filtering on a JTree [closed]

Problem

Applying filtering on a JTree to avoid certain nodes/leaves to show up in the rendered version of the JTree. Ideally I am looking for a solution which allows to have a dynamic filter, but I would already be glad if I can get a static filter to work.

To make it a bit easier, let us suppose the JTree only supports rendering, and not editing. Moving, adding, removing of nodes should be possible.

An example is a search field above a JTree, and on typing the JTree would only show the subtree with matches.

A few restrictions: it is to be used in a project which has access to JDK and SwingX. I would like to avoid to include other third party libs.

I already thought of a few possible solutions, but neither of those seemed ideal

Approaches

Model based filtering

  • decorate the TreeModel to filter out some of the values. A quick-and-dirt version is easy to write. Filter out nodes, and on every change of the filter or the delegate TreeModel the decorator can fire an event that the whole tree has changes (treeStructureChanged with the root node as node). Combine this with listeners which restore the selection state and the expansion state of the JTree and you get a version which works more or less, but the events originating from the TreeModel are messed up. This is more or less the approach used in this question
  • decorate the TreeModel but try fire the correct events. I did not (yet) managed to come up with a working version of this. It seems to require a copy of the delegate TreeModel in order to be able to fire an event with the correct child indices when nodes are removed from the delegate model. I think with some more time I could get this to work, but it just feels wrong (filtering feels like something the view should do, and not the model)
  • decorate whatever data structure was used to create the initial TreeModel. However, this is completely non-reusable, and probably as hard as to write a decorator for a TreeModel

View based filtering

This seems like the way to go. Filtering should not affect the model but only the view.

  • I took a look at RowFilter class. Although the javadoc seems to suggest you can use it in combination with a JTree:

    when associated with a JTree, an entry corresponds to a node.

    I could not find any link between RowFilter (or RowSorter) and the JTree class. The standard implementations of RowFilter and the Swing tutorials seems to suggest that RowFilter can only be used directly with a JTable (see JTable#setRowSorter). No similar methods are available on a JTree

  • I also looked at the JXTree javadoc. It has a ComponentAdapter available and the javadoc of ComponentAdapter indicates a RowFilter could interact with the target component, but I fail to see how I make the link between the RowFilter and the JTree
  • I did not yet look at how a JTable handles the filtering with RowFilters, and perhaps the same can be done on a modified version of a JTree.

So in short: I have no clue on what's the best approach to solve this

Note: this question is a possible duplicate of this question, but that question is still unanswered, the question rather short and the answers seems incomplete, so I thought to post a new question. If this is not done (the FAQ did not provide a clear answer on this) I will update that 3year old question

like image 247
Robin Avatar asked Feb 10 '12 20:02

Robin


2 Answers

Take a look at this implementation: http://www.java2s.com/Code/Java/Swing-Components/InvisibleNodeTreeExample.htm

It creates subclasses of DefaultMutableNode adding a "isVisible" property rather then actually removing/adding nodes from the TreeModel. Pretty sweet I think, and it solved my filtering problem neatly.

like image 179
Martin Wickman Avatar answered Oct 04 '22 17:10

Martin Wickman


View-based filtering is definitely the way to go. You can use something like the example I've coded below. Another common practice when filtering trees is to switch to a list view when filtering a tree, since the list won't require you to show hidden nodes whose descendants need to be shown.

This is absolutely horrendous code (I tried to cut every corner possible in whipping it up just now), but it should be enough to get you started. Just type your query in the search box and press Enter, and it'll filter the JTree's default model. (FYI, the first 90 lines are just generated boilerplate and layout code.)

package com.example.tree;  import java.awt.BorderLayout;  public class FilteredJTreeExample extends JFrame {      private JPanel contentPane;     private JTextField textField;      /**      * 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("Filter:");         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);         final JTree tree = new JTree();         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();                 while (e.hasMoreElements()) {                     if (matchesFilter(e.nextElement())) {                         return true;                     }                 }                  return false;             }         });     }  } 

When you implement it for real, you'll probably want to create your own TreeNode and TreeCellRenderer implementations, use a less stupid method for triggering an update, and follow MVC separation. Note that the "hidden" nodes are still rendered, but they're so small that you can't see them. If you use the arrow keys to navigate the tree, though, you'll notice that they're still there. If you just need something that works, this might be good enough.

Filtered tree (windows)

Edit

Here are screenshots of the unfiltered and filtered version of the tree in Mac OS, showing that the whitespace is visible in Mac OS:

Unfiltered treeFiltered tree

like image 36
rob Avatar answered Oct 04 '22 17:10

rob