Java 8下实现JTextArea固定悬浮搜索框的问题
Java 8下实现JTextArea固定悬浮搜索框的问题
嘿,我完全懂你的困扰——用JPopupMenu做的搜索框动不动就消失,滚动一下或者点个别的地方就没影了,根本没法像Chrome的搜索框那样稳稳贴在滚动面板的左上角。而且你还不想破坏JScrollPane的原有布局,也不想把搜索框放在BorderLayout的NORTH区域,就得是那种overlay在文本区域上面的效果,对吧?
其实JPopupMenu本身就是为临时弹出的菜单设计的,它自带“失去焦点就自动隐藏”的特性,所以用来做固定悬浮的搜索框天生就不合适。要实现你要的效果,咱们得换个思路,用普通的容器组件来做,然后手动控制它的位置和显示状态。
解决方案思路
- 把原来的
JPopupMenu换成普通的JPanel作为搜索容器,避免自动消失的问题 - 将这个容器设置为透明,添加到JScrollPane的Viewport的GlassPane上,完全不干扰原有布局
- 监听滚动和视图大小变化事件,实时调整搜索容器的位置,确保它始终停在Viewport左上角
- 保留原有的Ctrl+F快捷键逻辑,触发时显示搜索框并自动聚焦
修改后的完整代码
import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; import javax.swing.event.AncestorEvent; import javax.swing.event.AncestorListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; public class TextAreaSearchDemo { static JPanel mainPanel; static JTextField searchField; static JPanel searchOverlay; // 替换JPopupMenu为普通JPanel static JScrollPane scroller; public static void main(String[] args) { // 确保所有Swing操作在事件调度线程执行 SwingUtilities.invokeLater(() -> { Container mainPanel = createMainPanel(); JFrame frame = new JFrame("Text Area Search Demo"); frame.setContentPane(mainPanel); frame.setLocationRelativeTo(null); frame.pack(); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setVisible(true); }); } static JPanel createMainPanel() { mainPanel = new JPanel(new BorderLayout()); scroller = createScroller(); mainPanel.add(scroller); createSearchOverlay(); // 初始隐藏搜索框,按Ctrl+F显示 searchOverlay.setVisible(false); return mainPanel; } static JScrollPane createScroller() { JScrollPane scroller = new JScrollPane(); scroller.setPreferredSize(new Dimension(500, 400)); scroller.setViewportView(createTextArea()); // 监听滚动事件,实时更新搜索框位置 scroller.getViewport().addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { updateSearchOverlayPosition(); } }); return scroller; } static JTextArea createTextArea() { JTextArea textArea = new JTextArea(); textArea.setWrapStyleWord(true); textArea.setLineWrap(true); textArea.setEditable(false); textArea.setText(createText()); textArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F, KeyEvent.CTRL_DOWN_MASK), "search"); textArea.getActionMap().put("search", createSearchAction()); return textArea; } static private String createText() { return "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Maecenas bibendum, nulla a tempus volutpat, enim magna scelerisque enim, " + "et tempor erat urna vel lorem. Curabitur tristique, tortor sit amet bibendum tempus, " + "elit tellus semper justo, a semper ligula eros nec lectus. Donec lorem risus, posuere " + "at aliquet eget, aliquet eget mi. Proin viverra erat vehicula fringilla auctor. " + "Morbi eleifend nisl vitae eros hendrerit posuere. Ut porta tortor eu quam iaculis, vitae " + "luctus risus elementum. Phasellus ac bibendum eros. Nam quis magna non elit semper luctus quis " + "non odio. Sed iaculis risus non odio pulvinar, eu maximus felis eleifend. Aliquam aliquam augue " + "aliquam leo ullamcorper rhoncus. Suspendisse at nibh ante."; } private static Action createSearchAction() { return new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { searchOverlay.setVisible(true); // 延迟聚焦,确保搜索框已经显示 SwingUtilities.invokeLater(() -> searchField.requestFocus()); } }; } private static void createSearchOverlay() { // 创建透明悬浮面板,用null布局手动定位 searchOverlay = new JPanel(null); searchOverlay.setOpaque(false); // 透明不遮挡文本 searchOverlay.setPreferredSize(scroller.getViewport().getSize()); // 创建搜索框并设置位置 searchField = new JTextField(10); searchField.addActionListener(e -> search(searchField.getText())); searchField.setBounds(5, 5, 150, 25); // 左上角偏移5像素,避免贴边 searchOverlay.add(searchField); // 添加到Viewport的GlassPane,完全不干扰原有布局 scroller.getViewport().setGlassPane(searchOverlay); // 监听视图大小变化,更新悬浮面板尺寸 scroller.getViewport().addAncestorListener(new AncestorListener() { @Override public void ancestorAdded(AncestorEvent event) { updateSearchOverlaySize(); } @Override public void ancestorRemoved(AncestorEvent event) {} @Override public void ancestorMoved(AncestorEvent event) { updateSearchOverlayPosition(); } }); } private static void updateSearchOverlaySize() { searchOverlay.setSize(scroller.getViewport().getSize()); } private static void updateSearchOverlayPosition() { // 让悬浮面板和视图滚动位置同步,保持搜索框在左上角 searchOverlay.setLocation(scroller.getViewport().getViewPosition()); } private static void search(String text) { System.out.println("Searching for " + text); // 这里可以添加实际的文本高亮逻辑,比如: // JTextArea textArea = (JTextArea) scroller.getViewport().getView(); // int pos = textArea.getText().indexOf(text); // if (pos != -1) { // textArea.setSelectionStart(pos); // textArea.setSelectionEnd(pos + text.length()); // } } }
关键细节说明
- 用透明
JPanel替代JPopupMenu,彻底解决了自动消失的问题 - 通过GlassPane添加悬浮容器,完全不会影响JScrollPane的原有布局和滚动功能
- 监听滚动事件实时更新位置,确保搜索框始终停在可视区域的左上角
- 保留了原有的Ctrl+F快捷键逻辑,触发时自动显示并聚焦搜索框
- 后续要加关闭按钮的话,直接在
searchOverlay里添加JButton,绑定searchOverlay.setVisible(false)即可
内容来源于stack exchange
相关产品推荐
相关产品推荐

