Swing: Context Menu for TextComponents 文本控件右键菜单

 

Java编写的GUI程序里面,AWT,SWT,QT Jambi都是基于C++ Dll绘制界面,也就是所谓的重量级界面,而Swing是通过Java2D绘制出来的,完全基于java实现,是轻量级界面。Java的这个轻量级界面有对应于每个操作系统平台的Look and Feel,可以把程序装饰得跟本机程序一样。拿Windows里面的Look and Feel来说,几近以假乱真的程度,不过我还是可以通过一点来很容易的区分Java程序,那就是文本框对右键是没有任何反应的,普通的文本框,右键的时候都会有一个菜单,Cut,Copy,Paste,Select All,而Java的文本框?Nothing。这个缺陷其实不算大问题,Ctrl+C,Ctrl+v。。。就可以实现上面的这些功能,不过,想当初我学电脑的时候,也有很长很长一段时间不知道CTRL+C这些快捷键的,所以怎么说没有右键菜单都让人会觉得Swing在UI这块还是不够Professional,呵呵。 还记得去年实习,在公司做Swing的时候,就有这个想法,那个时候也花了大半天的时间去搜索现成的东东,不过没有找到,其实现在想想这个东西还是跟搜索的关键字有关,从技术的角度来讲,用中文搜跟用英文搜差别太大了,我今天又突然想起这个问题,用英文搜索了一下,结果第一页就找到答案了,呵呵,看来boldtech没白来,起码英文用得多一点了,对学习技术有帮助。 我首先搜索到的一篇文章,写文章的人也是因为对这个问题诟病很久了,它在文章里面提到要实现其实也不难,两种理所当然的方式,一是扩展这些文本组件,二是添加全局的MouseListener,实现Popup,不过这两种方式都被他否定了,因为这两种方式的代码量都比较多,前者需要扩展多种文本组件,TextField,TextArea之类,而且需要将之前的TextField,TextArea做一遍替换,后者则是针对每个Frame都要添加一遍Listener。然后作者提到了一种方案,通过自定义EventQueue来实现,主要的代码如下:

// @author Santhosh Kumar T - santhosh@in.fiorano.com 
public class MyEventQueue extends EventQueue{
protected void dispatchEvent(AWTEvent event){
super.dispatchEvent(event);

// interested only in mouseevents 
if(!(event instanceof MouseEvent))
return;

MouseEvent me = (MouseEvent)event;

// interested only in popuptriggers 
if(!me.isPopupTrigger())
return;

// me.getComponent(...) retunrs the heavy weight component on which event occured 
Component comp = SwingUtilities.getDeepestComponentAt(me.getComponent(), me.getX(), me.getY());

// interested only in textcomponents 
if(!(comp instanceof JTextComponent))
return;

// no popup shown by user code 
if(MenuSelectionManager.defaultManager().getSelectedPath().length>0)
return;

// create popup menu and show 
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);
}
}
很简单?的确,只是在MouseEvent里面对Event进行过滤,对右键菜单,并且是来自文本组件的右键菜单添加Popup菜单,
这种方案的确比较简单,最终只需要一行代码,
Toolkit.getDefaultToolkit().getSystemEventQueue().push(new MyEventQueue()); 将这个EventQueue加到系统的EventQueue
中就Ok了,不过这个贴发出来以后就有很多反对的声音,主要是这种对全局右键事件的监听可能会打破原有的事件监听体系。
所以这个方案也只是 看上去很美,不过后面有人提到Swinglab的SwingX项目里面通过在look And Feel 里面添加 auxiliary LF
来实现,刚好我在IPSeeker里面 用到了SwingX项目,结果通过一行代码也实现了右键菜单,
UIManager.addAuxiliaryLookAndFeel(new ContextMenuAuxLF());
其中ContextMenuAuxLF位于SwingX中,org.jdesktop.swingx.plaf.ContextMenuAuxLF,不过这个文件的代码我没怎么看懂,大概就是
通过在重新TextUI,在其中加入每个textComponent对应的Action所生成的Popup。代码都不多,看似很简洁,很美,
IPSeeker中右键菜单截图:
  • 空白文本框中的右键菜单
  • 选中文字后右键菜单
  • TextArea中的右键菜单

SwingX 实现分析:
SwingX是开源的,这点很不错,直接看源代码就可以知道是怎么实现的了,我感觉编程不是一个技术活,经过一段时间的
培训,人人都可以编程,关键要是编出很好看的代码,那才叫牛逼,就像写文章,每个人都会写,但只有少数人能够成为
作家。扯远了,前面也提到SwingX的实现方式主要是通过
UIManager.addAuxiliaryLookAndFeel(new ContextMenuAuxLF());给当前的UIManager添加一个辅助的LookAndFeel,这个
特性很重要,很多时候重新一个LookAndFeel所需要的工作量是相当巨大而且可能是没必要的,这个时候通过调整部分UI,
作为一个辅助的LookAndFeel来添加就显得尤为重要,说白了这个辅助的LookAndFeel会覆盖当前LookAndFeel当中已有的
部分,而辅助的LookAndFeel没有实现的那些部分让又原有的LookAndFeel负责。
  • 下面介绍下ContextMenuAuxLF(注释部分用绿色标出,下同):
/*
* $Id: ContextMenuAuxLF.java,v 1.5 2006/03/30 10:19:12 kleopatra Exp $
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
package org.jdesktop.swingx.plaf;

import javax.swing.LookAndFeel;
import javax.swing.UIDefaults;

/**
* Support for context dependent popup menus.
*
* It's meant to be used as a auxiliary LF on top of main LF:
*
*  
*  
*  UIManager.addAuxiliaryLookAndFeel(new ContextMenuAuxLF());
*  
*  
* * There are core-issues involved, which might or might not * impair its usefulness, for details please see a thread in * the SwingLabs forum:

* * * Experimental: default context menus for textcomponents/scrollbars * * * * @author Jeanette Winzenburg */ public class ContextMenuAuxLF extends LookAndFeel { private UIDefaults myDefaults; public String getName() { return "ContextMenuAuxLF"; } public String getID() { return getName(); } public String getDescription() { return "Auxiliary LF to Support Context Dependent Popups"; } public boolean isNativeLookAndFeel() { return false; } public boolean isSupportedLookAndFeel() { return true; } public UIDefaults getDefaults() { if (myDefaults == null) { initDefaults(); } return myDefaults; } private void initDefaults() { myDefaults = new MyUIDefaults(); Object[] mydefaults = { "TextFieldUI", "org.jdesktop.swingx.plaf.ContextMenuAuxTextUI", "EditorPaneUI", "org.jdesktop.swingx.plaf.ContextMenuAuxTextUI", "PasswordFieldUI", "org.jdesktop.swingx.plaf.ContextMenuAuxTextUI", "TextAreaUI", "org.jdesktop.swingx.plaf.ContextMenuAuxTextUI", "TextPaneUI", "org.jdesktop.swingx.plaf.ContextMenuAuxTextUI", "ScrollBarUI", "org.jdesktop.swingx.plaf.ContextMenuAuxScrollBarUI", }; myDefaults.putDefaults(mydefaults); /*关键在于这里,UIDefaults其实是一个HashTable,通过观察你也许就会发现,这里用来初始化UIDefaults的 是一些键值对,键是UI,Name是这个UI对应的实现。这里把TextField,EditorPane,PasswordField,TextAreaUI都初始化 "org.jdesktop.swingx.plaf.ContextMenuAuxTextUI"实现 */ } /** * UIDefaults without error msg. * */ private static class MyUIDefaults extends UIDefaults { /** * Overridden to do nothing. * There will be many errors because this is incomplete as * of component types by design * */ @Override protected void getUIError(String msg) { } } }

  • ContextMenuAuxLF引用到ContextMenuAuxTextUI
/*
* $Id: ContextMenuAuxTextUI.java,v 1.5 2005/10/24 13:20:45 kleopatra Exp $
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
package org.jdesktop.swingx.plaf;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseListener;

import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.TextUI;
import javax.swing.text.BadLocationException;
import javax.swing.text.EditorKit;
import javax.swing.text.JTextComponent;
import javax.swing.text.View;
import javax.swing.text.Position.Bias;

/**
* @author Jeanette Winzenburg
*/
public class ContextMenuAuxTextUI extends TextUI {

 private MouseListener mouseHandler;

 public static ComponentUI createUI(JComponent c) {
     return new ContextMenuAuxTextUI(); //单态模式
 }

 public void installUI(JComponent comp) {
     comp.addMouseListener(getMouseListener());//在InstallUI的时候给组件加上鼠标侦听
 }

 public void uninstallUI(JComponent comp) {
     comp.removeMouseListener(getMouseListener());//在卸载UI的时候移除监听
 }



 private MouseListener getMouseListener() {
     if (mouseHandler == null) {
         mouseHandler = createPopupHandler();//创建新的PopupHandler,处理右键弹出事件
     }
     return mouseHandler;
 }

 private MouseListener createPopupHandler() {
     return new ContextMenuHandler(createContextSource());
 }

 private ContextMenuSource createContextSource() {
     return new TextContextMenuSource();
 }

 public void update(Graphics g, JComponent c) {
 }

 public Rectangle modelToView(JTextComponent t, int pos)
         throws BadLocationException {
     // TODO Auto-generated method stub
     return null;
 }

 public Rectangle modelToView(JTextComponent t, int pos, Bias bias)
         throws BadLocationException {
     // TODO Auto-generated method stub
     return null;
 }

 public int viewToModel(JTextComponent t, Point pt) {
     // TODO Auto-generated method stub
     return 0;
 }

 public int viewToModel(JTextComponent t, Point pt, Bias[] biasReturn) {
     // TODO Auto-generated method stub
     return 0;
 }

 public int getNextVisualPositionFrom(JTextComponent t, int pos, Bias b,
         int direction, Bias[] biasRet) throws BadLocationException {
     // TODO Auto-generated method stub
     return 0;
 }

 public void damageRange(JTextComponent t, int p0, int p1) {
     // TODO Auto-generated method stub

 }

 public void damageRange(JTextComponent t, int p0, int p1, Bias firstBias,
         Bias secondBias) {
     // TODO Auto-generated method stub

 }

 public EditorKit getEditorKit(JTextComponent t) {
     // TODO Auto-generated method stub
     return null;
 }

 public View getRootView(JTextComponent t) {
     // TODO Auto-generated method stub
     return null;
 }

}

  • ContextMenuAuxTextUI中添加的ContextMenuHandler
/* * $Id: ContextMenuHandler.java,v 1.5 2005/10/24 13:20:46 kleopatra Exp $ * * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jdesktop.swingx.plaf; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.ActionMap; import javax.swing.JComponent; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; /** * Responsible for showing the default PopupMenu. * * @author Jeanette Winzenburg */ public class ContextMenuHandler extends MouseAdapter { private ActionMap actionMap; private ContextMenuSource contextMenuSource; private JPopupMenu popup; /** * creates a context handler for TextContextMenuSource. * */ public ContextMenuHandler() { this(null); } /** * creates a context handler for the given ContextMenuSource. * Defaults to TextContextMenuSource if source == null. * * @param source */ public ContextMenuHandler(ContextMenuSource source) { contextMenuSource = source; } // --------------------- MouseListener public void mousePressed(MouseEvent e) { maybeShowContext(e);//在鼠标按下时检测是否是除非弹出事件 } public void mouseReleased(MouseEvent e) { maybeShowContext(e);//在鼠标释放时检测是否是除非弹出事件 } private void maybeShowContext(final MouseEvent e) { if (!e.isPopupTrigger() || !e.getComponent().isEnabled())//判断条件:该事件是弹出事件,触发该事件的组件没被禁用 return; if (e.getComponent().hasFocus()) { showContextPopup(e);//显示Popup } else { ((JComponent) e.getComponent()).grabFocus(); SwingUtilities.invokeLater(new Runnable() { public void run() { showContextPopup(e); } }); } } private void showContextPopup(MouseEvent e) { showContextPopup((JComponent) e.getComponent(), e.getX(), e .getY()); } private void showContextPopup(JComponent component, int x, int y) { JPopupMenu popup = getPopupMenu(component, true); popup.show(component, x, y); } /** * @param component * @return */ private JPopupMenu getPopupMenu(JComponent component, boolean synchEnabled) { if (popup == null) { popup = new JPopupMenu(); ActionMap map = getActionMap(component);//得到组件的ActionMap String[] keys = getContextMenuSource().getKeys(); for (int i = 0; i < style="color: rgb(102, 255, 153);">//添加对于的Action到Popup中 } else { popup.addSeparator(); } } } if (synchEnabled) { getContextMenuSource().updateActionEnabled(component, actionMap);//检查哪些Action可用 } return popup; } private ActionMap getActionMap(JComponent component) { if (actionMap == null) { actionMap = getContextMenuSource().createActionMap(component); } else { // todo: replace actions with components? } return actionMap; } private ContextMenuSource getContextMenuSource() { if (contextMenuSource == null) { contextMenuSource = new TextContextMenuSource(); } return contextMenuSource; } }
  • ContextMenuHandler用到的TextContextMenuSource
/* * $Id: TextContextMenuSource.java,v 1.5 2006/05/14 08:19:45 dmouse Exp $ * * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jdesktop.swingx.plaf; import java.util.Map; import javax.swing.ActionMap; import javax.swing.JComponent; import javax.swing.JPasswordField; import javax.swing.text.DefaultEditorKit; import javax.swing.text.JTextComponent; /** * @author Jeanette Winzenburg */ public class TextContextMenuSource extends ContextMenuSource{ String UNDO = "Undo"; String CUT = "Cut"; String COPY = "Copy"; String PASTE = "Paste"; String DELETE = "Delete"; String SELECT_ALL = "Select All"; String[] keys = { DefaultEditorKit.cutAction, DefaultEditorKit.copyAction, DefaultEditorKit.pasteAction, DefaultEditorKit.deleteNextCharAction, null, // separator DefaultEditorKit.selectAllAction };//文本组件的Action Name,ActionMap的Key,Null用来表示一个分隔符号 String[] defaultValues = { CUT, COPY, PASTE, DELETE, null, SELECT_ALL, }; public String[] getKeys() { return keys; } public void updateActionEnabled(JComponent component, ActionMap map) { if (!(component instanceof JTextComponent)) return; JTextComponent textComponent = (JTextComponent) component; boolean selectedText = textComponent.getSelectionEnd() - textComponent.getSelectionStart() > 0;//是否有文本被选中,条件1 boolean containsText = textComponent.getDocument().getLength() > 0;//是否有文本,条件2 boolean editable = textComponent.isEditable();//可否编辑,条件3 boolean copyProtected = (textComponent instanceof JPasswordField);//如果是PasswordField,则不能Copy,条件4 boolean dataOnClipboard = textComponent.getToolkit() .getSystemClipboard().getContents(null) != null;//系统剪切板上是否有数据,条件5 map.get(DefaultEditorKit.cutAction).setEnabled( !copyProtected && editable && selectedText);//Cut可用的条件是 4,3,1均成立 map.get(DefaultEditorKit.copyAction).setEnabled( !copyProtected && selectedText);//Copy可用的条件是 4,1成立 map.get(DefaultEditorKit.pasteAction).setEnabled( editable && dataOnClipboard);//Paste可用的条件是3,5成立 map.get(DefaultEditorKit.deleteNextCharAction).setEnabled( editable && selectedText);//Delete成立的条件是3,1成立 map.get(DefaultEditorKit.selectAllAction).setEnabled(containsText);//selectedAll需要条件2 }
引用来自:

Swing: Context Menu for TextComponents

SwingLabs

0 评论: