您的位置:首页 > 编程语言 > Java开发

Java学习笔记(6):事件处理

2013-03-03 12:21 435 查看
事件处理基础

像Java这样的面向对象语言,都将事件的相关信息封装在一个事件对象(event object)中。

在Java中,所有的事件对象都最终派生于java.util.EventObject类。每个事件类型还有一个子类,例如,ActionEvent和WindowEvent。

不同的事件源可以产生不同类别的事件,例如,按钮可以发送一个ActionEvent对象,而窗口可以发送WindowEvent对象。

AWT事件处理机制的概要:

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

监听器示例:

ActionListener listener = ...;
JButton button = JButton("Ok");
button.addActionListener(listener);

只要按钮产生一个“动作事件”,listener对象就会得到通告。

为了实现ActionListener接口,监听器类必须有一个被称为actionPerformed的方法,该方法接收一个ActionEvent对象参数:

class MyListener implements ActionListener
{
...
public void actionPerformed(ActionEvent event)
{
// reaction to button click goes here
...
}
}

只要用户点击按钮,JButton对象就会创建一个ActionEvent对象,然后调用listener.actionPerformed(event)传递事件对象。

实例:处理按钮点击事件

可以通过在按钮构造器中指定一个标签字符串、一个图标或两项都指定来创建一个按钮:

JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton(new ImageIcon("blue-ball.gif"));

将按钮添加到面板中需要调用add方法:

JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("Blue");
JButton redButton = new JButton("Red");

buttonPanel.add(yellowButton);
buttonPanel.add(blueButton);
buttonPanel.add(redButton);

接下来需要增加让面板监听按钮的代码,这需要一个实现了ActionListener接口的类,其应该包含图个actionPerformed方法,其签名为:

public void actionPerformed(ActionEvent event)

当按钮被点击时,希望将面板的背景颜色设置为指定的颜色,这个颜色存储在监听器类中:

class ColorAction implements ActionListener
{
public ColorAction(Color c)
{
backgroundColor = c;
}

public void actionPerformed(ActionEvent event)
{
// set panel background color
...
}
}

然后,为每种颜色构造一个对象,并将这些对象设置为按钮监听器:

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);

这里还有一个需要考虑的问题。ColorAction对象不能访问buttonpanel变量。可以采用两种方式解决这个问题。

一个是将面板存储在ColorAction对象中,并在ColorAction的构造器中设置它;

另一个是将ColorAction作为ButtonFrame类的内部类。

这里使用第二种方法:

class ButtonPanel extends JFrame
{
...
private class ColorAction implements ActionListener
{
...
public void actionPerformed(ActionEvent event)
{
buttonPanel.setBackground(backgroundColor);
}

private Color backgroundColor;
}
private JPanel buttonPanel;
}


建议使用内部类

每个按钮的处理过程都是一样的:

用标签字符串构造按钮
将按钮添加到面板上
用对应的颜色构造一个动作监听器
添加动作监听器

为了简化这些操作过程,可以设计一个辅助方法:

public void makeButton(String name, Color backgroundColor)
{
JButton button = new JButton(name);
buttonPanel.add(button);
ColorAction action = new ColorAction(backgroundColor);
button.addActionListener(action);
}

然后简化调用:

makeButton("yellow", Color.YELLOW);
makeButton("blue", Color.BLUE);
makeButton("red", Color.RED);

进一步进行简化,注意,ColorAction类只在makeButton方法中使用一次。因此,可以将它设计为一个匿名类:

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


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

假设有一个标签为Load的按钮,它的事件处理只包含下面一个方法调用:

frame.loadData();

当然,可以使用匿名内部类:

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

但是,EventHandler类可以使用下列调用,自动地创建这样一个监听器:

EventHandler.create(ActionListener.class, frane, "loadData")

当然,仍然需要安装处理器:

loadButton.addActionListener(
EventHandler.create(ActionListener.class, frame, "loadData"));

如果事件监听器调用的方法只包含一个从事件处理器继承来的参数,就可以使用另外一种形式的create方法,例如,调用

EventHandler.create(ActionListener.class, frame, "loadData", "source.text")

等价于

new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
frame.loadData(((JTextField)event.getSource()).getText());
}
}

需要将属性source和text的名字转换成方法调用getSource和getText。

实例:改变观感

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

第一种方式是在Java安装的子目录jre/lib下有一个文件swing.properties。在这个文件中,将属性swing.defaultlaf设置为所希望的观感类名。例如,

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

第二种方式是动态地改变观感。这需要调用静态的IManager.setLookAndFeel方法,并提供所想要的观感类名,然后再调用静态方法SwingUtilities.updateComponentTreeUI来刷新全部的组件集。这里需要向这个方法提供一个组件,并由此找到其他的所有组件。

下面是示例,显示如何用程序切换至Motif观感:

String plaf = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
try
{
UIManager.setLookAndFeel(plaf);
SwingUtilities.updateComponentTreeUI(panel);
}
catch(Exception e) { e.printStackTrace(); }

然后采用下列方式得到每一种观感的名字和类名:

String name = infos[i].getName();
String className = infos[i].getClassName();


适配器类

当程序用户试图关闭一个框架窗口时,JFrame对象就是WindowEvent的事件源。如果希望捕获这个事件,就必须有一个合适的监听器对象,并将它添加到框架的窗口监听器列表中:

WindowListener listener = ...;
frame.addWindowListener(listener);

窗口监听器必须是实现WindowListener接口的类的一个对象。在WindowListener接口中包含7个方法。当发生窗口事件时,框架将调用这些方法响应7个不同的事件。下面是完整的WindowListener接口:

public interface WindowListener
{
void windowOpened(WindowEvent e);
void windowClosing(WindowEvent e);
void windowClosed(WindowEvent e);
void windowIconified(WindowEvent e);
void windowDeiconified(WindowEvent e);
void windowActivated(WindowEvent e);
void windowDeactivated(WindowEvent e);
}

可以这样定义实现这个接口的类:在windowClosing方法中增加一个对System.exit(0)的调用,其他6个方法不做任何事情:

class Terminator implements WindowListener
{
public void windowClosing(WindowEvent e)
{
if (user agrees)
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) {}
}

书写6个没有任何操作的方法是乏味的工作。鉴于简化的目的,每个含有多个方法的AWT监听器接口都有一个适配器类,这个类实现了接口中的所有方法,但每个方法没有做任何事情。可以通过扩展适配器类来指定对某些事件的响应动作,而不必实现接口中的每个方法。

下面使用窗口适配器。首先定义一个WIndowAdapter类的扩展类,其中包含继承的6个没有做任何事情的方法和一个覆盖的方法windowClosing:

class Terminator extends WindowAdapter
{
public void windowClosing(WindowEvent e)
{
if (user agrees)
System.exit(0);
}
}

现在,可以将一个Terminator对象注册为事件监听器:

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

事实上,没有必要为listener对象命名。只需写成:

frame.addWindowListener(new Terminator);

进一步,可以将监听器类定义为框架的匿名内部类:

frame.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
if (user agrees)
System.exit(0);
}
});


动作

Swing包提供了一种非常实用的机制来封装命令,并将它们连接到多个事件源,这就是Action接口。

一个动作是一个封装下列内容的对象:

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

Action接口包含下列方法:

void actionPerformed(ActionEvent event)
void set Enabled(boolean b)
boolean isEnabled()
void putValue(String key, Object value)
Object getValue(String key)
void addPropertyChangeListener(PropertyChangeListener listener)
void removePropertyChangeListener(PropertyChangeListener listener)

Action接口扩展于ActionListener接口,因此,可以在任何需要ActionListener对象的地方使用Action对象。

putValue和getValue方法允许存储和检索动作对象中的任意名/值。有两个重要的预定义字符串:Action.NAME和Action.SMALL_ICON,用于将动作的名字和图标存储到一个动作对象中:

action.putValue(Action.NAME, "Blue");
action.putValue(Action.SMALL_ICON, ne ImageIcon("blue-ball.gif"));


预定义动作表名称

名称
NAME动作名称,显示在按钮和菜单上
SMALL_ICON存储小图标的地方;显示在按钮、菜单项或工具栏中
SHORT_DESCRIPTION图标的简要说明;显示在工具提示中
LONG_DESCRIPTION图标的详细说明;使用在在线帮助中。没有Swing组件使用这个值
MNEMONIC_KEY快捷键的缩写;显示在菜单项中
ACCELERATOR_KEY存储加速击键的地方;Swing组件不使用这个值
ACTION_COMMAND_KEY历史遗留;仅在旧版本的registerKeyboardAction方法中使用
DEFAULT常用的综合属性;Swing组件不使用这个值
Action接口的最后两个方法能够让其他对象在动作对象的属性发生变化时得到通告,尤其是菜单或工具栏触发的动作。

注意,Action是一个接口,而不是一个类。实现这个接口的所有类都必须实现上述七个方法。庆幸的是,有一个AbstractAction类实现了这个接口除actionPerformed方法之外的所有方法。这个类存储了所有名/值对,并管理着属性变更监听器。我们可以直接扩展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, "Set panel color to " + name.toLowerCase());
}

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

为了将动作与击键关联起来,首先需要生成KeyStroke类对象,它封装了对键的说明。要想生成KeyStroke对象,不要调用构造器,而是调用KeyStroke类中的静态getKeyStroke方法:

KeyStroke ctrlBKey = KeyStroke.getKeyStroke("ctrl B");

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

每个JComponent有三个输入映射(input maps),每个映射的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 imap = panel.getInputMap(JComponent.WHEN_FOCUSED);

每个组件都可以有三个输入映射和一个动作映射,为了将它们组合起来,需要为动作命名。下面是将键与动作关联起来的方式:

imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow");
ActionMap amap = panel.getActionMap();
amap.put("panel.yellow", yellowAction);

习惯上,使用字符串none表示空动作。这样可以轻松地取消一个键动作:

imap.put(KeyStroke.getKeyStroke("ctrl C"), "none");

总结一下使用同一个动作响应按钮、菜单或击键的方式:

1)实现一个扩展于AbstractAction类的类。多个相关动作可以使用同一个类。

2)构造一个动作类的对象。

3)使用动作对象创建按钮或菜单项。构造器将从动作对象中读取标签文本和图标。

4)为了能够通过击键触发动作,必须额外执行几部操作。首先定位顶层窗口组件。

5)然后,得到顶层组件的WHEN_ANCESTOR_OF_FOCUS_COMPONENT输入映射。为需要的击键创建一个KeyStroke对象。创建一个描述动作字符串这样的动作键对象。将(击键, 动作键)对添加到输入映射中。

6)最后,得到顶层组件的动作映射。将(动作键, 动作对象)添加到映射中。

鼠标事件

当用户点击鼠标按钮时,将会调用三个监听器方法:鼠标第一次被按下时调用mousePressed;鼠标释放时调用mouseReleased;最后调用mouseClicked。如果只对最终的点击事件感兴趣,可以忽略前两个方法。用MouseEvent类对象作为参数,调用getX和getY方法可以获得鼠标被按下时鼠标指针所在的x和y坐标。要想区分单击、双击和三击,需要使用getClickCount方法。

当鼠标在窗口上移动时,窗口将会收到一连串的鼠标移动事件。注意,有两个独立的接口MouseListener和MouseMotionListener。当用户移动鼠标时,只关心点击(click)的监听器就不会被多余的鼠标移动(moves)所困扰。

可以利用Toolkit类中的createCustomCursor方法自定义光标类型:

Toolkit tk = Toolkit.getDefaultToolkit();
Image img = tk.getImage("dynamite.gif");
Cursor dynamiteCursor = tk.createCustomCursor(img, new Point(10, 10), "dynamite stick");

如果用户在移动鼠标的同时按下鼠标,就会调用moveMoved而不是调用mouseDragged。

只有鼠标在一个组件内部停留才会调用mouseMoved方法。然而,即使鼠标拖动到组件外面,mouseDragged也会被调用。

还有两个鼠标事件方法:mouseEntered和mouseExited。这两个方法是在鼠标进入或移出组件时被调用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: