- 大纲:
- Java事件处理的基本方法。
- Java AWT事件模型的工作机制。
- 如何以更加结构化的方式处理动作事件。
- 事件处理基础:
- 事件委托模型: 在AWT所知的事件范围内,完全可以控制事件从 事件源(如:按钮、滚动条)到 事件监听器 的传递过程,并将任何对象 指派 给事件监听器。
- 像Java这样的面向对象语言,都将事件的相关信息封装在一个 事件对象(Event Object)。在Java中,所有的事件对象都最终派生于 java.util.EventObject类。
- AWT事件处理机制的概要:
- 监听器对象是一个实现了 特定 监听器接口 的类的实例。
- 事件源是一个能够 注册 监听器对象 并 发送 事件对象 的对象。
- 当事件发生时,事件源将 事件对象 传递给所有注册的监听器。
- 监听器对象 利用 事件对象 中的信息决定如何对事件做出响应。
- 可以将多个监听器对象添加到一个像按钮这样的事件源中。只要用户点击按钮,按钮就会创建一个ActionEvent对象并调用所有监听器的actionPerformed方法(传入按钮创建的ActionEvent对象)。
- ActionListener接口并不仅限于按钮点击事件:
- 鼠标双击
- 选择菜单项
- 文本域中按回车
- Timer组件的到达指定的时间间隔
- 事件监听器对象通常需要执行一些对其他对象可能产生影响的操作。可以策略性地将监听器类放置在需要改变状态的哪个类中。(将监听器类作为某个类的内部类)
- 实例程序:(按相应的颜色按钮改变相应的背景颜色,但在按与背景颜色相同的颜色颜色按钮则恢复背景为默认颜色(原功能上稍加改动))
package button;
import java.awt.*;
import javax.swing.*;
public class ButtonTest {
public static void main( String[] args ) {
EventQueue.invokeLater(()->{
ButtonFrame frame = new ButtonFrame();
frame.setTitle("Button Frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
package button;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class ButtonFrame extends JFrame{
private JPanel buttonPanel;
private Color originalColor;
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 200;
public ButtonFrame() {
buttonPanel = new JPanel();
originalColor = buttonPanel.getBackground();
setSize( DEFAULT_WIDTH, DEFAULT_HEIGHT );
JButton yellowButton = new JButton("Yellow");
JButton redButton = new JButton("Red");
JButton blueButton = new JButton("Blue");
add(buttonPanel);
buttonPanel.add(yellowButton);
buttonPanel.add(redButton);
buttonPanel.add(blueButton);
yellowButton.addActionListener( new ColorAction(Color.YELLOW) );
redButton.addActionListener(new ColorAction(Color.RED));
blueButton.addActionListener(new ColorAction(Color.BLUE) );
}
private class ColorAction implements ActionListener{
private Color backgroundColor;
public ColorAction( Color c ) {
backgroundColor = c;
}
public void actionPerformed( ActionEvent event ) {
if( buttonPanel.getBackground().equals(backgroundColor) ) {
buttonPanel.setBackground(originalColor);
}else {
buttonPanel.setBackground(backgroundColor);
}
}
}
}
- 简洁地指定监听器:
- 更常见的情况是:每个监听器执行 一个 单独 的动作。这种情况下,没必要建立单独的类,只需要使用一个 lambda表达式。
- 较老的代码中,构造监听器可能会用到匿名类。(相对繁琐)
- 还可以创建实现了 ActionListener接口 的 事件源容器,再设置自身(this)作为监听器。(不建议)
- EventHandler类创建监听器(借助反射)。EventHandler.create( ActionListener.class, frame, "loadData" )。(效率不高,容易出错)
- java.awt.event.ActionEvent
- String getActionCommand() //返回与动作事件关联的命令字符串。
- java.util.EventObject
- Object getSource() //返回 发生这个事件的对象 的一个引用
- 实例:改变观感
- 在默认情况下,Swing程序使用 Metal观感。
- 可以采用两种方式改变观感:
- Java安装子目录jre/lib下的文件 swing.properties。可在此文件中,将 swing.defaultaf 设置为所希望的观感类名。
- 动态改变观感。需要调用 静态的 UIManager.setLookAndFeel方法,并提供所想要的观感类名,然后再调用静态方法 SwingUtilities.updateComponentTreeUI 来刷新全部的部件集。
- javax.swing.UIManager
- static UIManager.LookAndFeelInfo[] getInstalledLookAndFeels()
- static setLookAndFeel(String className)
- javax.swing.UIManager.LookAndFeelInfo
- String getName()
- String getClassName()
- javax.swing.SwingUtilities
- public static void updateComponentTreeUI(Component c)
- 示例程序:(实现通过按按钮切换观感)
package plaf;
import java.awt.*;
import javax.swing.*;
public class PlafFrameTest {
public static void main( String[] args ) {
EventQueue.invokeLater(()->{
PlafFrame frame = new PlafFrame();
frame.setTitle("PlafFrame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
package plaf;
import java.awt.*;
import javax.swing.*;
public class PlafFrame extends JFrame {
private JPanel panel;
public PlafFrame() {
panel = new JPanel();
UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();
for( UIManager.LookAndFeelInfo i : infos ) {
makeButton( i.getName(), i.getClassName() );
}
add(panel);
pack();
}
private void makeButton( String name, String className ) {
JButton button = new JButton(name);
panel.add(button);
button.addActionListener(event->{
try {
UIManager.setLookAndFeel(className);
SwingUtilities.updateComponentTreeUI(this);
pack();
}catch( Exception e ) {
e.printStackTrace();
}
});
}
}
- 适配器类
- 当用户试图关闭一个框架窗口时,JFrame对象 就是 WindowEvent 的事件源。如果需要捕获这个事件,就需要一个窗口监听器。
- 窗口监听器必须是实现 WindowListener接口 的类的对象。WindowListener接口中总共7个方法。
- 每个含有多个方法的AWT监听器接口都配有一个适配器(adapter)类,这个类实现了接口中的所有方法,但每个方法没有做任何事。有了适配器类,我们就可以通过扩展适配器类,只覆盖自己需要的方法,就不必把所有(不需要的)方法实现了。
- java.awt.event.WindowStateListener
- void windowStateChanged( WindowEvent event ) //窗口状态改变的时候调用这个方法
- java.awt.event.WindowEvent
- int getNewState()
- int getOldState()
- Frame.NORMAL
- Frame.ICONIFIED
- Frame.MAXIMIZED_HORIZ
- Frame.MAXIMIZED_VERT
- Frame.MAXIMIZED_BOTH
- java.awt.event.WindowListener
- void windowOpened( WindowEvent event )
- void windowClosing( WindowEvent event )
- void windowClosed( WindowEvent event )
- void windowIconified( WindowEvent event )
- void windowDeiconified( WindowEvent event )
- void windowActivated( WindowEvent event )
- void windowDeactivated( WindowEvent event )
- 动作:
- 通常,激活一个命令可以有多种方式。将所有事件连接到同一个监听器上。
- Swing包提供了一种非常实用的机制来封装命令,并将它们连接到多个事件源,这就是 Action接口。
- 一个 动作 是一个封装下列内容的对象:
- 命令的说明(一个文本字符串和一个可选图标)
- 执行命令所需要的参数
- 实际上,Action接口 扩展于 ActionListener接口。
- javax.swing.Action
- void actionPerformed( ActionEvent event )
- void setEnable( boolean b )
- boolean isEnable()
- void putValue( String key, Object value ) //允许存储和检索动作对象的任意名/值。
- Object getValue( String key )
- NAME 动作名称
- SMALL_ICON 存储小图标的地方(按钮 菜单栏 或 工具栏中)
- SHORT_DESCRIPTION 图标的简要说明(工具提示)
- LONG_DESCRIPTION 图标的详细说明(在线帮助)
- MNEMONIC_KEY 快捷键的缩写(菜单项中)
- ACCELERATOR_KEY
- ACTION_COMMAND_KEY
- DEFAULT
- void getPropertyChangeListener( PropertyChangeListener listener )
- void removePropertyChangeListener( PropertyChangeListener listener )
- 有一个类实现了 Action接口 除了 actionPerformed方法 之外的所有方法,即 AbstractAction类。
- 将动作对象添加到 击键 中:
- 生成 KeyStroke类对象,调用静态 getKeyStroke方法。
- 每个JComponent有三个输入映射(input map)
- WHEN_FOCUSED
- WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
- WHEN_IN_FOCUSED_WINDOW
- InputMap , ActionMap 。
- 习惯上,实用字符串none表示空动作。
- javax.swnig.JComponent
- ActionMap getActionMap()
- InputMap getInputMap( int flag )
- 总结(用同一个动作响应按钮、菜单项或按键的方式):
- 实现一个扩展于 AbstractAction的类。
- 构造一个 动作类 的对象。
- 使用动作对象构造按钮或菜单项。构造器可从动作对象中读取标签文本和图标。
- 按键触发动作:
- 定位顶层窗口组件
- 得到顶层组件的输入映射(WHEN_ANCESTOR_OF_FOCUS_COMPONENT)。创建一个KeyStroke对象,并通过 输入映射(InputMap) 将 keyStroke对象 与一个 字符串str 建立映射关系。
- 通过 ActionMap 将 字符串str(上面所建立的) 与 一个动作对象 建立映射关系。
- javax.swing.KeyStroke
- static KeyStroke getKeyStroke( String description ) //以空格分隔,以0或多个修饰符开始(shift control ctrl meta alt altGraph)..."typed + ?" 或 "pressed"(默认) 或 "released"紧跟一个键码结束。...
<modifiers>* (<typedID> | <pressedReleasedID>)。
- static KeyStroke getKeyStroke( String description ) //以空格分隔,以0或多个修饰符开始(shift control ctrl meta alt altGraph)..."typed + ?" 或 "pressed"(默认) 或 "released"紧跟一个键码结束。...
- 示例程序:(按钮 击键 触发面板换色)
package action;
import java.awt.*;
import javax.swing.*;
public class ActionFrameTest {
public static void main( String[] args ) {
EventQueue.invokeLater(()->{
ActionFrame frame = new ActionFrame();
frame.setTitle("Action Frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
package action;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class ActionFrame extends JFrame {
private JPanel panel;
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 200;
public ActionFrame() {
setSize(DEFAULT_WIDTH,DEFAULT_HEIGHT);
panel = new JPanel();
//define actions
Action yellowAction = new ColorAction("Yellow", Color.YELLOW);
Action blueAction = new ColorAction("Blue", Color.BLUE);
Action redAction = new ColorAction("Red", Color.RED);
//add buttons for these actions
panel.add(new JButton(yellowAction));
panel.add(new JButton(blueAction));
panel.add(new JButton(redAction));
//add panel to frame
add(panel);
//associate the Y, B and R keys with names
InputMap imap = panel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow");
imap.put(KeyStroke.getKeyStroke("ctrl B"), "panel.blue");
imap.put(KeyStroke.getKeyStroke("ctrl R"), "panel.red");
//associate the names with actions
ActionMap amap = panel.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, 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 event ) {
Color c = (Color) getValue("color");
panel.setBackground(c);
}
}
}
- 鼠标事件
- java.awt.event.MouseEvent
- int getX()
- int getY()
- Point getPoint()
- int getClickCount()
- java.awt.event.InputEvent
- int getModifiersEx() //使用下面的掩码值来检测返回值
- BUTTON1_DOWN_MASK
- BUTTON2_DOWN_MASK
- BUTTON3_DOWN_MASK
- SHIFT_DOWN_MASK
- CTRL_DOWN_MASK
- ALT_DOWN_MASK
- ALT_GRAPH_DOWN_MASK
- META_DOWN_MASK
- static String getModifiersExText( int modifiers )
- int getModifiersEx() //使用下面的掩码值来检测返回值
- java.awt.Toolkit
- public Cursor createCustomCursor( Image image, Point hotSpot, String name )
- java.awt.Component
- public void setCursor( Cursor cursor )
- 有两个独立的接口: MouseListener 和 MouseMotionListener 。点击监听器 和 移动监听器, 有利于提供效率。
- 光标形状样例:(P461 表11-3 10th)。Cursor类的getPredefinedCursor方法。
- 还可以利用 Toolkit类中的 createCustomCursor方法 来自定义光标类型。
- 示例程序:(鼠标事件测试:单击添加方块,可拖动,双击删除方块)
- java.awt.event.MouseEvent
package mouse;
import javax.swing.JFrame;
import java.awt.EventQueue;
public class MouseFrame extends JFrame {
public static void main( String[] args ) {
EventQueue.invokeLater(()->{
MouseFrame frame = new MouseFrame();
frame.setTitle("MouseFrame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
public MouseFrame() {
add( new MouseComponent() );
pack();
}
}
package mouse;
import javax.swing.JComponent;
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import java.awt.event.*;
public class MouseComponent extends JComponent {
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 200;
private static final int SLIDE_WIDTH = 10;
private ArrayList<Rectangle2D> squares;
private Rectangle2D current;
public MouseComponent() {
setSize( DEFAULT_WIDTH, DEFAULT_HEIGHT );
squares = new ArrayList<>();
current = null;
addMouseListener( new MouseHandler() );
addMouseMotionListener( new MouseMotionHandler() );
}
public void paintComponent(Graphics g ) {
Graphics2D graph = (Graphics2D) g;
for( Rectangle2D r : squares ) graph.draw(r);
}
public Dimension getPreferredSize() { return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT); }
public Rectangle2D find( Point2D p ) {
for( Rectangle2D rect : squares ) {
if( rect.contains(p)) return rect;
}
return null;
}
public void add( Point2D p ) {
if( find(p) == null ) {
double x = p.getX();
double y = p.getY();
current = new Rectangle2D.Double(x-SLIDE_WIDTH/2, y-SLIDE_WIDTH/2, SLIDE_WIDTH, SLIDE_WIDTH);
squares.add(current);
repaint();
}
}
public void remove( Rectangle2D rect ) {
if( rect == null ) return;
if( rect == current ) current = null;
squares.remove(rect);
repaint();
}
private class MouseHandler extends MouseAdapter{
public void mousePressed( MouseEvent event ) {
current = find(event.getPoint());
if( current == null ) add(event.getPoint());
}
public void mouseClicked( MouseEvent event ) {
current = find(event.getPoint());
if( current != null && event.getClickCount() >= 2 ) remove(current);
}
}
private class MouseMotionHandler extends MouseMotionAdapter{
public void mouseMoved( MouseEvent event ) {
if( find(event.getPoint()) == null ) setCursor(Cursor.getDefaultCursor());
else setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
}
public void mouseDragged( MouseEvent event ) {
if( current != null ) {
int x = event.getX();
int y = event.getY();
current.setFrame(x-SLIDE_WIDTH/2, y-SLIDE_WIDTH/2, SLIDE_WIDTH, SLIDE_WIDTH);
repaint();
}
}
}
}
- AWT事件继承层次
- 所有的事件都是由 java.util包 中的 EventObject类 扩展而来的。
- EventObject类有一个子类 AWTEvent 。它是所有 AWT事件类 的父亲。
- 事件对象 封装了 事件源 与 监听器 彼此通信的事件信息。
- AWT将事件分为 底层事件 和 语义事件。
- 语义事件是 表示用户动作的事件,如:按下按钮。
- 底层事件是 形成那些事件的事件,如:点击按钮时,包含 按下鼠标、连续移动鼠标、抬起鼠标事件。
- java.awt.event包中最常用的语义事件类:
- ActionEvent (按钮点击 菜单选择 选择列表项 在文本框中ENTER)
- AdjustmentEvent (滚动条)
- ItemEvent (从复选框或列表框中选择一项)
- 常用的5个底层事件
- KeyEvent
- MouseEvent
- MouseWheelEvent
- FocusEvent
- WindowEvent
- 以上事件都有对应的监听器接口。P467 10th。
- javax.swing.event包中,包含了许多专门用于Swing组件的附加事件。