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中右键菜单截图:
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
}
引用来自:
SwingLabs