在許多軟體應用中,文本組件(如 JTextField、JTextArea、JEditorPane、JTextPane 等)都需要提供統一的右鍵選單功能,包含剪下、複製、貼上、刪除、全選等操作。若為每個組件單獨編寫繼承類或添加滑鼠監聽器,不僅代碼冗餘,還難以維護。本文介紹一種通過攔截事件隊列實現全域統一右鍵選單的優雅方案。
Swing 所有事件均進入 java.awt.EventQueue 隊列,經由 dispatchEvent 方法分發。我們可繼承 EventQueue 並重寫 dispatchEvent 方法,攔截所有滑鼠事件,針對觸發彈出選單的動作(isPopupTrigger()),動態為所有繼承自 JTextComponent 的組件構建統一選單。
package org.kissjava.swingx.core;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.JPopupMenu;
import javax.swing.MenuSelectionManager;
import javax.swing.SwingUtilities;
import javax.swing.text.JTextComponent;
public class KJEventQueue extends EventQueue {
@Override
protected void dispatchEvent(AWTEvent event) {
super.dispatchEvent(event);
// 僅處理滑鼠事件
if (!(event instanceof MouseEvent)) return;
MouseEvent me = (MouseEvent) event;
// 僅處理彈出選單觸發事件
if (!me.isPopupTrigger()) return;
// 獲取最深層組件
Component comp = SwingUtilities.getDeepestComponentAt(me.getComponent(), me.getX(), me.getY());
// 僅處理文本組件
if (!(comp instanceof JTextComponent)) return;
// 若已有選單顯示則跳過
if (MenuSelectionManager.defaultManager().getSelectedPath().length > 0) return;
// 建立統一彈出選單
JTextComponent tc = (JTextComponent) comp;
JPopupMenu menu = new JPopupMenu();
menu.add(new CutAction(tc));
menu.add(new CopyAction(tc));
menu.add(new PasteAction(tc));
menu.add(new DeleteAction(tc));
menu.addSeparator();
menu.add(new SelectAllAction(tc));
Point pt = SwingUtilities.convertPoint(me.getComponent(), me.getPoint(), tc);
menu.show(tc, pt.x, pt.y);
}
// 剪下動作
class CutAction extends AbstractAction {
JTextComponent comp;
public CutAction(JTextComponent comp) { super("Cut"); this.comp = comp; }
public void actionPerformed(ActionEvent e) { comp.cut(); }
public boolean isEnabled() {
return comp.isEditable() && comp.isEnabled() && comp.getSelectedText() != null;
}
}
// 貼上動作
class PasteAction extends AbstractAction {
JTextComponent comp;
public PasteAction(JTextComponent comp) { super("Paste"); this.comp = comp; }
public void actionPerformed(ActionEvent e) { comp.paste(); }
public boolean isEnabled() {
if (comp.isEditable() && comp.isEnabled()) {
Transferable contents = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(this);
return contents.isDataFlavorSupported(DataFlavor.stringFlavor);
}
return false;
}
}
// 刪除動作
class DeleteAction extends AbstractAction {
JTextComponent comp;
public DeleteAction(JTextComponent comp) { super("Delete"); this.comp = comp; }
public void actionPerformed(ActionEvent e) { comp.replaceSelection(null); }
public boolean isEnabled() {
return comp.isEditable() && comp.isEnabled() && comp.getSelectedText() != null;
}
}
// 複製動作
class CopyAction extends AbstractAction {
JTextComponent comp;
public CopyAction(JTextComponent comp) { super("Copy"); this.comp = comp; }
public void actionPerformed(ActionEvent e) { comp.copy(); }
public boolean isEnabled() {
return comp.isEnabled() && comp.getSelectedText() != null;
}
}
// 全選動作
class SelectAllAction extends AbstractAction {
JTextComponent comp;
public SelectAllAction(JTextComponent comp) { super("Select All"); this.comp = comp; }
public void actionPerformed(ActionEvent e) { comp.selectAll(); }
public boolean isEnabled() {
return comp.isEnabled() && comp.getText().length() > 0;
}
}
}
在主程式入口(main 方法)中加入以下代碼即可啟用:
public static void main(String[] args) {
Toolkit.getDefaultToolkit().getSystemEventQueue().push(new KJEventQueue());
SwingUtilities.invokeLater(() -> new KJEventQueueDemo());
}


JTextComponent 的組件