事件处理(Java核心技术卷Ⅰ)

7. 事件处理

7.1. 事件处理基础

  • 任何支持GUI的操作环境都要不断地监视按键或者点击鼠标这样的事件。操作环境将这些事件报告给正在运行的应用程序。如果有事件产生,每个应用程序将决定如何对它们做出响应。程序员对相关的特定事件编写代码,并将这些代码放置在过程中,通过人们将它们成为事件过程(event process)。
  • Java AWT所知的事件范围内,完全可以控制事件从事件源(event source)例如,按钮或滚动条,到事件监听器(event listener)的传递过程,并将任何对象指派给事件监听器。
  • 像Java这样的面向对象语言,都将事件的相关信息封装在一个事件对象(event object)中。在Java中,所有的事件对象都最终派生于java.util.EventObject类。
  • 不同的事件源可以产生不同类别的事件。例如,按钮可以发送一个AcitonEvent对象,而窗口可以发送WindowEvent对象。

AWT事件处理机制概要:

  • 监听器对象是一个实现了特定监听器接口(listener interface)的类的实例。
  • 事件源是一个能够注册监听器对象并发送事件对象的对象。
  • 当事件发生时,事件源将事件对象传递给所有注册的监听器。
  • 监听器对象将利用事件对象中的信息决定如何对事件作出响应。
/* 事件委托模型(event delegation model)实例 */
//构造按钮->将按钮添加到面板上->构造监听器->添加动作监听器

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 redButton=new JButton("red");
        JButton blueButton=new JButton("blue");

        buttonPanel=new JPanel();
        //把按钮添加到面板上
        buttonPanel.add(blueButton);
        buttonPanel.add(redButton);
        buttonPanel.add(yellowButton);
        //添加面板到框架里
        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);
    }

    //内部类。才能访问buttonPanel变量
    private class ColorAction implements ActionListener{
        private Color backgroundColor;

        public ColorAction(Color c){
            backgroundColor=c;
        }

        //实现ActionListener接口,监听器类必须有一个actionPerformed方法
        public void actionPerformed(ActionEvent event){
            buttonPanel.setBackground(backgroundColor);
        }

    }
}

创建一个包含方法调用的监听器
假设有一个标签为load的按钮,它的事件处理只包含下面一个方法调用:frame.loadData();
可以使用匿名内部类

loadButton.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent event){
        frame.loadData();
    }
});

改变观感
默认情况下,Swing程序使用Metal观感,可以采用两种方式改变观感。

  1. (没有成功)在Java安装的子目录jre/lib下有一个文件swing.properties.在这个文件中,将swing.defaultlaf设置为所希望的观感类名,如swing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel 。注意,Metal观感位于javax.swing包中。其他的观感包位于com.sun.java包中,并且不是在每个Java实现中都提供。
  2. 动态地改变观感。这需要调用静态的UIManager.setLookAndFeel方法,并提供所想要的观感类名,然后再调用静态方法SwingUtilities.updateComponentTreeUI刷新全部的组件集。
/* 改变观感 */

public class PlafFrame extends JFrame{
    private JPanel buttonPanel;
    public PlafFrame(){
        buttonPanel=new JPanel();

        //获取所有安装的观感实现
        UIManager.LookAndFeelInfo[] infos=UIManager.getInstalledLookAndFeels();
        for(UIManager.LookAndFeelInfo info: infos){
            makeButton(info.getName(), info.getClassName());
        }
        add(buttonPanel);
        pack();
    }

    void makeButton(String name,final String plafName){
        JButton button=new JButton(name);
        buttonPanel.add(button);

        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                try {
                    //调用静态方法,设置观感
                    UIManager.setLookAndFeel(plafName);
                    //调用静态方法,刷新全部的组件集。这里需要向方法提供一个组件,并由此找到其他的所有组件
                    //外部对象的this引用必须将外部类名作为前缀
                    SwingUtilities.updateComponentTreeUI(PlafFrame.this);
                    pack();
                } catch (ClassNotFoundException | InstantiationException
                        | IllegalAccessException
                        | UnsupportedLookAndFeelException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

适配器类

  • 当用户试图关闭一个框架窗口时,JFrame对象就是WindowEvent的事件源。如果希望捕获这个事件,就必须有一个合适的监听器对象,将它添加到框架的窗口监听器列表中。WindowListener listener=...; frame.addWindowListener(listener);
  • 窗口监听器必须是实现WindowListener接口的类的一个对象。在WindowListener接口中包含7个方法。当发生窗口事件时,框架将调用这些方法响应7个不同的事件。
  • 在Java中,实现一个接口的任何类都必须实现其中所有的方法。在这里,意味着需要实现7个方法,然而只对名为windowClosing的方法感兴趣。可以在windowClosing方法增加对System.exit(0)的调用,其他6个方法不做任何事情。
  • 书写6个没有任何操作的方法代码显然是一种乏味的工作。基于简化的目的,每个含有多个方法的AWT监听器接口都配有一个适配器(adapter)类,这个类实现了接口中所有的方法,但每个方法没有做任何事情。这意味着适配器类自动地满足了Java实现相关监听器接口的技术需求。可以通过扩展适配器类来制定对某些事件的相应动作,而不必实现接口中的每个方法(actionListener接口只有一个方法,因此没必要提供适配器类)。
/* 适配器使用实例 */
//最清晰简练的方法

frame.addWindowListener(new WindowAdapter(){
    public void windowClosing(WindowEvent e){
        ...
    }
});

7.2. 动作

Action接口
通常,激活一个命令可以有多种方式。Swing包提供了一种非常实用的机制来封装命令,并将它们连接到多个事件源,这就是Action接口。一个动作是一个封装下列内容的对象:命令的说明(一个文本字符串和一个可选图标) 和 执行命令所需要的参数。

Action接口的方法功能
actionPerformed(ActionEvent e)设置响应动作(Action接口扩展于ActionListener接口)
setEnabled(boolean b)设置启用或禁用这个动作
isEnabled()检查动作当前是否被启用
putValue(String key,Object value)存储动作对象中的任意名/值
getValue(String key)检索动作对象中的任意名/值
addPropertyChangeListener(PropertyChangeListener listener)添加对象属性变化的监听器
removePropertyChangeListener(PropertyChangeListener listener)删除对象属性变化的监听器

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

把动作和击键关联起来

  • 要将动作对象添加到击键中,以便让用户敲击键盘命令来执行这项动作。为了将动作与击键关联起来,首先需要生成KeyStroke类对象。这个类封装了对键的说明。要想生成一个KeyStroke对象,不要调用构造器,而是调用KeyStroke类中的静态getKeyStroke方法:KeyStroke ctrlKey=KeyStroke.getKeyStroke("ctrl B");
  • 用户界面中包含许多按钮、菜单、滚动栏以及其他的组件。当用户敲击键盘时,这个动作会被发送给拥有焦点的组件。用户可以使用TAB键在组件之间移动焦点。当按下SPACE键时,就点击了拥有焦点的按钮。示例中不希望将击键发送给拥有焦点的组件。否则,每个按钮都需要知道如何处理CTRL+Y、CTRL+B、CTRL+R这些组合键。这是一个常见的问题,Swing设计者给出了一种很便捷的解决方案。每个JComponent有三个输入映射(input maps),每一个映射的KeyStroke对象都与动作关联。三个输入映射对应三个不同的条件。WHEN_FOCUSED:当这个组件拥有键盘焦点时激活。 WHEN_ANCESTOP_OF_FOCUSED_COMPONENT:当这个组件包含了拥有键盘焦点的组件时激活。WHEN_IN_FOCUSED_WINDOW:当这个组件被包含在一个拥有键盘焦点组件的窗口中时。可以使用getInputMap方法从组件中得到输入映射。InputMap imap=panel.getInputMap(JComponent.WHEN_FOCUSED);
  • InputMap不能直接地将KeyStroke对象映射到Action对象。而是先映射到任意对象上,然后由ActionMap类实现将对象映射到动作上的第2个映射。这样很容易实现来自不同输入映射的按键共享一个动作的目的。
  • 习惯上,使用字符串none表示空动作。这样可以轻松地取消一个按键动作。imap.put(KeyStroke.getKeyStroke("ctrl C"),"none");

总结:用同一个动作相应按钮、菜单项或按键

  1. 实现一个扩展于AbstractAction类的类。多个相关的动作可以使用同一个类。
  2. 构造一个动作类的对象。
  3. 使用动作对象创建按钮或菜单项。构造器将从动作对象中读取标签文本和图标。
  4. 为了能够通过按键触发动作,必须额外地执行几步操作。首先定位顶层窗口组件,如包含所有其他组件的面板。
  5. 然后得到顶层组件的WHEN_ANCESTOR_OF_FOCUSED_COMPONENT输入映射。为需要的按键创建一个KeyStroke对象。创建一个描述动作字符串这样的动作键对象。将(按键,动作键)对添加到输入映射中。
  6. 最后,得到顶层组件的动作映射。将(动作键,动作对象)添加到映射中。
public class ActionFrame extends JFrame{
    private JPanel buttonPanel;
    private static final int DEFAULT_WIDTH=300;
    private static final int DEFAULT_HEIGHT=200;

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

        Action yellowAction=new ColorAction("Yellow", new ImageIcon("yellow.gif"), Color.YELLOW);
        Action blueAction=new ColorAction("Blue", new ImageIcon("blue.gif"), Color.BLUE);
        Action redAction=new ColorAction("Red", new ImageIcon("red.gif"), Color.RED);

        //用Action对象构造按钮,把动作和按钮关联起来
        buttonPanel.add(new JButton(yellowAction));
        buttonPanel.add(new JButton(blueAction));
        buttonPanel.add(new JButton(redAction));
        add(buttonPanel);

        //得到顶层组件的WHEN_ANCESTOR_OF_FOCUSED_COMPONENT输入映射
        InputMap imap=buttonPanel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        //将(按键,动作键)添加到输入映射中
        imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow");
        imap.put(KeyStroke.getKeyStroke("ctrl B"), "panel.bule");
        imap.put(KeyStroke.getKeyStroke("ctrl R"), "panel.red");

        //得到顶层组件的动作映射
        ActionMap amap=buttonPanel.getActionMap();
        //将(动作键,动作对象)添加到映射中
        amap.put("panel.yellow", yellowAction);
        amap.put("panel.blue", blueAction);
        amap.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(Action.SHORT_DESCRIPTION, "Set panel color to "+name.toLowerCase());//显示在工具提示里
            putValue("color", c);
        }

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

    }

}

7.3. 鼠标事件

  • 如果只希望用户能够点击按钮或菜单,就不需要显式地处理鼠标事件。鼠标操作将由用户界面中的各种组件内部处理。然而,如果希望用户使用鼠标画图,就需要捕获鼠标移动点击和拖动事件。
  • 本部分设计一个简单的图形编辑器应用程序,它允许用户在画布上放置、移动和擦除方块。
  • 当用户点击鼠标按钮时,将会调用3个监听器方法:鼠标第一次被按下时调用mousePressed;鼠标被释放时调用mouseReleased;最后调用mouseClicked。如果只对最终的点击事件感兴趣,就可以忽略前两个方法。用MouseEvent类对象作为参数,调用getX和getY方法可以获得鼠标被按下时鼠标指针所在的x和y坐标。要想区分单击、双击和三击,需要使用getClickCount方法。
public class MouseFrame extends JFrame{
    public MouseFrame(){
        add(new MouseComponent());
        pack();
    }
}

class MouseComponent extends JComponent{
    private static final int SIDELENGTH=10;
    private ArrayList<Rectangle2D> squares;
    private Rectangle2D current;

    public MouseComponent(){
        squares=new ArrayList<>();
        current=null;

        addMouseListener(new MouseHandler());
        addMouseMotionListener(new MouseMotionHandler());
    }

    public void paintComponent(Graphics g){
        Graphics2D g2=(Graphics2D)g;
        for(Rectangle2D r:squares){
            g2.draw(r);
        }
    }

    public Rectangle2D find(Point2D p){
        for(Rectangle2D r:squares){
            if (r.contains(p)) return r;
        }
        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);
        squares.add(current);
        repaint();
    }

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

    //两个独立的接口MouseListener和MouseMotionListener,有利于提高效率。
    //当用户移动鼠标时,只关心鼠标点击的监听器就不会被多余的鼠标移动所困扰。
    private class MouseHandler extends MouseAdapter{
        public void mousePressed(MouseEvent e){
            //getPoint方法返回事件源组件左上角的x y坐标
            //判断该处是否已经绘制图形
            current=find(e.getPoint());
            if (current==null)  add(e.getPoint());
        }

        public void mouseClicked(MouseEvent e){
            current=find(e.getPoint());
            //双击鼠标,擦除方块
            if(current!=null && e.getClickCount()>=2) remove(current);
        }
    }

    private class MouseMotionHandler implements MouseMotionListener{
        //移动鼠标的同时按下鼠标,调用mouseMoved
        public void mouseMoved(MouseEvent e) {
            //光标在一个小方块之上时变成另外一种形状(十字)
            if(find(e.getPoint())==null) setCursor(Cursor.getDefaultCursor());//普通鼠标图标
            else setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));//十字形图标
        }

        //更新光标位置
        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();
            }
        }

    }
}

7.4. AWT事件继承层次

Java事件处理采用的是面向对象方法,所有的时间都是由java.util包中EventObject类扩展而来。EventObject类有一个子类AWTEvent,它是所有AWT事件类的父类。

语义事件和低级事件
AWT将事件分成低级(low-level)时间和语义(semantic)事件。语义事件是表示用户动作的事件,如点击按钮,因此,ActionEvent是一种语义事件。低级事件是形成那些事件的事件。在点击按钮时,包含了按下鼠标、连续移动鼠标、抬起鼠标事件。调节滚动条是一种语义事件,但拖动鼠标是低级事件。
下面是java.awt.event包中最常用的语义事件类:

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

常用的低级事件类是:

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

AWT监听器接口、事件和事件源:

接口方法参数/访问方法事件源
ActionListeneractionPerformedActionEvent
-getActionCommand
-getModifiers
AbstractButton
JComboBox
JTextField
Timer
AdjustmentListeneradjustmentValueChangedAdjustmentEvent
-getAdjustable
-getAdjustmentType
-getValue
JScrollbar
ItemListeneritemStateChangedItemEvent
-getItem
-getItemSelectable
-getStateChange
AbstractButton
JComboBox
FocusListenerfocusGained
focusLost
FocusEvent
-isTemporary
Component
KeyListenerkeyPressed
keyReleased
keyTyped
KeyEvent
-getKeyChar
-getKeyCode
-getKeyModifiersText
-getKeyText
-isActionKey
Component
MouseListenermousePressed
mouseReleased
mouseEntered
mouseExited
mouseClicked
MouseEvent
-getClickCount
-getX
-getY
-getPoint
-transaltePoint
Component
MouseMotionListenermouseDragged
mouseMoved
MouseEventComponent
MouseWheelListenermouseWheelMovedMouseWheelEvent
-getWheelRotation
-getScrollAmount
Component
WindowListenerwindowClosing
windowOpened
windowIconified
windowDeiconified
windowClosed
windowActivated
windowDeactiovated
WindowEvent
-getWindow
Window
WindowFocusListenerwindowGainedFocus
windowLostFocus
WindowEvent
-getOppositeWindow
Window
WindowStateListenerwindowStateChangedWindowEvent
-getOldState
-getNewState
Window
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值