Filtrage sur un JTree

problème

application de filtrage sur un JTree pour éviter que certains noeuds/feuilles apparaissent dans la version rendue du JTree . Idéalement, je suis à la recherche d'une solution qui permet d'avoir un filtre dynamique, mais je voudrais déjà être content si je peux obtenir un filtre statique de travail.

pour le rendre un peu plus facile, supposons que le JTree ne supporte que le rendu, et non l'édition. Déplacement, Ajout, Suppression des noeuds devraient être possible.

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

quelques restrictions: il doit être utilisé dans un projet qui a accès à JDK et SwingX. Je voudrais éviter d'inclure d'autres tiers libs.

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

approches

filtrage basé sur le modèle

  • décorez le TreeModel pour filtrer certaines des valeurs. Une version rapide et sale est facile à écrire. Filtrer les noeuds, et à chaque changement du filtre ou du délégué TreeModel le décorateur peut lancer un événement que l'arbre entier a des changements ( treeStructureChanged avec le noeud racine comme noeud). Combinez cela avec des écouteurs qui restaurent l'état de sélection et l'expansion état du JTree et vous obtenez une version qui fonctionne plus ou moins, mais les événements provenant du TreeModel sont foiré. C'est plus ou moins l'approche utilisée dans cette question
  • décorez le TreeModel mais essayez de tirer les événements corrects. Je n'ai pas (encore) réussi à en trouver une version de travail. Il semble exiger une copie du délégué TreeModel afin d'être en mesure de déclencher un événement avec le bon enfant indices lorsque les noeuds sont supprimés du modèle délégué. Je pense qu'avec un peu plus de temps, je pourrais faire en sorte que cela fonctionne, mais cela me semble tout simplement erroné (filtrage se sent comme quelque chose que la vue devrait faire, et pas le modèle)
  • décorent quelle que soit la structure de données utilisée pour créer le TreeModel initial . Cependant, c'est complètement non-réutilisable, et probablement aussi difficile d'écrire un décorateur pour un TreeModel

Vue de filtrage en fonction de

ça semble être la voie à suivre. Le filtrage ne doit pas affecter le modèle mais seulement la vue.

  • j'ai regardé RowFilter classe. Bien que la javadoc semble suggérer que vous pouvez l'utiliser en combinaison avec un JTree :

    lorsqu'elle est associée à un JTree, une entrée correspond à un noeud.

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

  • j'ai aussi regardé le JXTree javadoc. Il a un ComponentAdapter disponible et le javadoc de ComponentAdapter indique un RowFilter pourrait interagir avec le composant cible, mais je ne vois pas comment je fais le lien entre le RowFilter et le JTree
  • Je n'ai pas encore regardé comment un JTable gère le filtrage avec RowFilter s, et peut-être que la même chose peut être faite sur une version modifiée d'un JTree .

en bref: I n'ont aucune idée de ce qui est la meilleure approche pour résoudre ce

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

32
demandé sur Community 2012-02-11 00:19:39

10 réponses

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 des arbres est de passer à une vue Liste lors du filtrage d'un arbre, puisque la liste ne vous obligera pas à afficher les noeuds cachés dont les descendants doivent être affichés.

c'est un code absolument horrible (j'ai essayé de couper tous les coins possibles en le fouettant tout à l'heure), mais ça devrait être suffisant pour vous mettre en route. Il suffit de saisir votre interrogez dans la boîte de recherche et appuyez sur Entrée, et il filtrera le modèle par défaut de JTree. (Pour votre information, les 90 premières lignes ne sont que des codes boilerplate et layout générés.)

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émentez pour de vrai, 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 encore 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'arborescence, cependant, vous remarquerez qu'ils sont toujours là. Si vous avez juste besoin de quelque chose qui fonctionne, cela pourrait être suffisant.

Filtered tree (windows)

Modifier

voici des captures d'écran de la version non filtrée et filtrée de L'arbre dans Mac OS, montrant que L'espace est visible dans Mac OS:

Unfiltered tree Filtered tree

7
répondu rob 2013-03-31 16:21:30

Regardez cette implémentation: 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 enlevant/ajoutant des noeuds du TreeModel. Assez doux je pense, et ça a résolu mon problème de filtrage proprement.

6
répondu Martin Wickman 2012-12-14 13:31:07

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

JUST FILTERING THE VIEW:

Je sais qu'il N'est pas aussi propre que le filtrage du modèle et est livré avec des Tirants Arrière possibles, mais si vous voulez juste une solution rapide pour une petite Application:

Extend the DefaultTableCellRenderer, override gettreecellrendercomponent - invoke super.getTreeCellRendererComponent(...) et après cela il suffit de définir la hauteur préférée à zéro pour tous les noeuds que vous voulez cacher. Lors de la construction de votre JTree assurez - vous de mettre setRowHeight(0); - de sorte qu'il respectera la hauteur préférée de chaque rangée...

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

COMPLETE WORKING EXAMPLE

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;
    }
}
4
répondu Falco 2014-04-09 11:35:58

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 d'autres quick 'n Solutions Sales que j'ai trouvé en ligne.

1
répondu Nate W. 2012-04-03 22:10:37

j'ai travaillé sur un échafaudage pour filtrer un JXTreeTable étendu . J'ai suivi l'approche à deux modèles pour la 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;
   }

fondamentalement, l'idée était de connecter/déconnecter les noeuds du modèle original à la racine filtrée du modèle en gardant la trace des noeuds actuels famille .

Le modèle filtré aura un cartographie entre les enfants et les parents, avec la méthode appropriée pour restaurer cette famille. La méthode "restoreFamily" reliera à nouveau les enfants disparus.

le modèle filtré fera la plupart du travail dans sa méthode filter() laissant la abstract méthode shouldBeFiltered(node) aux implémentations:

il faut tenir compte du fait qu'il n'est pas nécessaire de découpler tous les enfants de la famille avant de relier les enfants filtrés aux enfants filtrés. racine. Si la performance est la clé, ce comportement pourrait être analysé plus en profondeur.

Extending JXTreeTable

enfin, mais le plus important, il y a la nécessité d'étendre le treetable sous-jacent en mettant en œuvre une méthode et en supplantant 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 treetable peut être trouvé à ce link . Il comprend un Main.java avec un arbre prêt à construire. Le testing GUI a un bouton qui ajoute des noeuds dans le noeud sélectionné (s'il y en a) et dans le haut du cadre un champ de texte qui filtre lors de l'écriture.

1
répondu Sebastian 2012-07-02 16:09:07

j'ai finalement réussi à tirer quelque chose qui répond parfaitement à mes besoins, et j'ai pensé que je partagerais au cas où quelqu'un d'autre pourrait l'utiliser.

j'ai essayé d'afficher deux JTrees côte à côte, qui contient une liste filtrée de l'autre.

en gros, j'ai fait deux Tremodels, et j'utilise le même noeud racine pour les deux. Cela semble bien fonctionner jusqu'à présent, aussi longtemps que je m'assure de passer outre chaque méthode qui est appelé de Defaultreemodel dans mon code, tel que nodeChanged (nœud TreeNode) ou bien il y aura de la douleur.

la douleur vient du fait que le seul moment où les noeuds eux-mêmes sont interrogés pour des informations comme childcount, est quand les méthodes de type nodestructure sont appelées sur le DefaultTreeModel. En dehors de cela, tous les appels à des informations sur la structure de l'arbre peuvent être interceptés et filtrés comme indiqué ci-dessous.

cela peut devenir désagréable que vous devez vous assurer que vous creusez à chaque fois le les noeuds eux-mêmes sont interrogés si vous utilisez DefaultTreeModel comme base comme je l'ai fait. Ce problème pourrait ne pas être là si vous mettez en œuvre TreeModel directement au lieu d'être paresseux comme moi. Aucune source modifiée ne vient directement de la source JDK.

j'ai eu la chance que ce que je voulais signifiait qu'il y avait toujours un chemin de retour vers le noeud racine à partir de chaque élément de la liste filtrée.

C'était tout ce dont j'avais besoin de faire, même si j'ai passé toute la journée à essayer sauvage et chaotique inventions, comme recréer une copie peu profonde de l'arbre etc, sans parler de lire beaucoup sur la pile !:

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);
        }
    }
}
}
1
répondu bobjandal 2012-08-15 11:19:15

ETable , une sous-classe de JTable et la classe de parent de Outline , décrit ici , comprend " caractéristiques de filtre rapide permettant de montrer seulement certaines lignes du modèle (voir setQuickFilter() )."Bien que cela viole l'exigence de non "tierce partie libs", le JAR Outline n'a pas de dépendances autres que le JDK.

1
répondu trashgod 2017-04-13 12:40:36

Principe que j'ai utilisé: Remplir L'ArrayList de DB, puis peupler l'arbre. Quand je dois filtrer les noeuds d'arbre, je n'ai qu'à itérer à travers ArrayList, enlever tous les noeuds qui ne correspondent pas aux critères, puis reconstruire l'arbre avec ArrayList modifié...

0
répondu guest86 2012-06-11 13:00:49

j'ai une suggestion qui pourrait vous intéresser. Je l'ai mis en pratique dans ma propre application et il semble fonctionner bien... ci-dessous, un sscce de mise en œuvre minimale absolue démontrant "insertNodeInto".

la conception centrale est des raccords multiples JTree-TreeModel qui sont tous maintenus en parfaite harmonie les uns avec les autres... sauf, évidemment, qu'une certaine forme de filtrage est appliquée de sorte que certains noeuds (et leurs sous-noeuds) ne sont pas présents dans un modèle. Pendant ce temps, chaque noeud de l'arbre ON a un noeud "homologue" dans L'arbre OFF (bien que l'inverse ne soit pas nécessairement vrai).

la conception la plus simple implique donc 2 accouplements de ce type: l'un avec le filtre "OFF" et l'autre avec le filtre "ON" (soit dit en passant, vous pouvez avoir plus d'un filtre, de sorte que vous avez alors besoin n^2 accouplements, où n est le nombre de filtres... et j'ai obtenu ce travail!).

pour passer d'un accouplement à un autre (c'est-à-dire à partir de ON remplace un JTree par un autre dans le jviewport contenant... et cela se produit en un clin d'œil, complètement indemne. Donc c'est un peu comme une illusion d'optique.

Par ailleurs, le filtre utilisé ici est "le toString() du nœud contient la chaîne 'nobble'"? (voir méthode FilterPair.is_filtered_out)

Certains pourraient dire qu'une telle idée serait ridiculement mémoire inefficace... mais en fait, le les noeuds des différents couplages, bien que différents noeuds, utilisent le même objet utilisateur... je pense donc que la structure est assez légère.

il est beaucoup plus difficile de synchroniser la mécanique de deux accouplements (encore moins 4 ou 8). Ci-dessous, je montre une mise en œuvre assez complète d'insertNodeInto... mais de nombreuses méthodes de DefaultTreeModel, de JTree, et aussi relatives à la sélection, exigent beaucoup de réflexion. E. g. si la sélection dans le (filtre) HORS de l'arbre est un nœud qui n'a pas d'homologue dans l'arbre (parce qu'elle ou l'un de ses ancêtres a été filtrés), où la sélection dans l'arbre? J'ai trouvé des réponses à toutes ces questions, mais il n'y a pas de place ici pour les afficher...

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


  }
}

une dernière réflexion: comment la généraliser pour que FilterTreeModel étende votre propre sous-classe de Defaultreemodel fourni? (et dans l'implementation complète de sorte que FilterJTree étend votre JTree sous-classe?). J'ai écrit ce code à L'origine en Jython, où passer une classe A comme paramètre d'une définition de classe B est trivial! En utilisant un vieux Java majestueux, cela pourrait être fait avec des méthodes de réflexion et d'usine statique, ou peut-être par une technique ingénieuse d'encapsulation. Ce serait un dur bossez bien. Mieux vaut passer à Jython si possible!

0
répondu mike rodent 2013-11-04 18:57:03

une solution a été donnée http://forums.sun.com/thread.jspa?forumID=57&threadID=5378510

nous l'avons mis en œuvre ici, adn il fonctionne comme un charme.

vous avez juste à mettre en œuvre vous TreeModel, et sur le JTree utiliser un FilterTreeModel avec un TreeFilter .

la mise en œuvre est assez simple, Il ya peut-être des choses à faire sur l'auditeur, depuis le code sera appelé deux fois, ce qui n'est pas bon du tout. Mon idée est de passer l'auditeur au modèle délégué, Je ne vois pas l'intérêt d'ajouter l'auditeur sur le modèle filter... Mais c'est une autre question.

-1
répondu Tony Chemit 2018-01-03 19:10:23