34

问题

对 a 应用过滤JTree以避免某些节点/叶子出现在JTree. 理想情况下,我正在寻找一种允许使用动态过滤器的解决方案,但如果我能让静态过滤器工作,我已经很高兴了。

为了让它更容易一点,让我们假设JTree只支持渲染,不支持编辑。移动、添加、删除节点应该是可能的。

一个示例是 a 上方的搜索字段JTree,并且在键入时JTree将仅显示匹配的子树。

一些限制:它用于可以访问 JDK 和 SwingX 的项目中。我想避免包含其他第三方库。

我已经想到了一些可能的解决方案,但这些似乎都不理想

方法

基于模型的过滤

  • 装饰TreeModel以过滤掉一些值。快速和肮脏的版本很容易编写。过滤掉节点,并且在过滤器或委托TreeModel的每次更改时,装饰器都可以触发整个树发生更改的事件(treeStructureChanged以根节点作为节点)。将此与恢复选择状态和扩展状态的侦听器结合使用JTree,您将获得一个或多或少工作的版本,但源自 的事件TreeModel被搞砸了。这或多或少是这个问题中使用的方法
  • 装饰TreeModel但尝试触发正确的事件。我(还)没有设法想出这个的工作版本。当从委托模型中删除节点时,似乎需要委托的副本TreeModel才能触发具有正确子索引的事件。我想再花一些时间我可以让它工作,但它只是感觉不对(过滤感觉像是视图应该做的事情,而不是模型)
  • 装饰用于创建初始TreeModel. 然而,这是完全不可重用的,可能和为一个装饰器写一个装饰器一样难TreeModel

基于视图的过滤

这似乎是要走的路。过滤不应该影响模型,而应该只影响视图。

  • 我看了看RowFilter课。尽管 javadoc 似乎建议您可以将它与 a 结合使用JTree

    当与 JTree 关联时,条目对应于一个节点。

    我找不到RowFilter(或RowSorter)与JTree班级之间的任何联系。Swing 教程的标准实现RowFilter似乎表明RowFilter只能直接与 a 一起使用JTable(请参阅 参考资料JTable#setRowSorter)。没有类似的方法可用JTree

  • 我还查看了JXTreejavadoc。它有一个ComponentAdapter可用的,并且 javadocComponentAdapter表明一个RowFilter可以与目标组件交互,但我看不到我是如何在RowFilter和 之间建立链接的JTree
  • 我还没有研究 a 如何JTable使用 s 处理过滤RowFilter,也许同样可以在 a 的修改版本上完成JTree

简而言之:我不知道解决这个问题的最佳方法是什么

注意:这个问题可能是这个问题的重复,但是那个问题仍然没有答案,这个问题很短而且答案似乎不完整,所以我想发布一个新问题。如果这没有完成(常见问题解答没有提供明确的答案)我将更新那个 3 岁的问题

4

10 回答 10

9

看看这个实现:http ://www.java2s.com/Code/Java/Swing-Components/InvisibleNodeTreeExample.htm

它创建 DefaultMutableNode 的子类,添加“isVisible”属性,而不是实际从 TreeModel 中删除/添加节点。我觉得很可爱,它巧妙地解决了我的过滤问题。

于 2012-12-14T13:31:07.070 回答
8

基于视图的过滤绝对是要走的路。您可以使用类似于我在下面编写的示例的内容。过滤树时的另一个常见做法是在过滤树时切换到列表视图,因为列表不需要您显示需要显示其后代的隐藏节点。

这绝对是可怕的代码(我试图在刚刚编写它时尽可能地削减每个角落),但它应该足以让你开始。只需在搜索框中输入您的查询并按 Enter,它就会过滤 JTree 的默认模型。(仅供参考,前 90 行只是生成的样板和布局代码。)

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

}

当您真正实现它时,您可能希望创建自己的 TreeNode 和 TreeCellRenderer 实现,使用不太愚蠢的方法来触发更新,并遵循 MVC 分离。请注意,“隐藏”节点仍会被渲染,但它们太小以至于您看不到它们。但是,如果您使用箭头键导航树,您会注意到它们仍然存在。如果您只需要一些有用的东西,这可能就足够了。

过滤树(窗口)

编辑

以下是 Mac OS 中树的未过滤和过滤版本的屏幕截图,显示空白在 Mac OS 中可见:

未过滤的树过滤树

于 2012-04-03T21:47:26.217 回答
5

老问题,我偶然发现......对于所有想要快速简单的解决方案的人

只是过滤视图:

我知道它不像过滤模型那么干净,并且可能会出现回撤,但是如果您只想为小型应用程序提供快速解决方案:

扩展 DefaultTableCellRenderer,覆盖getTreeCellRendererComponent - 调用 super.getTreeCellRendererComponent(...) ,然后将要隐藏的所有节点的首选高度设置为零。构建 JTree 时,请务必设置setRowHeight(0); - 所以它会尊重每一行的首选高度......

瞧 - 所有过滤的行都不可见!

完整的工作示例

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;
    }
}
于 2013-12-19T16:41:09.340 回答
2

我一直在研究用于过滤扩展的JXTreeTable. 为简单起见,我遵循了两种模型方法。

过滤模型

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

基本上,这个想法是将节点从原始模型连接/断开连接到过滤模型根,以跟踪当前系列节点。

过滤后的模型将在孩子和父母之间建立一个映射,并使用适当的方法来恢复这个家庭。“restoreFamily”方法将重新连接那些丢失的孩子。

过滤后的模型将在其方法中完成大部分工作,将方法filter()留给实现:abstractshouldBeFiltered(node)

应该考虑到,在将过滤后的孩子连接到过滤后的根之前,无需断开家庭中的所有孩子的连接。如果性能是关键,那么可以更深入地分析这种行为。

扩展 JXTreeTable

最后,但最重要的是,需要通过实现一种方法并覆盖另一种方法来扩展底层树表:

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

可以在此链接中找到带有树表的完整且有效的示例。它包括一个Main.java带有准备构建的树。测试GUI有一个按钮,用于在所选节点(如果有)中添加节点,并在框架顶部添加一个在写入时过滤的文本字段。

于 2012-07-02T16:09:07.763 回答
2

ETable, 的子类JTable和 的父类,此处Outline描述,包括“允许仅显示模型中的某些行的快速过滤功能(请参阅)。” 虽然这违反了没有“第三方库”的要求,但JAR 除了 JDK 之外没有其他依赖项。setQuickFilter()Outline

于 2013-06-04T11:47:44.430 回答
1

这是一个仅使用标准 Swing 组件的可能解决方案:

我还没有使用过这个,但我更喜欢它的实现,而不是我在网上找到的其他快速“n脏”的解决方案。

于 2012-04-03T22:10:37.303 回答
1

我终于设法得到了完全适合我需要的东西,并认为我会分享以防其他人可以使用它。

我一直在尝试并排显示两个 JTree——一个包含另一个的过滤列表。

基本上我已经制作了两个 TreeModel,并为两者使用相同的根节点。到目前为止,这似乎工作正常,只要我确保在我的代码中覆盖从 DefaultTreeModel 调用的每个方法,例如 nodeChanged( TreeNode node ) 否则会有痛苦。

痛苦来自这样一个事实,即节点本身被查询以获取诸如 childcount 之类的信息的唯一时间是在 DefaultTreeModel 上调用节点结构类型方法时。除此之外,所有对树结构信息的调用都可以被拦截和过滤掉,如下所示。

如果您像我一样使用 DefaultTreeModel 作为基础,那么您必须确保每次查询节点本身时都进行挖掘,这可能会变得令人讨厌。如果你直接实现 TreeModel 而不是像我一样偷懒,这个问题可能就不存在了。NodesChanged 源代码直接来自 JDK 源代码。

我很幸运,因为我想要的意味着总是有一条从过滤列表中的每个项目返回到根节点的路径。

这就是我需要做的,即使我花了一整天的时间尝试疯狂和混乱的发明,比如重新创建树的浅拷贝等,更不用说在 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);
        }
    }
}
}
于 2012-08-15T10:01:53.387 回答
0

我对此有一个可能感兴趣的建议。我已经在我自己的应用程序中将它付诸实践,它似乎工作得很好……下面是一个绝对最小的实现 SSCCE,展示了“insertNodeInto”。

中心设计是 JTree-TreeModel 的多个耦合,它们都彼此保持完美同步......除了,显然,应用了一些过滤模式,以便某些节点(及其子树)不存在于一个模型中。同时,ON 树中的每个节点在 OFF 树中都有一个“对应”节点(尽管反过来不一定正确)。

因此,最简单的设计涉及 2 个这样的耦合:一个带有过滤器“OFF”,另一个带有过滤器“ON”(顺便说一下,您可以拥有超过 1 个过滤器,因此您需要 n^2 个耦合,其中 n 是数量过滤器......我已经完成了这个工作!)。

要从一个耦合切换到另一个耦合(即从 ON 切换到 OFF,反之亦然),您只需在包含的 JViewport 中用一个 JTree 替换另一个 JTree……这会在眨眼之间发生,完全无法察觉。所以这有点像一种视错觉。

顺便说一句,这里使用的过滤器是“节点的 toString() 是否包含字符串 'nobble'”?(参见方法 FilterPair.is_filtered_out)

有人可能会说这样的想法内存效率低得离谱……但实际上不同耦合中的节点,虽然不同的节点,使用相同的用户对象……所以我建议结构相当轻量级。

让两个联轴器(更不用说 4 或 8 个)的机制相互同步要困难得多。下面我演示了一个相当全面的 insertNodeInto 实现...但是 DefaultTreeModel、JTree 的许多方法以及与选择有关的许多方法都需要大量思考。例如,如果(过滤器)OFF 树中的选择位于 ON 树中没有对应物的节点上(因为它或其祖先之一已被过滤掉),那么 ON 树中的选择应该去哪里?我已经找到了所有这些问题的答案,但这里没有空间来展示它们......

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


  }
}

再想一想:如何概括它,以便 FilterTreeModel 扩展您自己提供的 DefaultTreeModel 子类?(并且在完整实现中以便 FilterJTree 扩展您提供的 JTree 子类?)。我最初在 Jython 中编写了这段代码,其中将 A 类作为 B 类定义的参数传递是微不足道的!使用庄严的旧 Java,它可能通过反射和静态工厂方法来完成,或者可能通过一些巧妙的封装技术来完成。不过,这将是一个艰难的过程。如果可能,最好切换到 Jython!

于 2013-10-27T12:47:39.570 回答
0

我使用的原理:从 DB 中填充 ArrayList,然后填充树。当我必须过滤树节点时,我只需遍历 ArrayList,删除所有不符合条件的节点,然后使用修改后的 ArrayList 重建树...

于 2012-06-11T13:00:49.603 回答
-1

给出了解决方案http://forums.sun.com/thread.jspa?forumID=57&threadID=5378510

我们在这里实现了它,它就像一个魅力。

您只需要实现您的 TreeModel,并在 JTree 上使用带有TreeFilter的FilterTreeModel

实现非常简单,可能在监听器上需要做一些事情,因为实际的代码会调用两次,这一点都不好。我的想法是将侦听器传递给委托模型,我没有看到在过滤器模型上添加侦听器的意义......但这是另一个问题。

于 2018-01-03T19:10:23.230 回答