在 JTree 上筛选 [已关闭]

2022-09-01 07:52:24

问题

对 应用过滤,以避免某些节点/叶子显示在 的 渲染版本中。理想情况下,我正在寻找一种允许拥有动态过滤器的解决方案,但是如果我能让静态过滤器工作,我已经很高兴了。JTreeJTree

为了简化它,让我们假设唯一支持渲染,而不是编辑。移动,添加,删除节点应该是可能的。JTree

例如,在 a 上方有一个搜索字段,键入 时只会显示具有匹配项的子树。JTreeJTree

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

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

方法

基于模型的过滤

  • 修饰 以筛选出一些值。快速和污垢的版本很容易编写。筛选出节点,在过滤器或委托的每次更改时,装饰器都可以触发一个事件,该事件表明整个树都已更改(根节点作为节点)。将其与恢复选择状态和扩展状态的侦听器相结合,您将获得一个或多或少有效的版本,但是源自 的事件被搞砸了。这或多或少是这个问题中使用的方法。TreeModelTreeModeltreeStructureChangedJTreeTreeModel
  • 装饰,但尝试发射正确的事件。我(尚未)设法提出一个工作版本。它似乎需要委托的副本,以便在从委托模型中删除节点时能够触发具有正确子索引的事件。我认为再多花一些时间,我可以让它工作,但它只是感觉不对(过滤感觉像是视图应该做的事情,而不是模型)TreeModelTreeModel
  • 修饰用于创建初始 .但是,这是完全不可重用的,并且可能与为TreeModelTreeModel

基于视图的过滤

这似乎是要走的路。筛选不应影响模型,而应仅影响视图。

  • 我看了一下RowFilter类。尽管javadoc似乎建议你可以将其与:JTree

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

    我找不到(或RowSorter)和类之间的任何链接。和 Swing 教程的标准实现似乎表明只能直接与 a 一起使用(参见 JTable#setRowSorter)。没有类似的方法可用于RowFilterJTreeRowFilterRowFilterJTableJTree

  • 我还看了JXTree javadoc。它有一个可用的ConsqueAdapter和javadoc指示可以与目标组件交互,但我无法看到我如何在和ComponentAdapterRowFilterRowFilterJTree
  • 我还没有研究a如何处理s的过滤,也许在修改后的版本中也可以做同样的事情。JTableRowFilterJTree

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

注意:这个问题可能是这个问题的重复,但这个问题仍然没有答案,问题相当短,答案似乎不完整,所以我想发布一个新问题。如果没有这样做(FAQ没有提供明确的答案),我将更新那个3年前的问题


答案 1

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

它创建DefaultMutableNode的子类,添加“isVisible”属性,而不是实际从TreeModel中删除/添加节点。我认为非常甜蜜,它巧妙地解决了我的过滤问题。


答案 2

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

这绝对是可怕的代码(我刚才试图削减所有可能的角落),但它应该足以让你开始。只需在搜索框中键入查询并按 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分离。请注意,“隐藏”节点仍会呈现,但它们非常小,以至于您无法看到它们。但是,如果您使用箭头键在树中导航,您会注意到它们仍然存在。如果你只需要一些有效的东西,这可能就足够了。

Filtered tree (windows)

编辑

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

Unfiltered treeFiltered tree


推荐