1. [Java] MouseListener、MouseMotionListener 监听器与 MouseEvent 事件

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);	//鼠标离开

“鼠标浮动”指的是鼠标移动到组件上没有点击的时候,严肃点应该叫“进入”,但我个人感觉“浮动”更有趣。

每当鼠标进行完整的点击时,都会依次触发以下事件:

鼠标按下
鼠标松开
NULL
mousePressed()
mouseReleased()
mouseClicked()

需要注意的一点是,鼠标事件严格按照上面的流程图触发

例如,如果在 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
  • 离线文档下载:JDK17JDK8(可以把路径中的 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

输出介绍:

  1. “MOUSE_PRESSED”,触发的事件类型。范例是在 mousePressed 事件中调用。详情见 3.9 节。
  2. “(52,93)”,触发事件时鼠标相对于此组件的坐标。格式“(X,Y)”。和上文 3.2 中介绍的方法作用相同。
  3. “absolute(59,163)”,触发事件时鼠标的绝对坐标,前面跟一个“absolute”单词。与上文 3.1 中介绍的方法作用相同。
  4. “button=1”,指触发事件所用的鼠标按钮是哪个,示例中为左键。与上文 3.4 介绍的 getButton() 返回的值相同。
  5. “modifiers=Ctrl+Button1”,指触发事件时的修饰符。与接下来将在 3.7 介绍的方法作用相同。
  6. “extModifiers=Ctrl”,指触发事件时的修饰符。详情请见 3.8 节。
  7. “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

就挺玄学的,我在不同事件中测试,结果如下:

监听器事件状态
MouseListenermousePressed正常返回
MouseListenermouseReleased离谱特性
MouseListenermouseClicked离谱特性
MouseMotionListenermouseDragged正常返回

其他事件则没有监听鼠标点击。

至于 getModifiersExText() 则和上一节中的 getMouseModifiers() 作用相同。但 getModifiersExText() 用的是 XXX_DOWN_MASK,getModifiersText() 用的 XXX_MASK,所以不能混起来用。上一节中的两个方法配套使用,这一节的两个方法也只能配套使用。

3.9 getID()

返回触发的事件(int 常量),用于编写静态方法(工具)时使用,可用的常量如下:

常量名称事件
MOUSE_PRESSEDmousePressed(鼠标按下)501
MOUSE_RELEASEDmouseReleased(鼠标松开)502
MOUSE_CLICKEDmouseClicked(鼠标点击)500
MOUSE_ENTEREDmouseEntered(鼠标浮动)504
MOUSE_EXITEDmouseExited(鼠标离开)505
MOUSE_MOVEDmouseMoved(鼠标移动)503
MOUSE_DRAGGEDmouseDragged(鼠标拖动)506

还有一个较为特殊,这个是属于 MouseWheelListener(鼠标滚轮监听器)的:

常量名称事件
MOUSE_WHEELmouseWheelMoved(鼠标滚轮滚动)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 个监听器接口)。

  • 7
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Java鼠标点击事件监听可以通过实现 MouseListener 接口来完成。 MouseListener 接口定义了以下五个方法: - void mouseClicked(MouseEvent e):鼠标单击事件 - void mousePressed(MouseEvent e):鼠标按下事件 - void mouseReleased(MouseEvent e):鼠标释放事件 - void mouseEntered(MouseEvent e):鼠标进入组件事件 - void mouseExited(MouseEvent e):鼠标离开组件事件 要实现鼠标点击事件监听,需要创建一个类并实现 MouseListener 接口。例如: ```java import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.JFrame; public class ClickListener extends JFrame implements MouseListener { public ClickListener() { super("MouseListener Demo"); setSize(300, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); addMouseListener(this); setVisible(true); } public static void main(String[] args) { new ClickListener(); } @Override public void mouseClicked(MouseEvent e) { System.out.println("鼠标单击事件"); } @Override public void mousePressed(MouseEvent e) { System.out.println("鼠标按下事件"); } @Override public void mouseReleased(MouseEvent e) { System.out.println("鼠标释放事件"); } @Override public void mouseEntered(MouseEvent e) { System.out.println("鼠标进入组件事件"); } @Override public void mouseExited(MouseEvent e) { System.out.println("鼠标离开组件事件"); } } ``` 在上面的代码中,我们创建了一个类 ClickListener,并实现了 MouseListener 接口。在构造函数中,我们将当前对象作为监听器添加到 JFrame 上,这样该 JFrame 就能够监听鼠标事件了。 接下来,我们在各个监听方法中打印出相应的事件信息。当用户进行鼠标单击、按下、释放、进入组件和离开组件时,就会触发相应的监听方法,从而打印出相应的事件信息。 最后,我们创建一个 ClickListener 对象,运行程序即可看到效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值