27 votes

Filtrage sur un JTree

Problème

Appliquer le filtrage sur un JTree pour éviter que certains nœuds/feuilles n'apparaissent dans la version rendue de l'image. JTree . Idéalement je cherche une solution qui permette d'avoir un filtre dynamique, mais je serais déjà content si je peux faire fonctionner un filtre statique.

Pour rendre les choses un peu plus faciles, supposons que l'option JTree ne prend en charge que le rendu, et non l'édition. Le déplacement, l'ajout et la suppression de nœuds devraient être possibles.

Par exemple, un champ de recherche au-dessus d'un JTree et en tapant le JTree ne montrerait que le sous-arbre avec des correspondances.

Quelques restrictions : il doit être utilisé dans un projet qui a accès au JDK et à SwingX. J'aimerais éviter d'inclure d'autres librairies tierces.

J'avais déjà pensé à quelques solutions possibles, mais aucune ne semblait idéale.

Approches

Filtrage basé sur un modèle

  • décorer le TreeModel pour filtrer certaines des valeurs. Une version rapide est facile à écrire. Filtrez les noeuds, et à chaque changement du filtre ou du délégué TreeModel le décorateur peut déclencher un événement indiquant que l'arbre entier a été modifié ( treeStructureChanged avec le nœud racine comme nœud). Combinez cela avec des listeners qui restaurent l'état de sélection et l'état d'expansion de la fonction JTree et vous obtenez une version qui fonctionne plus ou moins, mais les événements provenant de la TreeModel sont perturbés. C'est plus ou moins l'approche utilisée dans cette question
  • décorer le TreeModel mais essayez de déclencher les bons événements. Je n'ai pas (encore) réussi à trouver une version fonctionnelle de ceci. Il semble qu'il faille une copie du délégué TreeModel afin de pouvoir déclencher un événement avec les bons indices enfants lorsque les nœuds sont supprimés du modèle de délégué. Je pense qu'avec un peu plus de temps, je pourrais faire en sorte que cela fonctionne, mais cela ne semble pas correct (le filtrage semble être quelque chose que la vue devrait faire, et non le modèle).
  • décorer la structure de données utilisée pour créer l'image initiale. TreeModel . Cependant, ceci est complètement non réutilisable, et probablement aussi difficile que d'écrire un décorateur pour un fichier TreeModel

Filtrage par vue

Cela semble être la voie à suivre. Le filtrage ne devrait pas affecter le modèle mais seulement la vue.

  • J'ai jeté un coup d'oeil à RowFilter classe. Bien que la javadoc semble indiquer que vous pouvez l'utiliser en combinaison avec une classe JTree :

    lorsqu'elle est associée à un JTree, une entrée correspond à un nœud.

    Je n'ai pas pu trouver de lien entre RowFilter (ou RowSorter ) et le JTree classe. Les implémentations standard de RowFilter et les tutoriels Swing semblent suggérer que RowFilter ne peut être utilisé directement qu'avec un JTable (voir JTable#setRowSorter ). Aucune méthode similaire n'est disponible sur un JTree

  • J'ai aussi regardé le JXTree javadoc. Il dispose d'un ComponentAdapter disponible et la javadoc de ComponentAdapter indique un RowFilter pourrait interagir avec le composant cible, mais je ne vois pas comment faire le lien entre les composants de l RowFilter et le JTree

  • Je n'ai pas encore examiné comment un JTable gère le filtrage avec RowFilter et on peut peut-être faire la même chose sur une version modifiée d'une JTree .

Donc, en bref, je n'ai aucune idée de la meilleure approche à adopter pour résoudre ce problème.

Note : cette question est un double éventuel de cette question mais cette question est toujours sans réponse, la question est plutôt courte et les réponses semblent incomplètes, alors j'ai pensé poster une nouvelle question. Si cela n'est pas fait (la FAQ n'a pas fourni de réponse claire à ce sujet), je mettrai à jour cette question vieille de 3 ans.

5voto

rob Points 3107

Le filtrage basé sur la vue est définitivement la voie à suivre. Vous pouvez utiliser quelque chose comme l'exemple que j'ai codé ci-dessous. Une autre pratique courante lors du filtrage d'arbres est de passer à une vue en liste pour filtrer un arbre, puisque la liste ne vous obligera pas à afficher les nœuds cachés dont les descendants doivent être affichés.

Il s'agit d'un code absolument horrible (j'ai essayé de couper tous les angles possibles en le réalisant à l'instant), mais il devrait suffire pour vous permettre de commencer. Il suffit de taper votre requête dans le champ de recherche et d'appuyer sur Entrée, et le modèle par défaut de JTree sera filtré. (Pour info, les 90 premières lignes sont juste du boilerplate généré et du code de mise en page).

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

}

Lorsque vous l'implémenterez pour de bon, vous voudrez probablement créer vos propres implémentations TreeNode et TreeCellRenderer, utiliser une méthode moins stupide pour déclencher une mise à jour, et suivre la séparation MVC. Notez que les noeuds "cachés" sont toujours rendus, mais ils sont si petits que vous ne pouvez pas les voir. Si vous utilisez les touches fléchées pour naviguer dans l'arbre, vous remarquerez qu'ils sont toujours là. Si vous avez juste besoin d'un outil qui fonctionne, cela peut être suffisant.

Filtered tree (windows)

Modifier

Voici des captures d'écran de la version non filtrée et filtrée de l'arbre sous Mac OS, montrant que les espaces blancs sont visibles sous Mac OS :

Unfiltered treeFiltered tree

3voto

Martin Wickman Points 9628

Jetez un coup d'œil à cette mise en œuvre : http://www.java2s.com/Code/Java/Swing-Components/InvisibleNodeTreeExample.htm

Il crée des sous-classes de DefaultMutableNode en ajoutant une propriété "isVisible" plutôt qu'en supprimant/ajoutant réellement des noeuds de l'arbre-modèle. C'est très bien, je pense, et cela a résolu mon problème de filtrage.

3voto

Falco Points 451

Vieille Question, je suis tombé sur... pour tous ceux qui veulent une Solution rapide et facile de

JUSTE FILTRER LA VUE :

Je sais que ce n'est pas aussi propre que le filtrage du modèle et qu'il y a de possibles inconvénients, mais si vous voulez juste une solution rapide pour une petite application :

Étendez le DefaultTableCellRenderer, en surchargeant getTreeCellRendererComponent - invoquez super.getTreeCellRendererComponent(...) et après cela, définissez simplement la hauteur préférée à ZERO pour tous les nœuds que vous souhaitez masquer. Lors de la construction de votre JTree, veillez à définir le paramètre setRowHeight(0) ; - afin qu'il respecte la hauteur préférée de chaque ligne...

voilà - toutes les rangées filtrées sont invisibles !

EXEMPLE COMPLET DE FONCTIONNEMENT

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

1voto

Nate W. Points 5211

Voici une solution possible qui n'utilise que des composants Swing standard :

Je ne l'ai pas encore utilisé, mais j'aime beaucoup plus sa mise en œuvre que les autres solutions rapides et sales que j'ai trouvées en ligne.

1voto

Sebastian Points 845

J'ai travaillé sur une solution de contournement pour le filtrage d'une longue JXTreeTable . J'ai suivi le deux modèles par souci de simplicité.

Le modèle filtré

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

En gros, l'idée était de connecter/déconnecter les nœuds du modèle original au modèle filtré Root en gardant la trace de l'état actuel de l'utilisateur. famille des nœuds.

Le modèle filtré aura une correspondance entre les enfants et les parents, avec la méthode appropriée pour restaurer cette famille. La méthode "restoreFamily" permettra de reconnecter les enfants manquants.

Le modèle filtré fera la plupart du travail dans son filter() laissant la méthode abstract méthode shouldBeFiltered(node) aux mises en œuvre :

Il faut tenir compte du fait qu'il n'est pas nécessaire de déconnecter TOUS les enfants de la famille avant de connecter les enfants filtrés à la racine filtrée. Si la performance est essentielle, ce comportement pourrait être analysé plus en profondeur.

Extension de JXTreeTable

Enfin, mais c'est le plus important, il est nécessaire d'étendre le tableau sous-jacent en implémentant une méthode et en surchargeant une autre :

@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 exemple complet et fonctionnel, avec un tableau, peut être trouvé à l'adresse suivante lien . Il comprend un Main.java avec un arbre prêt-à-construire. Les tests GUI a un bouton qui ajoute des noeuds dans le noeud sélectionné (s'il y en a) et en haut du cadre un champ de texte qui filtre pendant l'écriture.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X