sirve simple seleccionar que para nodo implementing icon example ejemplo create java swing design jtree swingx

java - simple - Filtrado en un JTree



seleccionar un nodo en jtree java (10)

Problema

Aplicar el filtrado en un JTree para evitar que ciertos nodos / hojas aparezcan en la versión renderizada del JTree . Idealmente, estoy buscando una solución que permita tener un filtro dinámico, pero ya estaría contento de poder hacer funcionar un filtro estático.

Para hacerlo un poco más fácil, supongamos que JTree solo es compatible con la representación y no con la edición. Mover, agregar, quitar nodos debería ser posible.

Un ejemplo es un campo de búsqueda sobre un JTree , y al escribir JTree solo mostraría el subárbol con coincidencias.

Algunas restricciones: se utilizará en un proyecto que tenga acceso a JDK y SwingX. Me gustaría evitar incluir otras librerías de terceros.

Ya pensé en algunas posibles soluciones, pero ninguna de ellas parecía ideal

Enfoques

Filtrado basado en modelo

  • decorar TreeModel para filtrar algunos de los valores. Una versión rápida y sucia es fácil de escribir. Filtra los nodos, y en cada cambio del filtro o del delegado TreeModel el decorador puede treeStructureChanged un evento de que todo el árbol tiene cambios ( treeStructureChanged con el nodo raíz como nodo). Combine esto con los oyentes que restauran el estado de selección y el estado de expansión de JTree y obtiene una versión que funciona más o menos, pero los eventos que se originan en el TreeModel están en mal estado. Este es más o menos el enfoque utilizado en esta pregunta
  • decora TreeModel pero intenta disparar los eventos correctos. No (todavía) logré encontrar una versión funcional de esto. Parece que se requiere una copia del TreeModel delegado para poder activar un evento con los índices secundarios correctos cuando los nodos se eliminan del modelo de delegado. Creo que con algo más de tiempo podría hacer que esto funcione, pero se siente mal (el filtrado parece algo que la vista debería hacer, y no el modelo)
  • decorar cualquier estructura de datos utilizada para crear el TreeModel inicial. Sin embargo, esto es completamente no reutilizable, y probablemente tan difícil como escribir un decorador para un TreeModel

Ver el filtrado basado

Esto parece ser el camino a seguir. El filtrado no debe afectar el modelo sino solo la vista.

  • RowFilter un vistazo a la clase RowFilter . Aunque el javadoc parece sugerir que puede usarlo en combinación con un JTree :

    cuando se asocia con un JTree, una entrada corresponde a un nodo.

    No pude encontrar ningún vínculo entre RowFilter (o RowSorter ) y la clase JTree . Las implementaciones estándar de RowFilter y los tutoriales de Swing parecen sugerir que RowFilter solo se puede usar directamente con una JTable (ver JTable#setRowSorter ). No hay métodos similares disponibles en un JTree

  • También miré el JXTree javadoc. Tiene un ComponentAdapter disponible y el javadoc de ComponentAdapter indica que RowFilter podría interactuar con el componente de destino, pero no veo cómo hago el enlace entre el RowFilter y el JTree
  • Todavía no me RowFilter en cómo maneja JTable el filtrado con RowFilter s, y quizás lo mismo se puede hacer en una versión modificada de un JTree .

En resumen: no tengo idea de cuál es el mejor enfoque para resolver este problema

Nota: esta pregunta es un posible duplicado de esta pregunta , pero esa pregunta aún no ha sido respondida, la pregunta es bastante breve y las respuestas parecen incompletas, así que pensé en publicar una nueva pregunta. Si esto no se hace (las preguntas frecuentes no dieron una respuesta clara al respecto) voy a actualizar esa pregunta de 3 años.


Aquí hay una posible solución que usa solo componentes Swing estándar:

No he usado esto todavía, pero me gusta su implementación mucho más que otras soluciones rápidas y sucias que encontré en línea.



El filtrado basado en la vista es definitivamente el camino a seguir. Puedes usar algo como el ejemplo que he codificado a continuación. Otra práctica común al filtrar árboles es cambiar a una vista de lista cuando filtre un árbol, ya que la lista no requerirá que muestre los nodos ocultos cuyos descendientes deben mostrarse.

Este es un código absolutamente horrendo (traté de cortar todos los rincones posibles para batirlo justo ahora), pero debería ser suficiente para comenzar. Simplemente escriba su consulta en el cuadro de búsqueda y presione Entrar, y se filtrará el modelo predeterminado de JTree. (Para su información, las primeras 90 líneas son solo un código repetitivo y un código de diseño).

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

Cuando lo implemente de manera real, probablemente quiera crear sus propias implementaciones TreeNode y TreeCellRenderer, usar un método menos estúpido para activar una actualización y seguir la separación de MVC. Tenga en cuenta que los nodos "ocultos" aún se representan, pero son tan pequeños que no puede verlos. Sin embargo, si usa las teclas de flecha para navegar por el árbol, notará que todavía están allí. Si solo necesitas algo que funcione, esto podría ser suficiente.

Editar

Aquí hay capturas de pantalla de la versión sin filtro y filtrada del árbol en Mac OS, que muestra que el espacio en blanco es visible en Mac OS:


Finalmente logré sacar algo que los trajes mis necesidades perfectamente, y pensé que compartiría en caso de que alguien más pueda usarlo.

He intentado mostrar dos JTrees uno al lado del otro, uno que contiene una lista filtrada del otro.

Básicamente, he creado dos TreeModels y uso el mismo nodo raíz para ambos. Esto parece funcionar bien hasta el momento, siempre y cuando me asegure de anular CADA método que se llama desde DefaultTreeModel en mi código, como nodeChanged (nodo TreeNode) o de lo contrario habrá dolor.

El problema viene del hecho de que la única vez que se consultan los nodos para obtener información, como childcount, es cuando se llaman a los métodos de tipo de estructura en DefaultTreeModel. Aparte de eso, todas las llamadas para información de estructura de árbol pueden ser interceptadas y filtradas como se muestra a continuación.

Esto puede volverse desagradable, ya que tienes que asegurarte de que excaves cada vez que se consultan los nodos si usas DefaultTreeModel como la base como lo hice. Este problema podría no estar allí si implementa TreeModel directamente en lugar de ser flojo como yo. La fuente de NodesChanged vino directamente de la fuente JDK.

Tuve la suerte de que lo que quería significaba que siempre había un camino de regreso al nodo raíz de cada elemento en la lista filtrada.

Esto era todo lo que tenía que hacer, aunque estuve todo el día probando inventos salvajes y caóticos, como recrear una copia superficial del árbol, etc., ¡sin mencionar leer muchos en Stack!

public class FilteredSceneModel extends DefaultTreeModel { public static boolean isSceneItem(Object child) { return !(child instanceof DataItem); } public FilteredSceneModel(RootSceneNode root, SelectionModel sm) { super(root, sm); } private boolean isSceneFolder(Object node) { return node instanceof RootSceneNode || node instanceof Floor; } @Override public AbstractSceneItem getChild(Object parent, int index) { AbstractSceneItem asi = (AbstractSceneItem) parent; if (isSceneItem(parent)) { int dex = 0; for (AbstractSceneItem child : asi.children) { if (isSceneItem(child)) { if (dex == index) { return child; } dex++; } } } System.out.println("illegal state for: " + parent + " at index: " + index); return asi.getChildAt(index); } @Override public int getChildCount(Object parent) { if (isSceneItem(parent)) { AbstractSceneItem asi = (AbstractSceneItem) parent; int count = 0; for (AbstractSceneItem child : asi.children) { if (isSceneItem(child)) { count++; } } return count; } return -1; } @Override public int getIndexOfChild(Object parent, Object childItem) { if (isSceneItem(parent)) { AbstractSceneItem asi = (AbstractSceneItem) parent; int count = 0; for (AbstractSceneItem child : asi.children) { if (isSceneItem(child)) { if (child == childItem) { return count; } count++; } } } return -1; } @Override public boolean isLeaf(Object node) { if (isSceneItem(node)) { if (isSceneFolder(node)) { return false; } } return true; } @Override public void activeFloorChanged(Floor floor) { for (AbstractSceneItem asi : floor) { if (isSceneItem(asi)) { nodeChanged(asi); } } } @Override protected void renamed(AbstractSceneItem asi) { if (isSceneItem(asi)) { nodeChanged(asi); System.out.println("scene only model renamed: " + asi.fullPathToString()); } } @Override public void nodeChanged(TreeNode tn) { if (isSceneItem(tn)) { filteredNodeChanged(tn); } } @Override public void nodeStructureChanged(TreeNode tn) { if (isSceneItem(tn)) { super.nodeStructureChanged(tn); } } private void filteredNodeChanged(TreeNode node) { if (listenerList != null && node != null) { TreeNode parent = node.getParent(); if (parent != null) { int anIndex = getIndexOfChild(parent, node); if (anIndex != -1) { int[] cIndexs = new int[1]; cIndexs[0] = anIndex; nodesChanged(parent, cIndexs); } } else if (node == getRoot()) { nodesChanged(node, null); } } } @Override public void nodesChanged(TreeNode node, int[] childIndices) { if (node != null) { if (childIndices != null) { int cCount = childIndices.length; if (cCount > 0) { Object[] cChildren = new Object[cCount]; for (int counter = 0; counter < cCount; counter++) { cChildren[counter] = getChild(node, childIndices[counter]); } fireTreeNodesChanged(this, getPathToRoot(node), childIndices, cChildren); } } else if (node == getRoot()) { fireTreeNodesChanged(this, getPathToRoot(node), null, null); } } } }


He estado trabajando en un workarround para filtrar una JXTreeTable extendida. He seguido el enfoque de dos modelos por simplicidad.

El modelo filtrado

public abstract class TellapicModelFilter extends DefaultTreeTableModel { protected Map<AbstractMutableTreeTableNode, AbstractMutableTreeTableNode> family; protected Map<AbstractMutableTreeTableNode, AbstractMutableTreeTableNode> filter; protected MyTreeTable treeTable; private boolean withChildren; private boolean withParents; /** * * @param model */ public TellapicModelFilter(MyTreeTable treeTable) { this(treeTable, false, false); } /** * * @param treeTable * @param wp * @param wc */ public TellapicModelFilter(MyTreeTable treeTable, boolean wp, boolean wc) { super(new DefaultMutableTreeTableNode("filteredRoot")); this.treeTable = treeTable; setIncludeChildren(wc); setIncludeParents(wp); } /** * */ public void filter() { filter = new HashMap<AbstractMutableTreeTableNode, AbstractMutableTreeTableNode>(); family = new HashMap<AbstractMutableTreeTableNode, AbstractMutableTreeTableNode>(); AbstractMutableTreeTableNode filteredRoot = (AbstractMutableTreeTableNode) getRoot(); AbstractMutableTreeTableNode root = (AbstractMutableTreeTableNode) treeTable.getTreeTableModel().getRoot(); filterChildren(root, filteredRoot); for(AbstractMutableTreeTableNode node : family.keySet()) node.setParent(null); for(AbstractMutableTreeTableNode node : filter.keySet()) node.setParent(filter.get(node)); } /** * * @param node * @param filteredNode */ private void filterChildren(AbstractMutableTreeTableNode node, AbstractMutableTreeTableNode filteredNode) { int count = node.getChildCount(); for(int i = 0; i < count; i++) { AbstractMutableTreeTableNode child = (AbstractMutableTreeTableNode) node.getChildAt(i); family.put(child, node); if (shouldBeFiltered(child)) { filter.put(child, filteredNode); if (includeChildren()) filterChildren(child, child); } else { filterChildren(child, filteredNode); } } } /** * */ public void restoreFamily() { for(AbstractMutableTreeTableNode child : family.keySet()) { AbstractMutableTreeTableNode parent = family.get(child); child.setParent(parent); } } /** * * @param node * @return */ public abstract boolean shouldBeFiltered(AbstractMutableTreeTableNode node); /** * Determines if parents will be included in the filtered result. This DOES NOT means that parent will be filtered * with the filter criteria. Instead, if a node {@code}shouldBeFiltered{@code} no matter what the parent node is, * include it in the filter result. The use of this feature is to provide contextual data about the filtered node, * in the terms of: "where was this node that belongs to?" * * @return True is parents should be included anyhow. */ public boolean includeParents() { return withParents; } /** * Determines if children should be filtered. When a node {@code}shouldBeFiltered{@code} you can stop the filtering * process in that node by setting: {@code}setIncludeChildren(false){@code}. In other words, if you want to filter * all the tree, {@code}includeChildren{@code} should return true. * * By letting this method return {@code}false{@code} all children of the node filtered will be automatically added * to the resulting filter. That is, children aren''t filtered with the filter criteria and they will be shown with * their parent in the filter result. * * @return True if you want to filter all the tree. */ public boolean includeChildren() { return withChildren; } /** * * @param include */ public void setIncludeParents(boolean include) { withParents = include; } /** * * @param include */ public void setIncludeChildren(boolean include) { withChildren = include; }

Básicamente, la idea era conectar / desconectar nodos del modelo original a la raíz del modelo filtrado manteniendo un registro de los nodos familiares actuales.

El modelo filtrado tendrá un mapeo entre niños y padres, con el método apropiado para restaurar esta familia. El método''restoreFamily'' volverá a conectar esos niños desaparecidos.

El modelo filtrado hará la mayor parte del trabajo en su método filter() dejando el método abstract shouldBeFiltered(node) a las implementaciones:

Se debe tener en cuenta que no es necesario desconectar TODOS los niños de la familia antes de conectar los filtrados a la raíz filtrada. Si el rendimiento es clave, ese comportamiento podría analizarse más a fondo.

Extendiendo JXTreeTable

Por último, pero lo más importante, existe la necesidad de ampliar la tabla de árbol subyacente implementando un método y anulando otro:

@Override public void setTreeTableModel(TreeTableModel treeModel) { if (!(treeModel instanceof TellapicModelFilter)) model = treeModel; super.setTreeTableModel(treeModel); } public void setModelFilter(TellapicModelFilter mf) { if (modelFilter != null) { modelFilter.restoreFamily(); setTreeTableModel(getUnfilteredModel()); } // Is this necessary? if (mf == null) { setTreeTableModel(getUnfilteredModel()); } else { modelFilter = mf; modelFilter.filter(); setTreeTableModel(modelFilter); } }

Un ejemplo completo y funcional con una treetable se puede encontrar en este link . Incluye un Main.java con un árbol listo para construir. La GUI prueba tiene un botón que agrega nodos en el nodo seleccionado (si corresponde) y en la parte superior del marco un campo de texto que se filtra mientras se escribe.


Pregunta anterior, me encontré con ... para todos aquellos que quieren una solución rápida y fácil de

SÓLO FILTRANDO LA VISIÓN:

Sé que no es tan limpio como Filtrar el modelo y viene con posibles backdraws, pero si solo quieres una solución rápida para una aplicación pequeña:

Extienda DefaultTableCellRenderer, anule getTreeCellRendererComponent - invoque super.getTreeCellRendererComponent (...) y luego configure la altura preferida en ZERO para todos los nodos que desee ocultar. Al construir su JTree, asegúrese de establecer setRowHeight (0); - por lo que respetará la altura preferida de cada fila ...

voila - ¡todas las filas filtradas son invisibles!

EJEMPLO DE TRABAJO COMPLETO

import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JTree; import javax.swing.UIManager; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; public class JTreeExample { public static void main( final String[] args ) throws Exception { UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() ); // The only correct way to create a SWING Frame... EventQueue.invokeAndWait( new Runnable() { @Override public void run() { swingMain(); } } ); } protected static void swingMain() { final JFrame f = new JFrame( "JTree Test" ); f.setLocationByPlatform( true ); f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); final int items = 5; final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode( "JTree", true ); final DefaultTreeModel myModel = new DefaultTreeModel( rootNode ); final Box buttonBox = new Box( BoxLayout.X_AXIS ); for( int i = 0; i < items; i++ ) { final String name = "Node " + i; final DefaultMutableTreeNode newChild = new DefaultMutableTreeNode( name ); rootNode.add( newChild ); final JButton b = new JButton( "Show/Hide " + i ); buttonBox.add( b ); b.addActionListener( new ActionListener() { @Override public void actionPerformed( final ActionEvent e ) { // If the node has a Text, set it to null, otherwise reset it newChild.setUserObject( newChild.getUserObject() == null ? name : null ); myModel.nodeStructureChanged( newChild.getParent() ); } } ); } final JTree tree = new JTree( myModel ); tree.setRowHeight( 0 ); tree.setCellRenderer( new JTreeExample.TreeRenderer() ); f.add( tree, BorderLayout.CENTER ); f.add( buttonBox, BorderLayout.SOUTH ); f.setSize( 600, 500 ); f.setVisible( true ); } public static class TreeRenderer extends DefaultTreeCellRenderer { @Override public Component getTreeCellRendererComponent( final JTree tree, final Object value, final boolean selected, final boolean expanded, final boolean leaf, final int row, final boolean hasFocus ) { // Invoke default Implementation, setting all values of this super.getTreeCellRendererComponent( tree, value, selected, expanded, leaf, row, hasFocus ); if( !isNodeVisible( (DefaultMutableTreeNode)value ) ) { setPreferredSize( new Dimension( 0, 0 ) ); } else { setPreferredSize( new Dimension( 200, 15 ) ); } return this; } } public static boolean isNodeVisible( final DefaultMutableTreeNode value ) { // In this example all Nodes without a UserObject are invisible return value.getUserObject() != null; } }


Principio que utilicé: llenando ArrayList desde DB, luego poblando el árbol. Cuando tengo que filtrar nodos de árbol, simplemente itero a través de ArrayList, elimino todos los nodos que no coinciden con los criterios, y luego reconstruyo el árbol con ArrayList modificado ...


Tengo una sugerencia para esto que puede ser de interés. Lo puse en práctica en mi propia aplicación y parece funcionar bien ... a continuación se muestra una implementación mínima absoluta de SSCCE que demuestra "insertNodeInto".

El diseño central es múltiples acoplamientos de J Tree TreeModel, que se mantienen sincronizados entre sí ... excepto, obviamente, que se aplica un patrón de filtrado para que ciertos nodos (y sus subárboles) no estén presentes en un modelo. Mientras tanto, cada nodo en el árbol ON tiene un nodo "homólogo" en el árbol DESACTIVADO (aunque el inverso no es necesariamente cierto).

El diseño más simple por lo tanto implica 2 de tales acoplamientos: uno con el filtro "OFF" y el otro con el filtro "ON" (por cierto, puede tener más de 1 filtro, por lo que necesita n ^ 2 acoplamientos, donde n es el número de filtros ... ¡y tengo esto funcionando!).

Para cambiar de un acoplamiento a otro (es decir, de ON a OFF o viceversa), simplemente sustituye un JTree por otro en el JViewport que lo contiene ... y esto sucede en un abrir y cerrar de ojos, completamente indesmallable. Entonces es como una ilusión óptica.

Por cierto, el filtro utilizado aquí es "does the toString () del nodo contiene la cadena ''nobble''"? (vea el método FilterPair.is_filtered_out)

Algunos podrían decir que tal idea sería ridículamente ineficiente en la memoria ... pero de hecho los nodos en los diferentes acoplamientos, aunque diferentes nodos, usan el mismo objeto de usuario ... así que sugiero que la estructura sea bastante liviana.

Mucho, mucho más difícil es lograr que la mecánica de dos acoplamientos (y mucho menos 4 u 8) se sincronice entre sí. A continuación, se muestra una implementación bastante completa de insertNodeInto ... pero muchos métodos de DefaultTreeModel, de JTree, y también en relación con la selección, requieren una gran cantidad de reflexión. Por ejemplo, si la selección en el árbol OFF (filtro) está en un nodo que no tiene contraparte en el árbol ON (porque él o uno de sus antepasados ​​se ha filtrado), ¿dónde debe ir la selección en el árbol ON? He encontrado respuestas a todas estas preguntas, pero no hay espacio aquí para mostrarlas ...

import java.awt.*; import java.awt.event.*; import java.io.*; import javax.swing.*; import javax.swing.tree.*; public class FilterTreeDemo { public static void main(String[] args) throws FileNotFoundException { EventQueue.invokeLater(new ShowIt()); } } class FiltNode extends DefaultMutableTreeNode { FiltNode( Object user_obj ){ super( user_obj ); } FiltNode m_counterpart_node; // public String toString(){ // // hash code demonstrates (as you toggle) that these are not the same nodes... // return super.toString() + " (" + hashCode() + ")"; // } } class FilterPair { TreeCoupling m_on_coupling, m_off_coupling; boolean m_filter_on = true; JFrame m_main_frame; FiltNode m_on_root = new FiltNode( "root" ); FiltNode m_off_root = new FiltNode( "root" ); // needed to prevent infinite calling between models... boolean m_is_propagated_call = false; FilterPair( JFrame main_frame ){ m_on_root.m_counterpart_node = m_off_root; m_off_root.m_counterpart_node = m_on_root; m_on_coupling = new TreeCoupling( true ); m_off_coupling = new TreeCoupling( false ); m_main_frame = main_frame; // starts by toggling to OFF (i.e. before display) toggle_filter(); } // this is the filter method for this particular FilterPair... boolean is_filtered_out( MutableTreeNode node ){ return node.toString().contains( "nobble"); } class TreeCoupling { class FilterTreeModel extends DefaultTreeModel { FilterTreeModel( TreeNode root ){ super( root ); } public void insertNodeInto(MutableTreeNode new_child, MutableTreeNode parent, int index){ // aliases for convenience FiltNode new_filt_node = (FiltNode)new_child; FiltNode parent_filt_node = (FiltNode)parent; FiltNode new_counterpart_filt_node = null; FiltNode counterpart_parent_filt_node = null; // here and below the propagation depth test is used to skip code which is leading to another call to // insertNodeInto on the counterpart TreeModel... if( ! m_is_propagated_call ){ // NB the counterpart new FiltNode is given exactly the same user object as its counterpart: no duplication // of the user object... new_counterpart_filt_node = new FiltNode( new_filt_node.getUserObject() ); counterpart_parent_filt_node = parent_filt_node.m_counterpart_node; // set up the 2 counterpart relationships between the node in the ON tree and the node in the OFF tree new_counterpart_filt_node.m_counterpart_node = new_filt_node; new_filt_node.m_counterpart_node = new_counterpart_filt_node; } if( TreeCoupling.this == m_on_coupling ){ // ... we are in the ON coupling // if first call and the parent has no counterpart (i.e. in the OFF coupling) sthg has gone wrong if( ! m_is_propagated_call && counterpart_parent_filt_node == null ){ throw new NullPointerException(); } if( ! is_filtered_out( new_filt_node ) ){ // only insert here (ON coupling) if the node is NOT filtered out... super.insertNodeInto( new_filt_node, parent_filt_node, index); } else { // enable the originally submitted new node (now rejected) to be unlinked and garbage-collected... // (NB if you suspect the first line here is superfluous, try commenting out and see what happens) new_filt_node.m_counterpart_node.m_counterpart_node = null; new_filt_node.m_counterpart_node = null; } if( ! m_is_propagated_call ){ // as we are in the ON coupling we can''t assume that the index value should be passed on unchanged to the // OFF coupling: some siblings (of lower index) may be missing here... but we **do** know that the previous // sibling (if there is one) of the new node has a counterpart in the OFF tree... so get the index of its // OFF counterpart and add 1... int off_index = 0; if( index > 0 ){ FiltNode prev_sib = (FiltNode)parent_filt_node.getChildAt( index - 1 ); off_index = counterpart_parent_filt_node.getIndex( prev_sib.m_counterpart_node ) + 1; } m_is_propagated_call = true; m_off_coupling.m_tree_model.insertNodeInto( new_counterpart_filt_node, counterpart_parent_filt_node, off_index); } } else { // ... we are in the OFF coupling super.insertNodeInto( new_filt_node, parent_filt_node, index); if( ! m_is_propagated_call ){ // we are in the OFF coupling: it is perfectly legitimate for the parent to have no counterpart (i.e. in the // ON coupling: indicates that it, or an ancestor of it, has been filtered out) if( counterpart_parent_filt_node != null ){ // OTOH, if the parent **is** available, we can''t assume that the index value should be passed on unchanged: // some siblings of the new incoming node (of lower index) may have been filtered out... to find the // correct index value we track down the index value until we reach a node which has a counterpart in the // ON coupling... or if not found the index must be 0 int on_index = 0; if( index > 0 ){ for( int i = index - 1; i >= 0; i-- ){ FiltNode counterpart_sib = ((FiltNode)parent_filt_node.getChildAt( i )).m_counterpart_node; if( counterpart_sib != null ){ on_index = counterpart_parent_filt_node.getIndex( counterpart_sib ) + 1; break; } } } m_is_propagated_call = true; m_on_coupling.m_tree_model.insertNodeInto( new_counterpart_filt_node, counterpart_parent_filt_node, on_index); } else { // ... no ON-coupling parent node "counterpart": the new ON node must be discarded new_filt_node.m_counterpart_node = null; } } } m_is_propagated_call = false; } } JTree m_tree; FilterTreeModel m_tree_model; TreeCoupling( boolean on ){ m_tree = new JTree(); m_tree_model = on ? new FilterTreeModel( m_on_root ) : new FilterTreeModel( m_off_root ); m_tree.setModel( m_tree_model ); } } void toggle_filter(){ m_filter_on = ! m_filter_on; m_main_frame.setTitle( m_filter_on? "FilterTree - ON (Ctrl-F6 to toggle)" : "FilterTree - OFF (Ctrl-F6 to toggle)" ); } TreeCoupling getCurrCoupling(){ return m_filter_on? m_on_coupling : m_off_coupling; } } class ShowIt implements Runnable { @Override public void run() { JFrame frame = new JFrame("FilterTree"); final FilterPair pair = new FilterPair( frame ); final JScrollPane jsp = new JScrollPane( pair.getCurrCoupling().m_tree ); Action toggle_between_views = new AbstractAction( "toggle filter" ){ @Override public void actionPerformed(ActionEvent e) { pair.toggle_filter(); jsp.getViewport().setView( pair.getCurrCoupling().m_tree ); jsp.requestFocus(); }}; JPanel cpane = (JPanel)frame.getContentPane(); cpane.getActionMap().put("toggle between views", toggle_between_views ); InputMap new_im = new InputMap(); new_im.put(KeyStroke.getKeyStroke(KeyEvent.VK_F6, InputEvent.CTRL_DOWN_MASK), "toggle between views"); cpane.setInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, new_im); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(jsp); frame.pack(); frame.setBounds(50, 50, 800, 500); frame.setVisible(true); // populate the tree(s) NB we are currently viewing the OFF tree FilterPair.TreeCoupling curr_coupling = pair.getCurrCoupling(); curr_coupling.m_tree_model.insertNodeInto( new FiltNode( "scrags 1" ), (FiltNode)curr_coupling.m_tree_model.getRoot(), 0 ); FiltNode d2 = new FiltNode( "scrags 2" ); curr_coupling.m_tree_model.insertNodeInto( d2, (FiltNode)curr_coupling.m_tree_model.getRoot(), 1 ); curr_coupling.m_tree_model.insertNodeInto( new FiltNode( "scrags 3" ), (FiltNode)curr_coupling.m_tree_model.getRoot(), 2 ); curr_coupling.m_tree_model.insertNodeInto( new FiltNode( "scrags 2.1" ), d2, 0 ); // this will be filtered out of the ON tree FiltNode nobble = new FiltNode( "nobble" ); curr_coupling.m_tree_model.insertNodeInto( nobble, d2, 1 ); // this will also be filtered out of the ON tree FiltNode son_of_nobble = new FiltNode( "son of nobble"); curr_coupling.m_tree_model.insertNodeInto( son_of_nobble, nobble, 0 ); curr_coupling.m_tree_model.insertNodeInto( new FiltNode( "peewit (granddaughter of n****e)"), son_of_nobble, 0 ); // expand the OFF tree curr_coupling.m_tree.expandPath( new TreePath( curr_coupling.m_tree_model.getRoot() ) ); curr_coupling.m_tree.expandPath( new TreePath( d2.getPath() ) ); curr_coupling.m_tree.expandPath( new TreePath( nobble.getPath() ) ); curr_coupling.m_tree.expandPath( new TreePath( son_of_nobble.getPath() ) ); // switch view (programmatically) to the ON tree toggle_between_views.actionPerformed( null ); // expand the ON tree curr_coupling = pair.getCurrCoupling(); curr_coupling.m_tree.expandPath( new TreePath( curr_coupling.m_tree_model.getRoot() ) ); curr_coupling.m_tree.expandPath( new TreePath( d2.m_counterpart_node.getPath() ) ); // try to expand the counterpart of "nobble"... there shouldn''t be one... FiltNode nobble_counterpart = nobble.m_counterpart_node; if( nobble_counterpart != null ){ curr_coupling.m_tree.expandPath( new TreePath( nobble_counterpart.getPath() ) ); System.err.println( "oops..." ); } else { System.out.println( "As expected, node /"nobble/" has no counterpart in the ON coupling" ); } // try inserting a node into the ON tree which will immediately be "rejected" by the ON tree (due to being // filtered out before the superclass insertNodeInto is called), but will nonetheless appear in the // OFF tree as it should... curr_coupling.m_tree_model.insertNodeInto( new FiltNode( "yet another nobble"), d2.m_counterpart_node, 0 ); } }

One more thought: how to generalise it so that FilterTreeModel extends your own supplied subclass of DefaultTreeModel? (and in the full implmentation so that FilterJTree extends your supplied JTree subclass?). I wrote this code originally in Jython, where passing a class A as a parameter of a definition of class B is trivial! Using stately old Java, it could possibly be done with reflection and static factory methods, or possibly by some ingenious encapsulation technique. It''d be a hard slog though. Better to switch to Jython if at all possible!


ETable , una subclase de JTable y la clase padre de Outline , descrita here , incluye "características de filtro rápido que permiten mostrar solo ciertas filas del modelo (ver setQuickFilter() )". Si bien esto infringe el requisito de no "libs de terceros", el JAR de Outline no tiene dependencias que no sean el JDK.


A solution was given http://forums.sun.com/thread.jspa?forumID=57&threadID=5378510

We have implemented it here, adn it works as a charm.

You just have to implement you TreeModel, and on the JTree use a FilterTreeModel with a TreeFilter .

The implementation is quite simple, there is perhaps maybe some stuff to do on listener, since the actual code will called twice, which is not good at all. My idea is to just pass listener to delegate model, I don''t see the point to add listener on the filter model... But that is another question.