七、事件处理

1、事件处理基础

在AWT所知的事件范围内,完全可以控制事件从事件源,例如,按钮或滚动条,到事件监听器的传递过程,并将任何对象指派给事件监听器。不过事实上,应该选择一个能够便于响应事件的对象。这种事件委托模型与VB那种预定义监听器模型比较起来更加灵活。
事件源有一些向其注册事件监听器的方法。当某个事件源产生事件时,事件源会向为事件注册的所有事件监听器对象发送一个通告。

AWT事件处理机制的概要:

  • 监听对象是一个实现了特定监听器接口的类的实例
  • 事件源是一个能够注册监听器对象并发送事件对象的对象
  • 当事件发生时,事件源将事件对象传递给所有注册的监听器
  • 监听器对象将利用事件对象中的信息决定如何对事件做出响应

我们要实现一个监听器,必须要让它实现ActionListener接口,并重写actionPerformed方法
public void actionPerformed(ActionEvent e);
actionPerformed方法将接受一个ActionEvent对象作为参数,这个事件对象包含了事件发生时的相关信息

1.1 实例:处理按钮点击事件

事件监听对象通常需要执行一些对其他对象可能产生影响的操作。可以策略性的将监听器类放置在需要修改的状态的那个类中。

示例代码

package com.java01.day06.test01;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * @author: ju
 * @date: 2020-05-11 17:33
 */
public class ButtonFrame extends JFrame {
    private JPanel buttonPanel;
    private static final int DEFAULT_WIDTH = 300;
    private static final int DEFAULT_HEIGHT = 200;

    public ButtonFrame(){
        //设置大小
        setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
        //定义按钮
        JButton yellowButton = new JButton("yellow");
        JButton blueButton = new JButton("blue");
        JButton redButton = new JButton("red");

        buttonPanel = new JPanel();
        //将按钮add到JPanel
        buttonPanel.add(yellowButton);
        buttonPanel.add(blueButton);
        buttonPanel.add(redButton);

        //addJPanel到JFrame
        add(buttonPanel);

        //构造监听器
        ColorAction yellowAction = new ColorAction(Color.YELLOW);
        ColorAction blueAction = new ColorAction(Color.blue);
        ColorAction redAction = new ColorAction(Color.red);

        //将监听器添加到按钮上
        yellowButton.addActionListener(yellowAction);
        blueButton.addActionListener(blueAction);
        redButton.addActionListener(redAction);
    }

    private class ColorAction implements ActionListener {
        //背景颜色
        private Color backgroundColor;

        //构造
        public ColorAction(Color backgroundColor) {
            this.backgroundColor = backgroundColor;
        }

        /**
         * 重写actionPerformed方法
         * @param e 事件对象 包含了事件发生时的相关信息
         */
        @Override
        public void actionPerformed(ActionEvent e) {
            buttonPanel.setBackground(backgroundColor);
        }
    }
}

测试

public class ButtonFrameTest {
    public static void main(String[] args){
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new ButtonFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setTitle("按钮点击");
                frame.setVisible(true);
            }
        });
    }
}

运行结果
在这里插入图片描述

1.2 建议使用内部类

示例代码

package com.java01.day06.test01;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * @author: ju
 * @date: 2020-05-11 17:33
 */
public class ButtonFrame extends JFrame {
    private JPanel buttonPanel;
    private static final int DEFAULT_WIDTH = 300;
    private static final int DEFAULT_HEIGHT = 200;

    public ButtonFrame(){
        //设置大小
        setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
        buttonPanel = new JPanel();

        makeButton("yellow", Color.yellow);
        makeButton("blue", Color.blue);
        makeButton("red", Color.red);

        add(buttonPanel);
    }

    private void makeButton(String name, final Color backgroundColor){
        JButton button = new JButton(name);
        buttonPanel.add(button);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                buttonPanel.setBackground(backgroundColor);
            }
        });
    }

}

1.3 创建包含一个方法调用的监听器

在这里插入图片描述
构造实现给定接口的一个代理类对象。命名方法或接口的所有方法都将在目标对象上执行给定动作。
这个动作可以是一个方法名或目标的一个属性。如果是一个属性,将执行其设置方法。例如,动作“text”将转换为一个setText的方法调用。
事件属性包括一个或多个用点号分隔的属性名。第一个属性从监听器方法的参数读取,第二个属性由结果对象读取,以此类推。最后的结果将作为动作的参数。例如:属性“source.text”会转换为getSource和getText方法调用。

示例代码

		button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                frame.loadDate();
            }
        });
       

可以使用下列调用自动创建一个监听器

		EventHandler.create(ActionListener.class, frame, "loadDate");
1.4 实例:改变观感

在默认情况下,Swing程序使用Metal观感,可以采用两种方式改变观感器。
第一种方式:在Java安装的子目录jre/lib下,有一个文件swing.properties。在这个文件中,将属性swing.defaultlaf设置为所希望的观感类名。例如:

swing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel

第二种方式:动态的改变观感。
调用静态的UIManager.setLookAndFeel方法,并提供所想要的观感类名,然后再调用静态方法SwingUtilities.updateComponentTreeUI(PlafFrame.this);刷新全部组件集。可以调用UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();列举全部的观感集合。

示例代码

package com.java01.day06.test02;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * @description:
 * @author: ju
 * @date: 2020-05-12 10:31
 */
public class PlafFrame extends JFrame {
    private JPanel buttonPanel;
    private static final int DEFAULT_WIDTH = 400;
    private static final int DEFAULT_HEIGHT = 400;

    public PlafFrame(){
        setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
        buttonPanel = new JPanel();
        //列举所有观感信息
        UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();
        //遍历
        for (UIManager.LookAndFeelInfo info : infos){
            //按钮添加监听事件改变观感
            //getName()观感的显示名称 getClassName()观感的类名
            makeButton(info.getName(), info.getClassName());
        }
        add(buttonPanel);
    }

    /**
     * 按钮添加监听事件改变观感
     * @param name 按钮名
     * @param plafName 观感类名
     */
    private void makeButton(String name, final String plafName){
        JButton button = new JButton(name);
        buttonPanel.add(button);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    //设置观感
                    UIManager.setLookAndFeel(plafName);
                    //刷新所有组件集
                    SwingUtilities.updateComponentTreeUI(PlafFrame.this);

                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
    }
}

测试

public class PlafFrameTest {
    public static void main(String[] args){
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new PlafFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setTitle("观感");
                frame.setVisible(true);
            }
        });
    }
}

运行结果
在这里插入图片描述

1.5 适配器类

在正规的程序中,我们希望在关闭窗口的时候提示一个确认关闭的弹窗,用户确认后再关闭窗口,这种情况下,当程序用户试图关闭一个框架窗口时,JFrame对象就是WindowEvent的事件源。如果希望捕获这个事件,就必须有一个合适的监听器对象,并将它添加到框架的窗口监听器列表中。

WindowListener windowListener = new WindowListener() {
public void windowOpened(WindowEvent e) {
}
public void windowClosing(WindowEvent e) {
}
public void windowClosed(WindowEvent e) {
}

};
frame.addWindowListener(windowListener);

窗口监听器必须是实现WindowListener 接口的类的一个对象,在WindowListener 接口中包含七个方法(如上所示)。当发生窗口事件时,框架将调用这7个方法响应7个不同的事件。在Java中,实现一个接口的任何类都必须实现其中的所有方法;在这里,意味着需要实现7个方法。然而我们现在只对名为windowClosing的方法感兴趣,这种情况下,书写其他6个方法是没有意义的。鉴于简化的目的,每个含有多个方法的AWT监听器接口都配有一个适配器(adapter)类,这个类实现了接口中的所有方法,但每个方法没有做任何事情。可以通过扩展适配器类来指定对某些事件的响应动作,而不必实现接口中的每个方法(ActionListener接口只有一个方法,因此没必要提供适配器类)。

下面使用窗口适配器类:

首先定义一个WindowAdapter的扩展类,重写windowClosing方法

public class Terminator extends WindowAdapter {
    @Override
    public void windowClosing(WindowEvent event){
        if ("userAgree"){
            System.exit(0);
        }
    }
}

注册为事件监听器

WindowListener windowListener = new Terminator();
frame.addWindowListener(windowListener);

简化代码

frame.addWindowListener(new WindowAdapter() {
	@Override
    public void windowClosing(WindowEvent e) {
           System.exit(0);
    }
});
 * public void windowOpened(WindowEvent e);
  窗口打开后调用这个方法
 * public void windowClosing(WindowEvent e);
  用户发出关闭窗口命令时调用这个方法
 * public void windowClosed(WindowEvent e);
  窗口关闭后调用这个方法
 * public void windowIconified(WindowEvent e);
  窗口图标化后调用这个方法
 * public void windowDeiconified(WindowEvent e);
  窗口非图标化后调用这个方法
 * public void windowActivated(WindowEvent e);
  激活窗口后调用这个方法
 * public void windowDeactivated(WindowEvent e);
  窗口变为未激活状态调用这个方法

2、动作

通常,激活一个命令可以有多种方式。用户可以通过菜单、击键或工具栏上的按钮选择特定的功能。在AWT事件模型中实现这些非常容易,将所有事件连接到同一个监听器上就可以实现。
Swing包提供了一种非常实用的机制来封装命令,并将他们连接到多个事件源,这就是Action接口。一个动作是一个封装下列内容的对象:

  • 命令的说明(一个文本字符串或一个可选图标)
  • 执行命令所需要的参数

Action接口包含下列方法:

     * public void actionPerformed(ActionEvent e);
     扩展于ActionListener
     * public void setEnabled(boolean b);
     启用/禁止这个动作
     * public boolean isEnabled();
     检查这个动作当前是否启用
     * public void putValue(String key, Object value);
     存储动作对象中的任意名/值
     * public Object getValue(String key);
     检索动作对象中的任意名/值
     * public void addPropertyChangeListener(PropertyChangeListener listener);
     * public void removePropertyChangeListener(PropertyChangeListener listener);
     让其他对象在动作对象的属性发生变化时得到通告

Action中预定于动作表名称

名称
NAME动作名称,显示在按钮和菜单上
SMALL_ICON存储小图标的地方,显示在按钮,菜单项或工具栏中
SHORT_DESCRIPTION图标的简要说明,显示在工具提示中
LONG_DESCRIPTION图标的详细说明,显示在在线帮助中,没有Swing组件使用这个值
MNEMONIC_KEY快捷键缩写,显示在菜单项中
ACCELERATOR_KEY存储加速击键的地方,Swing组件不使用这个值
DEFAULT常用的综合属性,Swing组件不使用这个值

Action是一个接口,而不是一个类,实现这个接口的所有类都必须实现它的7个方法。有一个类实现了这个接口除了actionPerformed方法之外的所有方法,它就是AbstractAction。这个类存储了所有名/值对,并管理着属性变更监听器,我们可以直接扩展AbstractAction类,并在扩展类中实现actionPerformed方法。

    public class ColorAction extends AbstractAction {

        public ColorAction(String name, Icon icon, Color c) {
            putValue(Action.NAME, name);
            putValue(Action.SMALL_ICON, icon);
            putValue("color", c);
            putValue(Action.SHORT_DESCRIPTION, "设置颜色到:" + name.toLowerCase());
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            Color c = (Color) getValue("color");
            buttonPanel.setBackground(c);
        }
    }

使用Action对象作为参数的JButton构造

        ColorAction blueAction = new ColorAction("blue", new ImageIcon(), Color.blue);
        JButton blueButton = new JButton(blueAction);

这样我们就可以看到前面1.1中的执行效果,点击按钮后更改背景色的效果。

同时我们想将这个动作对象添加到击键中,以便让用户敲击键盘命令来执行这项动作,首先我们要获得一个KeyStroke类对象,它封装了对键的说明,调用KeyStroke.getKeyStroke(“ctrl B”);拿到ctrl+B的键对象。

keyboard focus 的概念:
用户界面中可以包含许多按钮、菜单、滚动栏以及其他的组件。当用户敲击键盘时,这个动作会被发送给拥有焦点的组件。

然而,在这里的示例中,并不希望将击键发送给拥有焦点的组件。针对这种情况,Swing提供了一个便捷的解决方式,每个JComponent有三个输入映射,每个映射的KeyStroke对象都与动作关联。三个输入映射对应着三个不同的条件:

标志激活动作
WHEN_FOCUSED当这个组件拥有键盘焦点时
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT当这个组件包含了拥有键盘焦点的组件时
WHEN_IN_FOCUSED_WINDOW当这个组件被包含在一个拥有键盘焦点组件的窗口中时

按键处理将按照下列这些顺序检查这些映射:
1、检查具有输入焦点组件的WHEN_FOCUSED 映射。如果这个按键存在,将执行对应的动作。如果动作已启动,则停止处理。
2、从具有输入焦点的组件开始,检查其父组件的WHEN_ANCESTOR_OF_FOCUSED_COMPONENT映射,一旦找到按键对应的映射,就执行对应的动作。如果动作已启动,将停止处理。
3、查看具有输入焦点的窗口中的所有可视的和启用的组件,这个按键被注册到WHEN_IN_FOCUSED_WINDOW映射中。给这些组件(按照按键注册的顺序)一个执行对应动作的机会。一旦第一个启用的动作被执行,就停止处理。如果一个按键在多个WHEN_IN_FOCUSED_WINDOW映射中出现,这部分处理可能就会出现问题。

使用getInputMap从组件中得到输入映射

InputMap inputMap = buttonPanel.getInputMap(JPanel.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);

总结一下用同一个动作响应按钮、菜单项或按键的方式:
1、实现一个扩展于AbstractAction类的类。多个相关的动作可以使用同一类。
2、构造一个动作类的对象。
3、使用动作对象创建按钮或菜单项。构造器将从动作对象中读取标签文本和图标。
4、为了能够通过按键触发动作,必须额外的执行几步操作,首先定位顶层窗口组件,例如,包含所有其他组件的面板。
5、然后得到顶层组件的WHEN_ANCESTOR_OF_FOCUSED_COMPONENT输入映射。为需要的按键创建一个KeyStrike对象。创建一个描述动作字符串这样的动作键对象,将(按键、动作键)对添加到输入映射中。
6、最后,得到顶层组件的动作映射, 将(按键、动作键)添加到映射中

示例代码

package com.java01.day06.test03;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;

/**
 * @description:
 * @author: ju
 * @date: 2020-05-12 13:27
 */
public class ButtonFrame extends JFrame {
    private JPanel buttonPanel;
    private static final int DEFAULT_WIDTH = 300;
    private static final int DEFAULT_HEIGHT = 200;

    public ButtonFrame(){
        buttonPanel = new JPanel();
        setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

        //构造改变背景颜色的动作对象
        ColorAction blueAction = new ColorAction("blue", new ImageIcon(), Color.blue);
        ColorAction yellowAction = new ColorAction("yellow", new ImageIcon(), Color.yellow);
        ColorAction redAction = new ColorAction("red", new ImageIcon(), Color.red);

        //构造一个JButton按钮
        JButton blueButton = new JButton(blueAction);
        JButton yellowButton = new JButton(yellowAction);
        JButton redButton = new JButton(redAction);
        //添加到panel中
        buttonPanel.add(blueButton);
        buttonPanel.add(yellowButton);
        buttonPanel.add(redButton);
        //添加到框架上
        add(buttonPanel);
        //使用getInputMap从组件中得到输入映射
        InputMap inputMap = buttonPanel.getInputMap(JPanel.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        KeyStroke ctrlBKey = KeyStroke.getKeyStroke("ctrl B");
        KeyStroke ctrlYKey = KeyStroke.getKeyStroke("ctrl Y");
        KeyStroke ctrlRKey = KeyStroke.getKeyStroke("ctrl R");
        inputMap.put(ctrlBKey, "panel.blue");
        inputMap.put(ctrlYKey, "panel.yellow");
        inputMap.put(ctrlRKey, "panel.red");

        ActionMap actionMap = buttonPanel.getActionMap();
        actionMap.put("panel.blue", blueAction);
        actionMap.put("panel.yellow", yellowAction);
        actionMap.put("panel.red", redAction);
    }

    public class ColorAction extends AbstractAction {

        public ColorAction(String name, Icon icon, Color c) {
            putValue(Action.NAME, name);
            putValue(Action.SMALL_ICON, icon);
            putValue("color", c);
            putValue(Action.SHORT_DESCRIPTION, "设置颜色到:" + name.toLowerCase());
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            Color c = (Color) getValue("color");
            buttonPanel.setBackground(c);
        }
    }
}

测试

public class Test {
    public static void main(String[] args){
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new ButtonFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setTitle("动作");
                frame.setVisible(true);
            }

        });
        
    }
}

3、鼠标事件

如果用户希望使用鼠标画图,就需要捕获鼠标移动点击和拖动事件。

鼠标第一次被按下时调用mousePressed
鼠标被释放时调用mouseReleased
最后调用mouseClicked

示例代码:

package com.java01.day06.test04;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

/**
 * @description:
 * @author: ju
 * @date: 2020-05-13 09:46
 */
public class MouseComponent extends JComponent {
    private static final int SIDELENGTH = 10;
    private ArrayList<Rectangle2D> square;
    private Rectangle2D current;

    public MouseComponent(){
        square = new ArrayList<>();
        current = null;
        addMouseListener(new MouseHandle());
        addMouseMotionListener(new MouseMotionHandle());
    }

    @Override
    public void paintComponent(Graphics g){
        Graphics2D g2 = (Graphics2D) g;
        for (Rectangle2D rect : square){
            g2.draw(rect);
        }
    }

    public Rectangle2D find(Point2D p){
        for (Rectangle2D rect : square){
            if (rect.contains(p)){
                return rect;
            }
        }
        return null;
    }

    public void add(Point2D p){
        double x = p.getX();
        double y = p.getY();
        current = new Rectangle2D.Double(x - SIDELENGTH/2 , y - SIDELENGTH/2, SIDELENGTH, SIDELENGTH);
        square.add(current);
        repaint();
    }

    public void remove(Rectangle2D rect){
        if (rect == null){
            return;
        }
        if (rect == current){
            current = null;
        }
        square.remove(rect);
        repaint();
    }

    private class MouseHandle extends MouseAdapter {
        @Override
        public void mousePressed(MouseEvent e) {
            //e.getPoint() 返回事件发生时,事件源组件左上角的坐标
            current = find(e.getPoint());
            if (current == null){
                add(e.getPoint());
            }
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            current = find(e.getPoint());
            //e.getClickCount() 返回与事件关联的鼠标连击次数
            if (current != null && e.getClickCount() >= 2){
                //判断如果双击就擦除
                remove(current);
            }
        }
    }

    private class MouseMotionHandle implements MouseMotionListener {

        @Override
        public void mouseDragged(MouseEvent e) {
            if (current != null){
                int x = e.getX();
                int y = e.getY();
                current.setFrame(x - SIDELENGTH/2 , y - SIDELENGTH/2, SIDELENGTH, SIDELENGTH);
                repaint();
            }
        }

        @Override
        public void mouseMoved(MouseEvent e) {
            if (find(e.getPoint()) == null){
                //setCursor 用光标图像设置光标
                setCursor(Cursor.getDefaultCursor());
            }else {
                setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
            }
        }
    }
}
public class MouseFrame extends JFrame {
    public MouseFrame(){
        add(new MouseComponent());
        pack();
    }
}

测试

public class Test {
    public static void main(String[] args){
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new MouseFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setTitle("鼠标事件");
                frame.setVisible(true);
            }
        });
    }
}

运行结果
在这里插入图片描述
4、AWT事件继承层次
在这里插入图片描述
语义事件和低级事件
AWT将事件分为低级事件和语义事件。语义事件是表示用户动作的事件,例如:点击按钮;因此ActionEvent是一种语义事件。低级事件是形容那些事件的事件。在点击按钮时,包含了按下鼠标、连续移动鼠标,抬起鼠标事件。

常用的语义事件:

  • ActionEvent 对应按钮点击、菜单选择、选择列表项或在文本框中ENTER
  • AdjustmentEvent 用户调节滚动条
  • ItemEvent 用户从复选框或列表框中选择一项

常用的低级事件:

  • KeyEvent 一个键被按下或释放
  • MouseEvent 鼠标键被按下、释放、移动或拖动
  • MouseWheelEvent 鼠标滚轮被转动
  • FocusEvent 某个组件获得焦点或失去焦点
  • WindowEvent 窗口状态被改变
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值