事件监听器

前文中一直在讲解组件,这些组件本身并不带有任何功能。例如,在窗体中定义一个按钮,当用户单击该按钮时,虽然按钮可以凹凸显示,但在窗体中并没有实现任何功能。这时需要为按钮添加特定的事件监听器,该监听器负责处理用户单击按钮后实现的功能。本节将着重讲解 Swing 中常用的 3 种事件监听器,即动作事件监听器、键盘事件监听器、鼠标事件监听器。

ActionEvent动作事件

动作事件(ActionEvent)监听器是 Swing 中比较常用的事件监听器,很多组件的动作都会使用它进行监听,如按钮被单击等。表18.9描述了动作事件监听器的接口与事件源等。

image 2024 03 05 23 29 59 791
Figure 1. 表18.9 动作事件监听器

下面以单击按钮事件为例来说明动作事件监听器,当用户单击按钮时,将触发动作事件。

【例18.25】单击按钮后,修改按钮文本(实例位置:资源包\TM\sl\18\25)

创建 SimpleEvent 类,使该类继承 JFrame 类,在类中创建按钮组件,为按钮组件添加动作监听器,然后将按钮组件添加到窗体中。

import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.WindowConstants;

public class SimpleEvent extends JFrame {

	private JButton jb = new JButton("我是按钮,点击我");

	public SimpleEvent() {
		setLayout(null);
		setSize(200, 100);
		setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
		Container cp = getContentPane();
		cp.add(jb);
		jb.setBounds(10, 10, 150, 30);
		jb.addActionListener(new jbAction());
		setVisible(true);
	}

	class jbAction implements ActionListener {
		public void actionPerformed(ActionEvent arg0) {
			jb.setText("我被点击了");
		}
	}

	public static void main(String[] args) {
		new SimpleEvent();
	}
}

运行结果如图18.34所示。

image 2024 03 05 23 31 11 157
Figure 2. 图18.34 按钮添加动作事件后的点击效果

在本实例中,为按钮设置了动作监听器。由于获取事件监听时需要获取实现 ActionListener 接口的对象,因此定义了一个内部类 jbAction 实现 ActionListener 接口,同时在该内部类中实现了 actionPerformed() 方法,也就是在 actionPerformed() 方法中定义当用户单击该按钮后实现怎样的功能。

KeyEvent键盘事件

当向文本框中输入内容时,将发生键盘事件。KeyEvent 类负责捕获键盘事件,可以通过为组件添加实现了 KeyListener 接口的监听器类来处理相应的键盘事件。

KeyListener 接口共有 3 个抽象方法,分别在发生按键事件(按下并释放键)、按键被按下(手指按下键但不松开)和按键被释放(手指从按下的键上松开)时被触发,具体如下:

import java.awt.event.KeyEvent;public interface KeyListener extends EventListener {
    public void keyTyped(KeyEvent e);  // 发生按键事件时被触发
    public void keyPressed(KeyEvent e);  // 按键被按下时被触发
    public void keyReleased(KeyEvent e);  // 按键被释放时被触发
}

在上述每个抽象方法中,均传入了 KeyEvent 类的对象。KeyEvent 类中比较常用的方法如表18.10所示。

image 2024 03 05 23 34 37 821
Figure 3. 表18.10 KeyEvent类中的常用方法

在 KeyEvent 类中,以 “VK_” 开头的静态常量代表各个按键的 keyCode,可以通过这些静态常量判断事件中的按键,获得按键的标签。

【例18.26】虚拟键盘(实例位置:资源包\TM\sl\18\26)

通过键盘事件模拟一个虚拟键盘。首先需要自定义一个 addButtons() 方法,用来将所有的按键添加到一个 ArrayList 集合中,然后添加一个 JTextField 组件,并为该组件添加 addKeyListener 事件监听,在该事件监听中重写 keyPressed() 和 keyReleased() 方法,分别用来在按下和释放键时执行相应的操作。关键代码如下:

import java.awt.BorderLayout;
import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import java.awt.Color;
import java.awt.Component;

import javax.swing.JButton;
import java.awt.Font;
import javax.swing.SwingConstants;
import javax.swing.border.TitledBorder;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;

import javax.swing.JTextField;

/**
 * 虚拟键盘(键盘的按下与释放)
 */
public class KeyBoard extends JFrame { // 创建“键盘”类继承JFrame
	// 声明窗体中的成员组件
	private JPanel contentPane;
	private JTextField textField;
	private JButton btnQ;
	private JButton btnW;
	private JButton btnE;
	private JButton btnR;
	private JButton btnT;
	private JButton btnY;
	private JButton btnU;
	private JButton btnI;
	private JButton btnO;
	private JButton btnP;
	private JButton btnA;
	private JButton btnS;
	private JButton btnD;
	private JButton btnF;
	private JButton btnG;
	private JButton btnH;
	private JButton btnJ;
	private JButton btnK;
	private JButton btnL;
	private JButton btnZ;
	private JButton btnX;
	private JButton btnC;
	private JButton btnV;
	private JButton btnB;
	private JButton btnN;
	private JButton btnM;
	Color green = Color.GREEN;// 定义Color对象,用来表示按下键的颜色
	Color white = Color.WHITE;// 定义Color对象,用来表示释放键的颜色

	ArrayList<JButton> btns = new ArrayList<JButton>();// 定义一个集合,用来存储所有的按键ID
	// 自定义一个方法,用来将容器中的所有JButton组件添加到集合中

	private void addButtons() {
		for (Component cmp : contentPane.getComponents()) {// 遍历面板中的所有组件
			if (cmp instanceof JButton) {// 判断组件的类型是否为JButton类型
				btns.add((JButton) cmp);// 将JButton组件添加到集合中
			}
		}
	}

	/**
	 * 主方法
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() { // 使得Runnable中的的run()方法在the system EventQueue的指派线程中被调用
			public void run() {
				try {
					KeyBoard frame = new KeyBoard(); // 创建KeyBoard对象
					frame.setVisible(true); // 使frame可视
					frame.addButtons();// 初始化存储所有按键的集合
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * 创建JFrame窗体
	 */
	public KeyBoard() { // KeyBoard的构造方法
		setTitle("\u865A\u62DF\u952E\u76D8\uFF08\u6A21\u62DF\u952E\u76D8\u7684\u6309\u4E0B\u4E0E\u91CA\u653E\uFF09"); // 设置窗体题目
		setResizable(false); // 不可改变窗体宽高
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置窗体关闭的方式
		setBounds(100, 100, 560, 280); // 设置窗体的位置和宽高
		/**
		 * 创建JPanel面板contentPane置于JFrame窗体中,并设置面板的背景色、边距和布局
		 */
		contentPane = new JPanel();
		contentPane.setBackground(Color.WHITE);
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);
		/**
		 * 创建按钮button置于面板contentPane中,设置按钮的背景色、位置、宽高以及按钮中的字体位置、内容、样式
		 */
		btnQ = new JButton("Q");
		btnQ.setBackground(white);
		btnQ.setVerticalAlignment(SwingConstants.TOP);
		btnQ.setHorizontalAlignment(SwingConstants.LEADING);
		btnQ.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnQ.setBounds(0, 60, 47, 45);
		contentPane.add(btnQ);
		/**
		 * 创建按钮button_2置于面板contentPane中,设置按钮的背景色、位置、宽高以及按钮中的字体位置、内容、样式
		 */
		btnW = new JButton("W");
		btnW.setBackground(white);
		btnW.setVerticalAlignment(SwingConstants.TOP);
		btnW.setHorizontalAlignment(SwingConstants.LEADING);
		btnW.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnW.setBounds(55, 60, 49, 45);
		contentPane.add(btnW);
		/**
		 * 创建按钮button_3置于面板contentPane中,设置按钮的背景色、位置、宽高以及按钮中的字体位置、内容、样式
		 */
		btnE = new JButton("E");
		btnE.setBackground(white);
		btnE.setVerticalAlignment(SwingConstants.TOP);
		btnE.setHorizontalAlignment(SwingConstants.LEADING);
		btnE.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnE.setBounds(110, 60, 45, 45);
		contentPane.add(btnE);
		/**
		 * 创建按钮button_4置于面板contentPane中,设置按钮的背景色、位置、宽高以及按钮中的字体位置、内容、样式
		 */
		btnR = new JButton("R");
		btnR.setBackground(white);
		btnR.setVerticalAlignment(SwingConstants.TOP);
		btnR.setHorizontalAlignment(SwingConstants.LEADING);
		btnR.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnR.setBounds(165, 60, 45, 45);
		contentPane.add(btnR);
		/**
		 * 创建按钮button_5置于面板contentPane中,设置按钮的背景色、位置、宽高以及按钮中的字体位置、内容、样式
		 */
		btnF = new JButton("F");
		btnF.setBackground(white);
		btnF.setVerticalAlignment(SwingConstants.TOP);
		btnF.setHorizontalAlignment(SwingConstants.LEADING);
		btnF.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnF.setBounds(195, 125, 45, 45);
		contentPane.add(btnF);
		/**
		 * 创建按钮button_6置于面板contentPane中,设置按钮的背景色、位置、宽高以及按钮中的字体位置、内容、样式
		 */
		btnD = new JButton("D");
		btnD.setBackground(white);
		btnD.setVerticalAlignment(SwingConstants.TOP);
		btnD.setHorizontalAlignment(SwingConstants.LEADING);
		btnD.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnD.setBounds(137, 125, 45, 45);
		contentPane.add(btnD);

		btnT = new JButton("T");
		btnT.setVerticalAlignment(SwingConstants.TOP);
		btnT.setHorizontalAlignment(SwingConstants.LEADING);
		btnT.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnT.setBackground(white);
		btnT.setBounds(220, 60, 45, 45);
		contentPane.add(btnT);

		btnY = new JButton("Y");
		btnY.setVerticalAlignment(SwingConstants.TOP);
		btnY.setHorizontalAlignment(SwingConstants.LEADING);
		btnY.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnY.setBackground(white);
		btnY.setBounds(275, 60, 45, 45);
		contentPane.add(btnY);

		btnU = new JButton("U");
		btnU.setVerticalAlignment(SwingConstants.TOP);
		btnU.setHorizontalAlignment(SwingConstants.LEADING);
		btnU.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnU.setBackground(white);
		btnU.setBounds(330, 60, 45, 45);
		contentPane.add(btnU);

		btnI = new JButton("I");
		btnI.setVerticalAlignment(SwingConstants.TOP);
		btnI.setHorizontalAlignment(SwingConstants.LEADING);
		btnI.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnI.setBackground(white);
		btnI.setBounds(385, 60, 45, 45);
		contentPane.add(btnI);

		btnO = new JButton("O");
		btnO.setVerticalAlignment(SwingConstants.TOP);
		btnO.setHorizontalAlignment(SwingConstants.LEADING);
		btnO.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnO.setBackground(white);
		btnO.setBounds(440, 60, 46, 45);
		contentPane.add(btnO);

		btnP = new JButton("P");
		btnP.setVerticalAlignment(SwingConstants.TOP);
		btnP.setHorizontalAlignment(SwingConstants.LEADING);
		btnP.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnP.setBackground(white);
		btnP.setBounds(495, 60, 45, 45);
		contentPane.add(btnP);

		btnA = new JButton("A");
		btnA.setVerticalAlignment(SwingConstants.TOP);
		btnA.setHorizontalAlignment(SwingConstants.LEADING);
		btnA.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnA.setBackground(white);
		btnA.setBounds(23, 125, 45, 45);
		contentPane.add(btnA);

		btnS = new JButton("S");
		btnS.setVerticalAlignment(SwingConstants.TOP);
		btnS.setHorizontalAlignment(SwingConstants.LEADING);
		btnS.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnS.setBackground(white);
		btnS.setBounds(82, 125, 45, 45);
		contentPane.add(btnS);

		btnG = new JButton("G");
		btnG.setVerticalAlignment(SwingConstants.TOP);
		btnG.setHorizontalAlignment(SwingConstants.LEADING);
		btnG.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnG.setBackground(white);
		btnG.setBounds(251, 125, 45, 45);
		contentPane.add(btnG);

		btnH = new JButton("H");
		btnH.setVerticalAlignment(SwingConstants.TOP);
		btnH.setHorizontalAlignment(SwingConstants.LEADING);
		btnH.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnH.setBackground(white);
		btnH.setBounds(306, 125, 45, 45);
		contentPane.add(btnH);

		btnJ = new JButton("J");
		btnJ.setVerticalAlignment(SwingConstants.TOP);
		btnJ.setHorizontalAlignment(SwingConstants.LEADING);
		btnJ.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnJ.setBackground(white);
		btnJ.setBounds(361, 125, 45, 45);
		contentPane.add(btnJ);

		btnK = new JButton("K");
		btnK.setVerticalAlignment(SwingConstants.TOP);
		btnK.setHorizontalAlignment(SwingConstants.LEADING);
		btnK.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnK.setBackground(white);
		btnK.setBounds(416, 125, 47, 45);
		contentPane.add(btnK);

		btnL = new JButton("L");
		btnL.setVerticalAlignment(SwingConstants.TOP);
		btnL.setHorizontalAlignment(SwingConstants.LEADING);
		btnL.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnL.setBackground(white);
		btnL.setBounds(471, 125, 45, 45);
		contentPane.add(btnL);

		btnZ = new JButton("Z");
		btnZ.setVerticalAlignment(SwingConstants.TOP);
		btnZ.setHorizontalAlignment(SwingConstants.LEADING);
		btnZ.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnZ.setBackground(white);
		btnZ.setBounds(39, 190, 45, 45);
		contentPane.add(btnZ);

		btnX = new JButton("X");
		btnX.setVerticalAlignment(SwingConstants.TOP);
		btnX.setHorizontalAlignment(SwingConstants.LEADING);
		btnX.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnX.setBackground(white);
		btnX.setBounds(107, 190, 45, 45);
		contentPane.add(btnX);

		btnC = new JButton("C");
		btnC.setVerticalAlignment(SwingConstants.TOP);
		btnC.setHorizontalAlignment(SwingConstants.LEADING);
		btnC.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnC.setBackground(white);
		btnC.setBounds(178, 190, 45, 45);
		contentPane.add(btnC);

		btnV = new JButton("V");
		btnV.setVerticalAlignment(SwingConstants.TOP);
		btnV.setHorizontalAlignment(SwingConstants.LEADING);
		btnV.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnV.setBackground(white);
		btnV.setBounds(250, 190, 45, 45);
		contentPane.add(btnV);

		btnB = new JButton("B");
		btnB.setVerticalAlignment(SwingConstants.TOP);
		btnB.setHorizontalAlignment(SwingConstants.LEADING);
		btnB.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnB.setBackground(white);
		btnB.setBounds(315, 190, 45, 45);
		contentPane.add(btnB);

		btnN = new JButton("N");
		btnN.setVerticalAlignment(SwingConstants.TOP);
		btnN.setHorizontalAlignment(SwingConstants.LEADING);
		btnN.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnN.setBackground(white);
		btnN.setBounds(382, 190, 47, 45);
		contentPane.add(btnN);

		btnM = new JButton("M");
		btnM.setVerticalAlignment(SwingConstants.TOP);
		btnM.setHorizontalAlignment(SwingConstants.LEADING);
		btnM.setFont(new Font("Times New Roman", Font.PLAIN, 14));
		btnM.setBackground(white);
		btnM.setBounds(449, 190, 48, 45);
		contentPane.add(btnM);
		/**
		 * 创建面板panel置于面板contentPane中,设置面板panel的位置、宽高、TitledBorder、背景色以及布局方式(边界布局)
		 */
		JPanel panel = new JPanel();
		panel.setBorder(new TitledBorder(null, "文本显示区", TitledBorder.LEADING, TitledBorder.TOP, null, null));
		panel.setBackground(Color.WHITE);
		panel.setBounds(0, 0, 540, 45);
		contentPane.add(panel);
		panel.setLayout(new BorderLayout(0, 0));

		/**
		 * 创建文本框textField置于面板panel的中间
		 */
		textField = new JTextField();
		textField.addKeyListener(new KeyAdapter() { // 文本框添加键盘事件的监听
			char word;

			@Override
			public void keyPressed(KeyEvent e) { // 按键被按下时被触发
				word = e.getKeyChar();// 获取按下键表示的字符
				for (int i = 0; i < btns.size(); i++) {// 遍历存储按键ID的ArrayList集合
					// 判断按键是否与遍历到的按键的文本相同
					if (String.valueOf(word).equalsIgnoreCase(btns.get(i).getText())) {
						btns.get(i).setBackground(green);// 将指定按键颜色设置为绿色
					}
				}
			}

			@Override
			public void keyReleased(KeyEvent e) { // 按键被释放时被触发
				word = e.getKeyChar();// 获取释放键表示的字符
				for (int i = 0; i < btns.size(); i++) {// 遍历存储按键ID的ArrayList集合
					// 判断按键是否与遍历到的按键的文本相同
					if (String.valueOf(word).equalsIgnoreCase(btns.get(i).getText())) {
						btns.get(i).setBackground(white);// 将指定按键颜色设置为白色
					}
				}
			}
		});
		panel.add(textField, BorderLayout.CENTER);
		textField.setColumns(10);
	}
}

运行本实例,将鼠标定位到文本框组件中,然后按下键盘上的按键,窗体中的相应按钮会变为绿色,释放按键时,相应按钮变为白色,效果如图18.35所示。

image 2024 03 05 23 37 02 474
Figure 4. 图18.35 键盘事件

MouseEvent鼠标事件

所有组件都能发生鼠标事件,MouseEvent 类负责捕获鼠标事件,可以通过为组件添加实现了 MouseListener 接口的监听器类来处理相应的鼠标事件。

MouseListener 接口共有 5 个抽象方法,分别在光标移入或移出组件、鼠标按键被按下或被释放和发生单击事件时被触发。所谓单击事件,就是按键被按下并被释放。需要注意的是,如果按键是在移出组件之后才被释放的,则不会触发单击事件。MouseListener 接口的具体定义如下:

import java.awt.event.MouseEvent;
import java.util.EventListener;

public interface MouseListener extends EventListener {
    public void mouseEntered(MouseEvent e);  // 光标移入组件时被触发
    public void mousePressed(MouseEvent e);  // 鼠标按键被按下时被触发
    public void mouseReleased(MouseEvent e);  // 鼠标按键被释放时被触发
    public void mouseClicked(MouseEvent e);  // 发生单击事件时被触发
    public void mouseExited(MouseEvent e);   // 光标移出组件时被触发

}

在上述每个抽象方法中,均传入了 MouseEvent 类的对象。MouseEvent 类中比较常用的方法如表18.11所示。

image 2024 03 05 23 41 55 313
Figure 5. 表18.11 MouseEvent类中的常用方法

当需要判断触发此次事件的按键时,可以通过表18.12中的静态常量判断由 getButton() 方法返回的 int 型值代表的键。

image 2024 03 05 23 42 34 328
Figure 6. 表18.12 MouseEvent类中代表鼠标按键的静态常量

【例18.27】捕捉鼠标在窗体中的行为(实例位置:资源包\TM\sl\18\27)

创建一个窗体,在窗体中捕捉鼠标进入、移出和单击事件,关键代码如下:

import java.awt.BorderLayout;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.JFrame;
import javax.swing.JLabel;

public class MouseEventDemo extends JFrame { // 继承窗体类JFrame

	public static void main(String args[]) {
		MouseEventDemo frame = new MouseEventDemo();
		frame.setVisible(true); // 设置窗体可见,默认为不可见
	}

	/**
	 * 判断按下的鼠标键,并输出相应提示
	 * 
	 * @param e 鼠标事件
	 */
	private void mouseOper(MouseEvent e) {
		int i = e.getButton(); // 通过该值可以判断按下的是哪个键
		if (i == MouseEvent.BUTTON1)
			System.out.println("按下的是鼠标左键");
		else if (i == MouseEvent.BUTTON2)
			System.out.println("按下的是鼠标滚轮");
		else if (i == MouseEvent.BUTTON3)
			System.out.println("按下的是鼠标右键");
	}

	public MouseEventDemo() {
		super(); // 继承父类的构造方法
		setTitle("鼠标事件示例"); // 设置窗体的标题
		setBounds(100, 100, 500, 375); // 设置窗体的显示位置及大小
		// 设置窗体关闭按钮的动作为退出
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		final JLabel label = new JLabel();
		label.addMouseListener(new MouseListener() {
			public void mouseEntered(MouseEvent e) {// 光标移入组件时被触发
				System.out.println("光标移入组件");
			}

			public void mousePressed(MouseEvent e) {// 鼠标按键被按下时被触发
				System.out.print("鼠标按键被按下,");
				mouseOper(e);
			}

			public void mouseReleased(MouseEvent e) {// 鼠标按键被释放时被触发
				System.out.print("鼠标按键被释放,");
				mouseOper(e);
			}

			public void mouseClicked(MouseEvent e) {// 发生单击事件时被触发
				System.out.print("单击了鼠标按键,");
				mouseOper(e);
				int clickCount = e.getClickCount();// 获取鼠标单击次数
				System.out.println("单击次数为" + clickCount + "下");
			}

			public void mouseExited(MouseEvent e) {// 光标移出组件时被触发
				System.out.println("光标移出组件");
			}
		});
		getContentPane().add(label, BorderLayout.CENTER);
		//
	}

}

运行结果如图18.36所示,首先将光标移入窗体,然后单击,接着双击,最后将光标移出窗体,在控制台中将得到如图18.37所示的信息。当双击鼠标时,第一次单击鼠标将触发一次单击事件。

image 2024 03 05 23 43 45 973
Figure 7. 图18.36 程序弹出的窗体
image 2024 03 05 23 44 19 514
Figure 8. 图18.37 在控制台中输出的日志

编程训练(答案位置:资源包\TM\sl\18\编程训练)

【训练17】移动图标 创建“前进”、“后退”、“上移”、“下移” 4 个按钮,单击后可以让一个图标在窗体中的位置发生变化。

【训练18】可操控的全景地图 使用键盘事件(↑:北;↓:南;←:西;→:东),查看十字路口的全景图。