过滤JTree [英] Filtering on a JTree

查看:185
本文介绍了过滤JTree的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题



JTree 中应用过滤,以避免某些节点/叶片在呈现版本中显示的 JTree 。理想情况下,我正在寻找一个允许使用动态过滤器的解决方案,但如果我能够获得静态过滤器,我将很高兴。



有点简单,让我们假设 JTree 只支持渲染,而不是编辑。移动,添加,删除节点应该是可能的。



一个示例是 JTree 上方的搜索字段,并输入 JTree 只会显示具有匹配项的子树。



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



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



方法



基于模型的过滤




  • 装饰 TreeModel 以过滤掉一些值。一个快速和脏的版本很容易写。过滤出节点,并且在过滤器或委托的每个更改 TreeModel 上,装饰器可以触发整个树有更改的事件( treeStructureChanged ,以根节点为节点)。将这与将恢复 JTree 的选择状态和扩展状态的监听器组合起来,您可以获得更多或更少的工作版本,但来自 TreeModel 被搞砸了。这或多或少是此问题中使用的方法

  • 装饰 TreeModel ,但尝试启动正确的事件。我没有(还)设法提出了一个这样的工作版本。当代理模型中删除节点时,似乎需要委托代码 TreeModel 的副本,以便能够使用正确的子索引触发事件。我想有更多的时间我可以得到这个工作,但它只是感觉错误(过滤感觉像视图应该做的,而不是模型)

  • 装饰任何数据结构被使用创建初始的 TreeModel 。但是,这是完全不可重用的,可能很难写一个 TreeModel


的装饰器

基于视图的过滤



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




  • 我看了一下 RowFilter 类。虽然javadoc似乎建议您可以将它与 JTree 结合使用:


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


    我找不到 RowFilter (或 RowSorter )和 JTree 类。 RowFilter 和Swing教程的标准实现似乎表明 RowFilter 只能直接使用 JTable (请参阅 JTable#setRowSorter )。没有类似的方法可以在 JTree


  • 我也查看了 JXTree javadoc。它有一个 ComponentAdapter 可用,并且 ComponentAdapter 的javadoc表示一个 RowFilter 可以与目标组件进行交互,但是我未能看到我如何在 RowFilter JTree

  • 我还没有看到一个 JTable 如何处理过滤 RowFilter s,也可以在修改版本的 JTree 上完成。



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



注意:这个问题可能是这个问题的重复,但是这个问题仍然没有得到答复这个问题比较简短,答案似乎是不完整,所以我想提出一个新的问题。如果没有这样做(常见问题解答没有提供一个明确的答案),我会更新3年前的问题

解决方案

查看的过滤绝对是要走的路。你可以使用像下面编码的例子。过滤树的另一个常见做法是在过滤树时切换到列表视图,因为列表不需要您显示需要显示后代的隐藏节点。<​​/ p>

这是绝对可怕的代码(我试图削减每一个角落可能在打搅它刚刚),但它应该足以让你开始。只需在搜索框中输入查询,然后按Enter键即可过滤JTree的默认模型。 (FYI,前90行只是生成样板和布局代码。)

  package com.example.tree; 

import java.awt.BorderLayout;

public class FilteredJTreeExample extends JFrame {

private JPanel contentPane;
private JTextField textField;

/ **
*启动应用程序。
* /
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();
}
}
});
}

/ **
*创建框架。
* /
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 ,对象值,
boolean arg2,boolean arg3,boolean arg4,int arg5,boolean arg6){

组件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){
枚举< DefaultMutableTreeNode> e = node.breadthFirstEnumeration();
while(e.hasMoreElements()){
if(matchesFilter(e.nextElement())){
return true;
}
}

return false;
}
});
}

}

您可能需要创建自己的TreeNode和TreeCellRenderer实现,使用较少的愚蠢方法触发更新,并遵循MVC分离。请注意,隐藏节点仍然呈现,但它们太小,无法看到它们。如果您使用箭头键导航树,那么您会注意到它们仍然存在。如果你只需要一些有用的东西,这可能就够了。





修改



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




Problem

Applying filtering on a JTree to avoid certain nodes/leaves to show up in the rendered version of the JTree. Ideally I am looking for a solution which allows to have a dynamic filter, but I would already be glad if I can get a static filter to work.

To make it a bit easier, let us suppose the JTree only supports rendering, and not editing. Moving, adding, removing of nodes should be possible.

An example is a search field above a JTree, and on typing the JTree would only show the subtree with matches.

A few restrictions: it is to be used in a project which has access to JDK and SwingX. I would like to avoid to include other third party libs.

I already thought of a few possible solutions, but neither of those seemed ideal

Approaches

Model based filtering

  • decorate the TreeModel to filter out some of the values. A quick-and-dirt version is easy to write. Filter out nodes, and on every change of the filter or the delegate TreeModel the decorator can fire an event that the whole tree has changes (treeStructureChanged with the root node as node). Combine this with listeners which restore the selection state and the expansion state of the JTree and you get a version which works more or less, but the events originating from the TreeModel are messed up. This is more or less the approach used in this question
  • decorate the TreeModel but try fire the correct events. I did not (yet) managed to come up with a working version of this. It seems to require a copy of the delegate TreeModel in order to be able to fire an event with the correct child indices when nodes are removed from the delegate model. I think with some more time I could get this to work, but it just feels wrong (filtering feels like something the view should do, and not the model)
  • decorate whatever data structure was used to create the initial TreeModel. However, this is completely non-reusable, and probably as hard as to write a decorator for a TreeModel

View based filtering

This seems like the way to go. Filtering should not affect the model but only the view.

  • I took a look at RowFilter class. Although the javadoc seems to suggest you can use it in combination with a JTree:

    when associated with a JTree, an entry corresponds to a node.

    I could not find any link between RowFilter (or RowSorter) and the JTree class. The standard implementations of RowFilter and the Swing tutorials seems to suggest that RowFilter can only be used directly with a JTable (see JTable#setRowSorter). No similar methods are available on a JTree

  • I also looked at the JXTree javadoc. It has a ComponentAdapter available and the javadoc of ComponentAdapter indicates a RowFilter could interact with the target component, but I fail to see how I make the link between the RowFilter and the JTree
  • I did not yet look at how a JTable handles the filtering with RowFilters, and perhaps the same can be done on a modified version of a JTree.

So in short: I have no clue on what's the best approach to solve this

Note: this question is a possible duplicate of this question, but that question is still unanswered, the question rather short and the answers seems incomplete, so I thought to post a new question. If this is not done (the FAQ did not provide a clear answer on this) I will update that 3year old question

解决方案

View-based filtering is definitely the way to go. You can use something like the example I've coded below. Another common practice when filtering trees is to switch to a list view when filtering a tree, since the list won't require you to show hidden nodes whose descendants need to be shown.

This is absolutely horrendous code (I tried to cut every corner possible in whipping it up just now), but it should be enough to get you started. Just type your query in the search box and press Enter, and it'll filter the JTree's default model. (FYI, the first 90 lines are just generated boilerplate and layout code.)

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

}

When you implement it for real, you'll probably want to create your own TreeNode and TreeCellRenderer implementations, use a less stupid method for triggering an update, and follow MVC separation. Note that the "hidden" nodes are still rendered, but they're so small that you can't see them. If you use the arrow keys to navigate the tree, though, you'll notice that they're still there. If you just need something that works, this might be good enough.

Edit

Here are screenshots of the unfiltered and filtered version of the tree in Mac OS, showing that the whitespace is visible in Mac OS:

这篇关于过滤JTree的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆