java - 排序后,如何在 Netbeans swing 大纲(树表)中保持搜索结果一致?

标签 java swing netbeans

我尝试实现的搜索是类似 google-chrome 的搜索类型,所有匹配项均以黄色突出显示,当前匹配项以橙色突出显示,并且可以使用 Enter 向前导航或使用 Shift+Enter 向后导航。我使用的 Swing 组件是 Netbeans Swing Outline,它基本上看起来像 JTree 表。它一直有效,直到通过单击大纲的任何列对大纲进行排序为止。对于本示例,只有一列。

为了使这个示例正常工作,我们需要来自 Netbeans 发行版的大纲 jar,该文件位于 netbeans\platform\modules\org-netbeans-swing-outline.jar。 另一种选择是将此 Gradle 构建文件 (build.gradle) 放置在 Gradle 项目的根目录中:

plugins {
    id 'java-library'
}

dependencies {
    compile 'uk.gov.nationalarchives.thirdparty.netbeans:org-netbeans-swing-outline:7.2'
}

repositories {
    mavenCentral()
}

树模型具有搜索功能 findNodesWithPattern,可将搜索放入列表中:

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Pattern;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;

public class TestTreeModel extends DefaultTreeModel {

    private static final long serialVersionUID = 1L;

    public TestTreeModel() {
        super(buildModel());
    }

    private static TreeNode buildModel() {
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("");
        for (int i = 0; i < 3; i++) {
            DefaultMutableTreeNode node = new DefaultMutableTreeNode("test" + i);
            for (int j = 0; j < 3; j++) {
                node.add(new DefaultMutableTreeNode("subtest" + j));
            }
            root.add(node);
        }
        return root;
    }

    @Override
    public DefaultMutableTreeNode getRoot() {
        return (DefaultMutableTreeNode) super.getRoot();
    }

    public List<TreeNode> findNodesMatchingPattern(Pattern pattern) {
        List<TreeNode> matchingNodes = new ArrayList<>();
        findNodesWithpattern(matchingNodes, getRoot(), pattern);
        return matchingNodes;
    }

    private void findNodesWithpattern(List<TreeNode> matchingNodes, TreeNode node, Pattern pattern) {
        if (pattern.matcher(node.toString()).find()) {
            matchingNodes.add(node);
        }
        Enumeration<DefaultMutableTreeNode> children = node.children();
        while (children.hasMoreElements()) {
            findNodesWithpattern(matchingNodes, children.nextElement(), pattern);
        }
    }

}

还有一个行模型,它使用对象的哈希代码定义一个名为 Value 的列(仅用于演示目的)

import org.netbeans.swing.outline.RowModel;

public class TestRowModel implements RowModel {
    @Override
    public Class getColumnClass(int column) {
        return Integer.class;
    }

    @Override
    public int getColumnCount() {
        return 1;
    }

    @Override
    public String getColumnName(int column) {
        return "Value";
    }

    @Override
    public Object getValueFor(Object node, int column) {
        return node.hashCode();
    }

    @Override
    public boolean isCellEditable(Object node, int column) {
        return false;
    }

    @Override
    public void setValueFor(Object node, int column, Object value) {
        // do nothing for now
    }
}

负责以黄色或橙色突出显示的渲染:

import java.awt.Color;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;

import org.netbeans.swing.outline.RenderDataProvider;

public class TestRenderData implements RenderDataProvider {

    private String searchPattern;
    private TreeNode currentMatch;

    @Override
    public java.awt.Color getBackground(Object o) {
        return null;
    }

    @Override
    public String getDisplayName(Object o) {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode) o;
        if (searchPattern == null || searchPattern.length() == 0) {
            return node.toString();
        }
        try {
            Pattern pattern = Pattern.compile("(" + searchPattern + ")");
            Matcher matcher = pattern.matcher(node.toString());
            Color bgColor = Color.YELLOW;
            if (matcher.find()) {
                if (node == currentMatch) {
                    bgColor = Color.ORANGE;
                }
            }
            String bgHexColor = Integer.toHexString(bgColor.getRGB() & 0xffffff);
            String replacement = matcher.replaceAll("<span style=\"background-color: #" + bgHexColor + "\">$1</span>");
            return "<html>" + replacement + "</html>";
        } catch (PatternSyntaxException e) {
            return node.toString();
        }
    }

    @Override
    public java.awt.Color getForeground(Object o) {
        return null;
    }

    @Override
    public javax.swing.Icon getIcon(Object o) {
        return null;
    }

    @Override
    public String getTooltipText(Object o) {
        return null;
    }

    @Override
    public boolean isHtmlDisplayName(Object o) {
        return false;
    }

    public void setSearchPattern(String searchPattern) {
        this.searchPattern = searchPattern;
    }

    public void setCurrentMatch(TreeNode currentMatch) {
        this.currentMatch = currentMatch;
    }
}

主类使用快速过滤器(不匹配的节点被过滤掉)和触发搜索的关键监听器构建大纲。当输入 Enter 键时,它会增加计数器 (nthMatch),并且索引 nthMatch 处的当前匹配项会被渲染器以橙色高亮显示。

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.netbeans.swing.etable.QuickFilter;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline;
import org.netbeans.swing.outline.OutlineModel;

public class MainFrame extends JFrame {
    private static final long serialVersionUID = 1L;

    public MainFrame() {
        TestTreeModel treeMdl = new TestTreeModel();
        OutlineModel mdl = DefaultOutlineModel.createOutlineModel(treeMdl, new TestRowModel(), true, "Test");
        Outline outline = new Outline();
        TestRenderData renderData = new TestRenderData();
        outline.setRenderDataProvider(renderData);
        outline.setRootVisible(false);
        outline.setModel(mdl);
        JScrollPane jScrollPane1 = new JScrollPane(outline);
        jScrollPane1.setViewportView(outline);
        JMenuBar mb = new JMenuBar();
        JLabel searchPatternLabel = new JLabel("  Search :  ");
        mb.add(searchPatternLabel);
        JTextField searchPatternTextField = new JTextField();
        searchPatternLabel.setLabelFor(searchPatternTextField);
        outline.setQuickFilter(0, new QuickFilter() {

            @Override
            public boolean accept(Object aValue) {
                if (searchPatternTextField.getText() == null || searchPatternTextField.getText().length() == 0) {
                    return true;
                }
                if (aValue instanceof DefaultMutableTreeNode) {
                    DefaultMutableTreeNode node = (DefaultMutableTreeNode) aValue;
                    Enumeration<DefaultMutableTreeNode> children = node.children();
                    while (children.hasMoreElements()) {
                        DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement();
                        if (accept(child)) {
                            return true;
                        }
                    }
                    try {
                        Pattern searchPattern = Pattern.compile(searchPatternTextField.getText());
                        return searchPattern.matcher(node.toString()).find();
                    } catch (PatternSyntaxException ex) {
                        return true;
                    }
                }
                return false;
            }
        });
        mb.add(searchPatternTextField);
        setJMenuBar(mb);
        getContentPane().add(jScrollPane1);
        pack();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);

        searchPatternTextField.addKeyListener(new KeyAdapter() {

            private int nthMatch = 0;

            @Override
            public void keyReleased(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    if (e.isShiftDown()) {
                        nthMatch--;
                    } else {
                        nthMatch++;
                    }
                }
                try {
                    Pattern searchPattern = Pattern.compile(searchPatternTextField.getText());
                    List<TreeNode> matchingNodes = treeMdl.findNodesMatchingPattern(searchPattern);
                    if (matchingNodes.size() > 0) {
                        if (nthMatch >= matchingNodes.size()) {
                            nthMatch = 0;
                        }
                        if (nthMatch < 0) {
                            nthMatch = 0;
                        }
                        TreeNode matchingNode = matchingNodes.get(nthMatch);
                        renderData.setCurrentMatch(matchingNode);
                        TreePath matchingNodePath = new TreePath(((DefaultMutableTreeNode) matchingNode).getPath());
                        outline.expandPath(matchingNodePath);
                        outline.scrollRectToVisible(outline.getPathBounds(matchingNodePath));
                        renderData.setSearchPattern(searchPatternTextField.getText());
                    }
                } catch (PatternSyntaxException ex) {

                }
                //outline.repaint();
                treeMdl.nodeChanged(treeMdl.getRoot());
            }

        });

    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new MainFrame();
            }
        });

    }
}

作为一个基本场景,我通过按 Enter 反复查找“test”。 每当单击某一列进行排序时,搜索结果的顺序就会困惑。 Outline中有2个继承的方法convertRowIndexToModel和convertRowIndexToView似乎做了索引映射,但我不知道如何使用它们。

最佳答案

每当搜索字段发生变化时,只需通知渲染器新的搜索模式即可。这将处理键入时以黄色突出显示的部分:

searchPatternTextField.getDocument().addDocumentListener(new DocumentListener() {

    @Override
    public void removeUpdate(DocumentEvent e) {
        renderData.setSearchPattern(searchPatternTextField.getText());
        treeMdl.nodeChanged(treeMdl.getRoot());
    }

    @Override
    public void insertUpdate(DocumentEvent e) {
        renderData.setSearchPattern(searchPatternTextField.getText());
        treeMdl.nodeChanged(treeMdl.getRoot());
    }

    @Override
    public void changedUpdate(DocumentEvent e) {
        renderData.setSearchPattern(searchPatternTextField.getText());
        treeMdl.nodeChanged(treeMdl.getRoot());
    }
});

然后监听 Enter 和 Shift+Enter。只需在 View 中查找上一个、当前和下一个匹配节点:

searchPatternTextField.addKeyListener(new KeyAdapter() {

    private void showMatchingNode(TreeNode node) {
        renderData.setCurrentMatch(node);
        TreePath matchingNodePath = new TreePath(((DefaultMutableTreeNode) node).getPath());
        outline.expandPath(matchingNodePath);
        outline.scrollRectToVisible(outline.getPathBounds(matchingNodePath));
    }

    public TreeNode findNextMatchingNode(boolean lookForPrevious) {
        try {
            Pattern searchPattern = Pattern.compile(searchPatternTextField.getText());
            boolean currentMatchFound = false;
            TreeNode previousMatch = null;
            for (int i = 0; i < outline.getRowCount(); i++) {
                TreeNode node = (TreeNode) outline.getValueAt(i, 0);
                if (node == renderData.getCurrentMatch()) {
                    currentMatchFound = true;
                    if (previousMatch != null && lookForPrevious) {
                        return previousMatch;
                    }
                } else if (searchPattern.matcher(node.toString()).find()) {
                    if (renderData.getCurrentMatch() == null || (currentMatchFound && !lookForPrevious)) {
                        return node;
                    }
                    previousMatch = node;
                }
            }
        } catch (PatternSyntaxException ex) {

        }
        return null;
    }

    @Override
    public void keyReleased(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
            TreeNode node = findNextMatchingNode(e.isShiftDown());
            if (node != null) {
                showMatchingNode(node);
            }
            treeMdl.nodeChanged(treeMdl.getRoot());
        }
    }

});

关于java - 排序后,如何在 Netbeans swing 大纲(树表)中保持搜索结果一致?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54476528/

相关文章:

java - EasyMock 和 Unitils 相当于 Mockito @InjectMocks

java - 如何检查该字段是否存在于 firestore 中?

java - JTable模型的重要性?

java - Netbeans 中央存储库的解包索引

java - 在 Java 8 中是否有更简洁的方法来执行此操作 - map 操作

java - 如何获取泛型类的类引用

java - 如何在 Swing 中制作此 GUI 布局

java - JTextField 不更新 for 循环中的用户输入

java - Netbeans 中的 "Convert to try-with-resources"- Cool Beans?

java - 设置公共(public)类以供其他项目访问