目录
- 1. 监听器 - MouseListener(鼠标监听器)
- 2. 监听器 - MouseMotionListener(鼠标运动监听器)
- 3. 事件 - MouseEvent(鼠标事件)
- 4. 示例
- 5. 适配器
1. 监听器 - MouseListener(鼠标监听器)
由 addMouseListener() 添加、 removeMouseListener() 移除此监听器对象;这两个方法都是在 JComponent 类中定义的,因此此监听器适用于 JComponent 及其派生类。
MouseListener 监听器是监听鼠标对组件进行的操作的,共有以下
5
5
5 个方法:
public void mouseClicked(MouseEvent e); //鼠标点击(左、中、右键和单、双击均可触发)
public void mousePressed(MouseEvent e); //鼠标按下(左、中、右键均可触发)
public void mouseReleased(MouseEvent e);//鼠标松开
public void mouseEntered(MouseEvent e); //鼠标浮动
public void mouseExited(MouseEvent e); //鼠标离开
“鼠标浮动”指的是鼠标移动到组件上没有点击的时候,严肃点应该叫“进入”,但我个人感觉“浮动”更有趣。
每当鼠标进行完整的点击时,都会依次触发以下事件:
需要注意的一点是,鼠标事件严格按照上面的流程图触发。
例如,如果在 mousePressed 之后没有触发 mouseReleased(鼠标按下后拖到别处再松开),则也不会有接下来的 mouseClicked;
或是没有 mousePressed 空有 “mouseReleased”(鼠标在别处按下拖到此组件中再松开),则不会触发上述流程图中的任何事件。可以简单地记住:上面流程图中的任何事件都依赖于它左侧的事件。
2. 监听器 - MouseMotionListener(鼠标运动监听器)
此监听器同样适用于 JComponent 及其派生类。
尽管 MouseMotionListener 也是个监听器,但 JavaSwing 并没有提供“MouseMotionEvent”这样的事件,所以 MouseListener 和 MouseMotionListener 都是用的 MouseEvent 事件。
public void mouseMoved(MouseEvent e); //鼠标移动
public void mouseDragged(MouseEvent e); //鼠标拖动
这里的“鼠标移动”是狭义的,也就是说它并不包含“鼠标拖动”,因此这两个事件是“互斥”的。
3. 事件 - MouseEvent(鼠标事件)
链接:JDK 文档
没找到想看内容或没看懂的戳这里:
- JDK17 在线文档(可以把路径中的 17 17 17 改为自己的版本,但需大于等于 11 11 11)。
- 离线文档下载:JDK17 或 JDK8(可以把路径中的 17 17 17 或 8 8 8 改为自己的版本)。
3.1 getXOnScreen()、getYOnScreen()、getLocationOnScreen()
“绝对坐标”指相对于整个电脑屏幕的坐标,从左上角开始。
public int getXOnScreen(); //返回鼠标指针的绝对 X 坐标
public int getYOnScreen(); //返回鼠标指针的绝对 Y 坐标
public Point getLocationOnScreen(); //返回鼠标指针的绝对坐标
其中的 getLocationOnScreen() 方法返回 java.awt.Point 类型,定义如下:
public int x, y;
注意它们既没有被 final 关键字修饰,也没有 synchronized 的 Getter、Setter 方法,因此 Point 类是可变的。(也许是它的设计初衷仅打算在 JavaSwing 中使用,而后者广泛地使用线程封闭技术)
3.1.1 示例:输出鼠标指针的绝对坐标
log.info( e.getLocationOnScreen() );
//Logger 一般会自动调用 Point 类的 toString 方法。
out.printf( "(%d, %d)\n", e.getXOnScreen(), e.getYOnScreen() );
//用 printf 格式化,推荐方式。
3.2 getX()、getY()、getPoint()
public int getX(); //返回鼠标指针的相对 X 坐标
public int getY(); //返回鼠标指针的相对 Y 坐标
public java.awt.Point getPoint(); //返回鼠标指针的相对坐标
用法及示例同上一节。这一节与上一节的区别在于,上一节是绝对坐标(相对于整个屏幕),这一节则是相对于 this 组件的坐标。
3.3 getClickCount()
仅用于点击事件:
public int getClickCount(); //返回点击的次数(不局限于 1~2)
按下不同键不构成双击。例如,按顺序快速点击左键与右键,则两次都返回
1
1
1。
如果是在没有鼠标点击的事件中调用此方法(mouseEntered 等),则返回
0
0
0。
注意,此方法的返回值并不局限于
0
0
0、
1
1
1 和
2
2
2,如果连续点击
3
3
3 次,则 getClickCount() 就为
3
3
3。
3.4 getButton()
用于鼠标按下、松开和点击事件:
public int getButton(); //返回触发事件的鼠标按钮(常量)
//鼠标按钮常量(位于 MouseEvent 中):
public static final int NOBUTTON = 0; //没有按下鼠标按键(例如 mouseEntered 等事件)
public static final int BUTTON1 = 1; //鼠标左键
public static final int BUTTON2 = 2; //鼠标中键
public static final int BUTTON3 = 3; //鼠标右键
鼠标中键其实就是按下滚轮(打游戏的各位肯定都懂)。
如果你的鼠标有三个以上的按钮,则在 MouseEvent 中找不到对应的常量。比如你的鼠标有 5 5 5 个按钮,则可能返回以下数值:
数值 | 对应常量 | 鼠标按键 |
---|---|---|
0 0 0 | j a v a . a w t . e v e n t . M o u s e E v e n t . N O B U T T O N \mathsf{java.awt.event.MouseEvent.NOBUTTON} java.awt.event.MouseEvent.NOBUTTON | (无) |
1 1 1 | j a v a . a w t . e v e n t . M o u s e E v e n t . B U T T O N 1 \mathsf{java.awt.event.MouseEvent.BUTTON1} java.awt.event.MouseEvent.BUTTON1 | 左键 |
2 2 2 | j a v a . a w t . e v e n t . M o u s e E v e n t . B U T T O N 2 \mathsf{java.awt.event.MouseEvent.BUTTON2} java.awt.event.MouseEvent.BUTTON2 | 中键 |
3 3 3 | j a v a . a w t . e v e n t . M o u s e E v e n t . B U T T O N 3 \mathsf{java.awt.event.MouseEvent.BUTTON3} java.awt.event.MouseEvent.BUTTON3 | 右键 |
4 4 4 | (无) | (第 4 4 4 个按键) |
5 5 5 | (无) | (第 5 5 5 个按键) |
3.4.1 示例:检测按下的是什么键
用 If-else 语句:
if (e.getButton() == MonseEvent.BUTTON1)
return "鼠标左键";
else if (e.getButton() == MonseEvent.BUTTON2)
return "鼠标中键";
else if (e.getButton() == MonseEvent.BUTTON3)
return "鼠标右键";
else
return "按键 " + e.getButton();
或是用 Switch 语句:
switch ( e.getButton() ) {
case MouseEvent.BUTTON1:
return "鼠标左键";
case MouseEvent.BUTTON2:
return "鼠标中键";
case MouseEvent.BUTTON3:
return "鼠标右键";
default:
return "按键 " + e.getButton();
}
3.5 getWhen()
顾名思义,作用为返回事件所发生的时间,是 UTC(协调世界时)时间。
public long getWhen();
//返回事件发生时的时间戳(1970 年 1 月 1 日 00:00 至今的毫秒数)
3.5.1 示例:输出事件发生时间(不同格式)
由于 getWhen() 所返回的是 1970 年至今的毫秒数,一般需要经过处理再打印出来,所以平时可以用 java.util.Date 类进行快捷地处理,如果需要还可以用 SimpleDateFormat 类进行可定制的处理:
import java.util.Date;
import java.text.SimpleDateFormat;
log.info( e.getWhen() );
//直接打印未经处理的毫秒数,如:“1675421707077”
//使用 Date 类:
log.info( new Date(e.getWhen()) );
//大多数 Logger 会自动调用 Date 类的 toString() 方法
//如:“Fri Feb 03 18:55:07 CST 2023”
//使用 SimpleDateFormat 进行可定制的格式化:
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
log.info( sdf.format(e.getWhen()) ); //如:“2023-02-03 18:55:07”
//使用 printf() 方法:
out.printf("%tc", e.getWhen()); //如:“周五 2月 03 18:55:07 CST 2023”
如你所见,还可以用 printf() 方法,只要使用时间占位符 %t
。范例中的 c 表示格式化方式,可以替换为其他方式。
3.6 paramString()
返回标识此事件的参数字符串,此方法对于事件日志记录和调试很有用。
示例输出:
MOUSE_PRESSED,(52,93),absolute(59,163),button=1,modifiers=Ctrl+Button1,extModifiers=Ctrl,clickCount=2
输出介绍:
- “MOUSE_PRESSED”,触发的事件类型。范例是在 mousePressed 事件中调用。详情见 3.9 节。
- “(52,93)”,触发事件时鼠标相对于此组件的坐标。格式“(X,Y)”。和上文 3.2 中介绍的方法作用相同。
- “absolute(59,163)”,触发事件时鼠标的绝对坐标,前面跟一个“absolute”单词。与上文 3.1 中介绍的方法作用相同。
- “button=1”,指触发事件所用的鼠标按钮是哪个,示例中为左键。与上文 3.4 介绍的 getButton() 返回的值相同。
- “modifiers=Ctrl+Button1”,指触发事件时的修饰符。与接下来将在 3.7 介绍的方法作用相同。
- “extModifiers=Ctrl”,指触发事件时的修饰符。详情请见 3.8 节。
- “clickCount=2”,指点击次数,与上文 3.3 中介绍的方法作用相同。
……如有遗漏欢迎补充。
3.7 getModifiers()、getMouseModifiersText()
其实 getModifiers() 方法已被打上 Deprecated 注解,不提倡使用。更提倡用下一节介绍的 getModifiersEx() 方法,但上文有所提及,所以简单介绍一下。
getModifiers() 方法返回事件的修饰符,常量在 InputEvent 中定义,用位或方式结合。如我按住 Ctrl 键用左键触发事件,返回的修饰符如下:
e.getModifiers() == InputEvent.CTRL_MASK | InputEvent.BUTTON1_MASK;
之所以被废除,个人猜测是因为有时有些奇怪的 BUG(也许和跨平台有关):
//如我按下鼠标中键,它返回这个:
InputEvent.ALT_MASK | InputEvent.BUTTON2_MASK //可实际上我并没有按 Alt
再说说静态方法 getMouseModifiersText() :
public static String getMouseModifiersText(int modifiers);
//将 getModifiers() 返回的修饰符翻译成字符串
//比如这个:
InputEvent.CTRL_MASK | InputEvent.BUTTON1_MASK
//它翻译为:
"Ctrl+Button1" //(实际上没有双引号)
3.8 getModifiersEx()、getModifiersExText()
静态方法 getModifiersExText() 在 InputEvent 类中定义。
上一节中 getModifiers() 和 getMouseModifiersText() 的合适替代品,更提倡用这个。
上一节所使用的常量为 XXX_MASK,这节所介绍的方法所使用的常量为 XXX_DOWN_MASK。
getModifiersEx() 同样有一些奇怪的 BUG(特性?),我把它分为 2 2 2 种情况(在我的台式机上测试):
一、BUG(特性?) 发作,不仅是上一节中介绍的凭空多出按键,而且附赠忽略鼠标按钮,还是上一节中的范例:
//我按下鼠标中键:
InputEvent.BUTTON2_DOWN_MASK //我认为它应该返回这个
InputEvent.ALT_DOWN_MASK //结果事实让我震惊了
//(它先生成了一个 Alt,然后又把 Button2 给忽略掉了)
二、奇奇怪怪的修饰符也不加了,按钮也不忽略了,按下哪个就返回哪个。总之就突然恢复正常了,如:
//我按下 Ctrl + Alt + 左键,它正常返回:
InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK | InputEvent.BUTTON1_DOWN_MASK
就挺玄学的,我在不同事件中测试,结果如下:
监听器 | 事件 | 状态 |
---|---|---|
MouseListener | mousePressed | 正常返回 |
MouseListener | mouseReleased | 离谱特性 |
MouseListener | mouseClicked | 离谱特性 |
MouseMotionListener | mouseDragged | 正常返回 |
其他事件则没有监听鼠标点击。
至于 getModifiersExText() 则和上一节中的 getMouseModifiers() 作用相同。但 getModifiersExText() 用的是 XXX_DOWN_MASK,getModifiersText() 用的 XXX_MASK,所以不能混起来用。上一节中的两个方法配套使用,这一节的两个方法也只能配套使用。
3.9 getID()
返回触发的事件(int 常量),用于编写静态方法(工具)时使用,可用的常量如下:
常量名称 | 事件 | 值 |
---|---|---|
MOUSE_PRESSED | mousePressed(鼠标按下) | 501 |
MOUSE_RELEASED | mouseReleased(鼠标松开) | 502 |
MOUSE_CLICKED | mouseClicked(鼠标点击) | 500 |
MOUSE_ENTERED | mouseEntered(鼠标浮动) | 504 |
MOUSE_EXITED | mouseExited(鼠标离开) | 505 |
MOUSE_MOVED | mouseMoved(鼠标移动) | 503 |
MOUSE_DRAGGED | mouseDragged(鼠标拖动) | 506 |
还有一个较为特殊,这个是属于 MouseWheelListener(鼠标滚轮监听器)的:
常量名称 | 事件 | 值 |
---|---|---|
MOUSE_WHEEL | mouseWheelMoved(鼠标滚轮滚动) | 507 |
3.9.1 示例:id --> 字符串
由于常量的值是 int
类型,要转化为字符串,可以用 Switch 或 If-else 暴力检测,也可以用反射获取常量名。反正 Switch、If-else 就是疯狂复制粘贴,这里就贴一个简短的反射代码吧:
public static String getEventName(final int id)
throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
Class<?> c = MouseWheelEvent.class; //MouseWheelEvent 包含上表中所有常量
Field first = c.getField("MOUSE_FIRST"), last = c.getField("MOUSE_LAST"); //跟 MouseEvent 类库的一些设计有关
if (first.getInt(null) <= id && id <= last.getInt(null)) //常量是否存在
for (Field field : c.getFields())
if (field.get(null) instanceof Integer
&& field.getInt(null) == id //如果检测到了 int 常量且值相等
&& !field.equals(first) && !field.equals(last))
return field.getName();
throw new IllegalArgumentException("The id doesn't exist.");
}
/****** 关于上面的 first 和 last: ******/
//In MouseEvent.class:
public static final int MOUSE_FIRST = 501; //注意,和 MOUSE_PRESSED 的值相等
public static final int MOUSE_LAST = 507; //和 MOUSE_WHEEL_MOVED 的值相等
3.10 getSource()
此方法返回触发事件的对象,返回类型为 Object,所以需要进行显式的向下转型。
public Object getSource(); //返回触发事件的对象
常用于不同按钮添加同一个监听器对象,还有自己的静态方法(工具)中使用。
3.10.1 示例:输出点击的按钮名称
就是上面说的不同按钮添加同一个监听器对象:
public static void main(String[] args) {
MouseListener ml = new MouseAdapter() {
//由于只实现了一个 mouseClicked 方法,使用将在第 5.1 节中介绍的鼠标适配器
@Override
public void mouseClicked(MouseEvent e) {
JButton source = (JButton) e.getSource(); //获取触发的按钮对象
System.out.printf(
"Clicked button: \"%s\".\n", source.getText() ); //输出按钮名
}
};
//测试代码:
JButton button1 = new JButton("Button-1"),
button2 = new JButton("Button-2");
button1.addMouseListener(ml); //添加同一个监听器
button2.addMouseListener(ml);
JFrame frame = new JFrame("示例-3.10.1");
frame.setSize(240, 180);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridLayout(1, 2)); //这里用网格布局管理器
frame.add(button1);
frame.add(button2);
frame.setVisible(true);
}
4. 示例
4.1 实时显示鼠标坐标
显示鼠标相对于 JFrame 窗口的坐标。
这是 MouseMotionListener 监听器的示例:
public static void main(String[] args) {
JFrame frame = new JFrame("示例-4.1");
JLabel label = new JLabel(); //显示鼠标坐标
frame.addMouseMotionListener(new MouseMotionListener() {
public void mouseMoved(MouseEvent e) {
label.setText(String.format(
"X=%d, Y=%d", e.getX(), e.getY()
)); }
public void mouseDragged(MouseEvent e) {
label.setText(String.format(
"X=%d, Y=%d", e.getX(), e.getY()
)); }
});
//测试代码:
frame.setSize(240, 180);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(label);
frame.setVisible(true);
}
4.2 生成简单日志字符串
没有太大实用意义,但运用了第 3 3 3 节中大部分方法。
/**
* 根据参数生成一个简单的代表日志的字符串。
* 字符串格式:[时间]_触发事件,鼠标坐标,绝对坐标,鼠标按钮,修饰符,修饰符新,点击次数。
*
* @param e 鼠标事件的对象,包含很多重要信息
* @return 生成的一行代表日志的字符串
*/
private static String log(MouseEvent e) throws IllegalAccessException {
return String.format(
"[%s]_id=%s, coordinate=[%d,%d], absolute[%d,%d], button=%d, modifiers=%s, modifiersEx=%s, clickCount=%d",
new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss").format(e.getWhen()), //事件发生时间
e.getID(), //发生的事件
e.getX(), e.getY(), //事件发生时鼠标的相对坐标
e.getXOnScreen(), e.getYOnScreen(), //鼠标的绝对坐标
e.getButton(), //按下的鼠标按钮
MouseEvent.getMouseModifiersText(e.getModifiers()), //修饰符
InputEvent.getModifiersExText(e.getModifiersEx()), //新修饰符
e.getClickCount() //点击次数
);
}
现在可以把此方法加入到监听器中试一试了:
public static void main(String[] args) {
JButton button = new JButton("Button");
button.addMouseListener(new MouseListener() {
public void mousePressed(MouseEvent e) { System.out.println(log(e)); }
public void mouseReleased(MouseEvent e) { System.out.println(log(e)); }
public void mouseClicked(MouseEvent e) { System.out.println(log(e)); }
public void mouseEntered(MouseEvent e) { System.out.println(log(e)); }
public void mouseExited(MouseEvent e) { System.out.println(log(e)); }
});
button.addMouseMotionListener(new MouseMotionListener() {
public void mouseMoved (MouseEvent e) { System.out.println(log(e)); }
public void mouseExited (MouseEvent e) { System.out.println(log(e)); }
});
JFrame frame = new JFrame("示例-4.2");
frame.setSize(240, 180);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(button);
frame.setVisible(true);
}
4.3 鼠标拖动 JLabel
利用 MouseMotionListener,在鼠标拖动时让 JLabel 的坐标保持和鼠标偏移量同步,效果很不错。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
@SuppressWarnings("serial")
public class DemoFrame extends JFrame {
private Point mousePos; //用于计算偏移量(now - mousePos)
private JLabel label = new JLabel("JLabel", JLabel.CENTER);
public DemoFrame(String title, int width, int height) {
this.setTitle(title);
this.setSize(width, height);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setLayout(null); //默认布局管理器,直接控制坐标
this.add(label);
label.setSize(width / 8, height / 8);
label.setBorder(BorderFactory.createLineBorder(Color.BLACK)); //设置黑色边框,便于拖动观察
label.addMouseMotionListener(new MouseMotionListener() {
@Override
public void mouseMoved(MouseEvent e) //实时记录鼠标坐标
{ mousePos = e.getLocationOnScreen(); }
@Override
public void mouseDragged(MouseEvent e) {
Point offset = new Point(e.getXOnScreen() - mousePos.x, e.getYOnScreen() - mousePos.y); //计算鼠标坐标偏移量
label.setBounds(label.getX() + offset.x, label.getY() + offset.y, label.getWidth(), label.getHeight()); //移动 JLabel
mousePos = e.getLocationOnScreen(); //更新鼠标坐标
}
});
}
public static void main(String[] arg)
{ new DemoFrame("示例-4.3", 480, 360).setVisible(true); }
}
///:~
5. 适配器
MouseListener、MouseMotionListener 监听器的适配器都是 MouseAdapter,它同时也是 MouseWheelListener(鼠标滚轮监听器)的适配器(它同时导出自这 3 3 3 个监听器接口)。