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); } }); } }
关键逻辑说明
- 搜索状态跟踪:用
currentMatchRow和currentMatchCol记录当前找到的匹配位置,每次点击搜索从这个位置的下一个单元格开始查找,避免重复定位同一个匹配项。 - 循环查找:当搜索到表格末尾时,会自动从头开始查找,通过
searchedAll标记避免无限循环提示。 - 渲染器优化:每次渲染单元格时先清空高亮,仅在当前匹配的单元格中高亮关键词,确保同一时刻只有一个高亮项。
- 滚动定位:调用你提供的滚动方法,直接定位到匹配行,还可以开启代码中的注释实现行居中显示。
内容的提问来源于stack exchange,提问作者Molay
相关产品推荐
相关产品推荐

