Swing 文本组件没有带有剪切/复制/粘贴/等的上下文菜单。我想添加一个,以便它的行为更流畅,就像本机应用程序一样。我已经为此编写了一个菜单,它工作正常。我使用以下方法将其添加到每个文本框中:
someTextBox.setComponentPopupMenu(TextContextMenu.INSTANCE);
问题是,到处添加这个很烦人。其次,如果我在某处忘记了文本框,则应用程序不一致。第三,我无法为无法控制创建代码的文本框添加它,例如来自JOptionPane.showInputDialog
或JFileChooser
对话框的文本框。
有没有办法覆盖JTextComponent
应用程序范围的默认上下文菜单?我知道这将是一种幽灵般的远距离行动形式,但我对此没意见。也欢迎对菜单本身发表评论。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.JTextComponent;
public class TextContextMenu extends JPopupMenu implements ActionListener {
public static final TextContextMenu INSTANCE = new TextContextMenu();
private final JMenuItem itemCut;
private final JMenuItem itemCopy;
private final JMenuItem itemPaste;
private final JMenuItem itemDelete;
private final JMenuItem itemSelectAll;
private TextContextMenu() {
itemCut = newItem("Cut", 'T');
itemCopy = newItem("Copy", 'C');
itemPaste = newItem("Paste", 'P');
itemDelete = newItem("Delete", 'D');
addSeparator();
itemSelectAll = newItem("Select All", 'A');
}
private JMenuItem newItem(String text, char mnemonic) {
JMenuItem item = new JMenuItem(text, mnemonic);
item.addActionListener(this);
return add(item);
}
@Override
public void show(Component invoker, int x, int y) {
JTextComponent tc = (JTextComponent)invoker;
boolean changeable = tc.isEditable() && tc.isEnabled();
itemCut.setVisible(changeable);
itemPaste.setVisible(changeable);
itemDelete.setVisible(changeable);
super.show(invoker, x, y);
}
@Override
public void actionPerformed(ActionEvent e) {
JTextComponent tc = (JTextComponent)getInvoker();
tc.requestFocus();
boolean haveSelection = tc.getSelectionStart() != tc.getSelectionEnd();
if (e.getSource() == itemCut) {
if (!haveSelection) tc.selectAll();
tc.cut();
} else if (e.getSource() == itemCopy) {
if (!haveSelection) tc.selectAll();
tc.copy();
} else if (e.getSource() == itemPaste) {
tc.paste();
} else if (e.getSource() == itemDelete) {
if (!haveSelection) tc.selectAll();
tc.replaceSelection("");
} else if (e.getSource() == itemSelectAll) {
tc.selectAll();
}
}
}
我已经弄清楚了如何在应用程序范围内执行此操作,包括在 JFileChoosers 和 showInputDialog 等上!我不确定它有多理智和适当,但它有效。它(ab)使用可插拔的外观和感觉系统。JTextComponent 在其构造函数期间调用 updateUI,这提供了在 L&F 被要求提供其 UI 委托时调用 setComponentPopupMenu 的机会。
如果更改已打开的窗口的外观,将再次调用每个组件的 updateUI 方法。为了防止再次设置默认菜单,下面的代码存储文本框是否已初始化的属性,该属性是否已使用 JComponent.putClientProperty。
实际效果是它的行为就像每个 JTextComponent 本身在其构造函数期间只调用 setComponentPopupMenu 一次一样。因此,对于不需要菜单或想要不同菜单的特殊文本框,很容易覆盖它:只需再次调用 setComponentPopupMenu。例如,来自文本字段子类构造函数或来自创建窗口及其小部件的调用代码。
这是在应用程序启动时运行一次的代码:
UIManager.addAuxiliaryLookAndFeel(new LookAndFeel() {
private final UIDefaults defaults = new UIDefaults() {
@Override
public javax.swing.plaf.ComponentUI getUI(JComponent c) {
if (c instanceof javax.swing.text.JTextComponent) {
if (c.getClientProperty(this) == null) {
c.setComponentPopupMenu(TextContextMenu.INSTANCE);
c.putClientProperty(this, Boolean.TRUE);
}
}
return null;
}
};
@Override public UIDefaults getDefaults() { return defaults; };
@Override public String getID() { return "TextContextMenu"; }
@Override public String getName() { return getID(); }
@Override public String getDescription() { return getID(); }
@Override public boolean isNativeLookAndFeel() { return false; }
@Override public boolean isSupportedLookAndFeel() { return true; }
});
您有两个选择:
-
如果您的应用程序具有自己的自定义外观,则可以只需在文本UI
installUI(JComponent c)
方法中执行此操作即可。 -
如果你没有自己的L&F,你必须遍历组件树,找到 JTextComponent 的所有实例,并添加您的上下文菜单。我将使其成为静态帮助程序方法。对于对话框和框架,您需要一个基本类,它重新定义了要添加的方法 setVisible()所有文本组件的上下文菜单。您的所有自定义对话框或框架必须扩展基本对话框/框架。
由于我与JavaHelp的冲突,我需要不同的解决方案。 我想出的是选择2的变体。在第二个答案中,我在实例化类而不是调用setVisible()
时完成工作。
public static void installDefaultTextContextMenus( Container aContainer )
{
if ( aContainer != null )
{
if ( aContainer instanceof JFrame )
{
aContainer = ((JFrame)aContainer).getContentPane();
}
else if ( aContainer instanceof JDialog )
{
aContainer = ((JDialog)aContainer).getContentPane();
}
Component[] lComponents = aContainer.getComponents();
for ( int lCompNum = 0; lCompNum < lComponents.length; ++lCompNum )
{
lComponents[ lCompNum ].getClass();
if ( ( lComponents[ lCompNum ] instanceof JPanel ) ||
( lComponents[ lCompNum ] instanceof JInternalFrame ) ||
( lComponents[ lCompNum ] instanceof JScrollPane ) ||
( lComponents[ lCompNum ] instanceof JSplitPane ) ||
( lComponents[ lCompNum ] instanceof JTabbedPane ) ||
( lComponents[ lCompNum ] instanceof Panel ) ||
( lComponents[ lCompNum ] instanceof ScrollPane ) ||
( lComponents[ lCompNum ] instanceof JViewport ) ||
( lComponents[ lCompNum ] instanceof JFrame ) ||
( lComponents[ lCompNum ] instanceof JDialog ) )
{
installDefaultTextContextMenus( (Container)lComponents[ lCompNum ] );
}
else if ( lComponents[ lCompNum ] instanceof JTextComponent )
{
((JTextComponent)lComponents[ lCompNum ]).setComponentPopupMenu( TextContextMenu.INSTANCE );
}
else if ( lComponents[ lCompNum ] instanceof JComboBox )
{
Component lEditorComp = ((JComboBox)lComponents[ lCompNum ]).getEditor().getEditorComponent();
if ( lEditorComp instanceof JTextComponent )
{
((JTextComponent)lEditorComp).setComponentPopupMenu( TextContextMenu.INSTANCE );
}
}
}
}
}
该方法几乎将遍历任何 Swing 组件树。 幸运的是,我已经有一个常用方法来装饰我的所有应用程序的顶级 JFrames,我能够向该方法添加调用。 但是,我确实必须为未由顶级菜单处理的帧或对话框的每个实例化添加对此方法的调用。