You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swing中JTable单步搜索定位并高亮功能实现问询

JTable 实现记事本式单匹配搜索与滚动定位方案

兄弟,我看了你的代码和需求,目前的问题主要是渲染器会高亮所有匹配项,而且没有跟踪搜索位置来逐个定位匹配行。我给你调整一下代码,刚好能实现你要的「点击搜索依次定位下一个匹配项,同一时刻仅高亮当前匹配内容」的功能,同时解决滚动的问题。

核心修改点

  • 新增搜索状态变量:跟踪当前匹配的行/列位置,避免重复从头搜索,还能实现循环查找(到末尾后回到开头)
  • 重构搜索逻辑:从当前位置开始逐行逐列扫描匹配项,找到后记录位置并触发滚动
  • 修改单元格渲染器:仅高亮当前匹配的单元格内容,每次渲染前清空高亮避免残留
  • 整合你找到的滚动方法,确保视图精准定位到目标行

修改后的完整代码

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.text.BadLocationException;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Form extends JFrame {
    // 搜索关键词
    private String textForSearch = "";
    private JTable t;
    // 跟踪当前匹配的位置:行、列
    private int currentMatchRow = -1;
    private int currentMatchCol = 0;
    // 标记是否已经完整搜索过一遍(用于循环查找)
    private boolean searchedAll = false;

    public Form() {
        t = new JTable();
        String[] headers = new String[]{"first", "second", "third"};
        DefaultTableModel model = new DefaultTableModel(0, 0);
        model.setColumnIdentifiers(headers);
        // 填充测试数据
        for (int i = 0; i < 1000; i++) {
            model.addRow(new Object[]{i, "test" + i, i * 2});
        }
        t.setModel(model);
        // 给所有列设置自定义渲染器
        for (int i = 0; i < t.getColumnCount(); i++) {
            t.getColumnModel().getColumn(i).setCellRenderer(getRenderer());
        }

        JScrollPane jsp = new JScrollPane(t);
        final RightPanel right = new RightPanel();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        add(jsp, BorderLayout.CENTER);
        add(right, BorderLayout.EAST);
        pack();
        setLocationRelativeTo(null);
    }

    private TableCellRenderer getRenderer() {
        return new TableCellRenderer() {
            JTextField f = new JTextField();

            @Override
            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
                // 每次渲染前清空所有高亮,避免残留
                f.getHighlighter().removeAllHighlights();
                if (value != null) {
                    String cellText = value.toString();
                    f.setText(cellText);
                    // 仅高亮当前匹配的单元格
                    if (row == currentMatchRow && col == currentMatchCol && textForSearch.length() > 0) {
                        int index = cellText.indexOf(textForSearch);
                        if (index != -1) {
                            try {
                                f.getHighlighter().addHighlight(index, index + textForSearch.length(),
                                        new javax.swing.text.DefaultHighlighter.DefaultHighlightPainter(Color.RED));
                            } catch (BadLocationException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                } else {
                    f.setText("");
                }
                // 同步表格选中样式
                if (isSelected) {
                    f.setBackground(table.getSelectionBackground());
                    f.setForeground(table.getSelectionForeground());
                } else {
                    f.setBackground(table.getBackground());
                    f.setForeground(table.getForeground());
                }
                return f;
            }
        };
    }

    // 你提供的滚动方法,稍微调整适配
    private void scrollToVisible(JTable table, int rowIndex) {
        if (!(table.getParent() instanceof JViewport)) return;
        JViewport viewport = (JViewport) table.getParent();
        Rectangle rect = table.getCellRect(rowIndex, 0, true);
        viewport.scrollRectToVisible(rect);
        // 可选:将行居中显示,参考你提到的方法
        // int viewHeight = viewport.getHeight();
        // int rowHeight = table.getRowHeight(rowIndex);
        // int y = rect.y - (viewHeight / 2) + (rowHeight / 2);
        // viewport.setViewPosition(new Point(0, y));
    }

    // 核心搜索逻辑:查找下一个匹配项
    private void findNextMatch() {
        if (textForSearch.isEmpty()) {
            // 清空关键词时重置状态
            currentMatchRow = -1;
            currentMatchCol = 0;
            searchedAll = false;
            t.repaint();
            return;
        }

        DefaultTableModel model = (DefaultTableModel) t.getModel();
        int rowCount = model.getRowCount();
        int colCount = model.getColumnCount();

        // 从当前位置的下一个位置开始搜索
        int startRow = currentMatchRow == -1 ? 0 : currentMatchRow;
        int startCol = currentMatchRow == -1 ? 0 : currentMatchCol + 1;

        boolean found = false;
        // 先从当前行的下一列开始搜索
        for (int col = startCol; col < colCount; col++) {
            Object value = model.getValueAt(startRow, col);
            if (value != null && value.toString().contains(textForSearch)) {
                currentMatchRow = startRow;
                currentMatchCol = col;
                found = true;
                break;
            }
        }
        // 当前行没找到,从下一行开始逐行搜索
        if (!found) {
            for (int row = startRow + 1; row < rowCount; row++) {
                for (int col = 0; col < colCount; col++) {
                    Object value = model.getValueAt(row, col);
                    if (value != null && value.toString().contains(textForSearch)) {
                        currentMatchRow = row;
                        currentMatchCol = col;
                        found = true;
                        break;
                    }
                }
                if (found) break;
            }
        }
        // 所有行都搜完没找到,从头开始搜索(循环查找)
        if (!found && !searchedAll) {
            searchedAll = true;
            for (int row = 0; row <= startRow; row++) {
                int startColForLoop = row == 0 ? 0 : 0;
                for (int col = startColForLoop; col < colCount; col++) {
                    // 跳过之前已经搜过的位置
                    if (row == startRow && col >= startCol) continue;
                    Object value = model.getValueAt(row, col);
                    if (value != null && value.toString().contains(textForSearch)) {
                        currentMatchRow = row;
                        currentMatchCol = col;
                        found = true;
                        break;
                    }
                }
                if (found) break;
            }
        }
        // 找到匹配项后滚动并更新视图
        if (found) {
            scrollToVisible(t, currentMatchRow);
            // 可选:选中当前匹配行
            t.setRowSelectionInterval(currentMatchRow, currentMatchRow);
        } else {
            // 完全没找到时提示
            JOptionPane.showMessageDialog(this, "没有找到匹配内容");
            currentMatchRow = -1;
            searchedAll = false;
        }
        t.repaint();
    }

    class RightPanel extends JPanel {
        public RightPanel() {
            setLayout(new GridBagLayout());
            GridBagConstraints c = new GridBagConstraints();
            c.insets = new Insets(5, 5, 5, 5);
            c.gridy = 0;
            final JTextField f = new JTextField(15);
            add(f, c);

            JButton b = new JButton("搜索");
            b.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    String newText = f.getText().trim();
                    // 关键词变化时重置搜索状态
                    if (!newText.equals(textForSearch)) {
                        currentMatchRow = -1;
                        currentMatchCol = 0;
                        searchedAll = false;
                        textForSearch = newText;
                    }
                    findNextMatch();
                }
            });
            c.gridy++;
            add(b, c);
        }
    }

    public static void main(String[] args) throws Exception {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new Form().setVisible(true);
            }
        });
    }
}

关键逻辑说明

  1. 搜索状态跟踪:用currentMatchRow和currentMatchCol记录当前找到的匹配位置,每次点击搜索从这个位置的下一个单元格开始查找,避免重复定位同一个匹配项。
  2. 循环查找:当搜索到表格末尾时,会自动从头开始查找,通过searchedAll标记避免无限循环提示。
  3. 渲染器优化:每次渲染单元格时先清空高亮,仅在当前匹配的单元格中高亮关键词,确保同一时刻只有一个高亮项。
  4. 滚动定位:调用你提供的滚动方法,直接定位到匹配行,还可以开启代码中的注释实现行居中显示。

内容的提问来源于stack exchange,提问作者Molay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:42:16