1、事件学习(1)监听器
(1)与事件相关的几个概念
事件源:发生某个情况(事件)的地方(组件)
事件:描述一种情况,比如:鼠标点击,键盘按下某个键,.......
监听器:用于监控事件的发生;不要在监听器中的事件处理方法内编写耗时操作,否则界面将卡死直至耗时操作完毕;因为在
swing中对UI控制和事件处理是同一个线程中进行
(2)事件委托模型
事件源产生事件,监听器监听事件,并做出相应的处理。以响应按钮点击事件为例,在一个面板中放置3个按钮,添加3个监听器对象
作为按钮的动作监听器。
下例中相关的API:
JButton的API:
JButton(String label) 构造一个按钮,label可看作按钮名
JButton(Icon icon)
JButton(String label, Icon icon)
Container的API:
Component add(Component c) 将组件c添加到该容器中
package GUI_Event;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class ButtonFrame extends JFrame{
private JPanel buttonPanel;
private static final int DEFAULT_WIDTH = 800;
private static final int DEFAULT_HEIGHT = 600;
public ButtonFrame() {
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
//create buttons
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("Blue");
JButton redButton = new JButton("Red");
buttonPanel = new JPanel();
//add buttons to panel
buttonPanel.add(yellowButton);
buttonPanel.add(blueButton);
buttonPanel.add(redButton);
//add panel to frame
add(buttonPanel);
//associate actions with buttons
yellowButton.addActionListener(new ColorAction(Color.YELLOW));
blueButton.addActionListener(new ColorAction(Color.BLUE));
redButton.addActionListener(new ColorAction(Color.RED));
}
/**
* 一个监听器,监听多种情况
*监听器作为内部类,是为了能够容易的访问到buttonPanel
*/
private class ColorAction implements ActionListener{
private Color backgroundColor;
public ColorAction(Color c) {
backgroundColor = c;
}
@Override
public void actionPerformed(ActionEvent e) {
buttonPanel.setBackground(backgroundColor);
}
}
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.setVisible(true);//显示窗口
}
});
}
}
(3)由上面实例理解事件处理机制
1、按钮是事件源,点击按钮是事件;点击按钮的事件是一个动作事件,事件源在点击按钮后,会发送一个ActionEvent对象;
与之相对的还有窗口可以发送WindowEvent对象
2、根据发送的对象不同,需要不同类别的监听器去监听事件源。本例中的ColorAction就需要实现特定的监听器接口
ActionListener接口
3、事件源可以注册一个监听器对象,即安排一个监听器对象去监听事件这个事件源,在注册监听器时,可以通过参数让监听
器实现需要达到的响应事件的效果。本例中就是通过传递Color参数,让监听器实现对应的效果。
(4)对上述实例的解读
1、生成3个按钮,添加到buttonPanel(按钮面板)中,再将按钮面板添加到JFrame(框架)中
2、由于按钮产生的事件类型是动作事件,所以自定义监听器应该实现ActionListener接口
3、每个按钮都需要注册一个ColorAction监听器对象,但是通过其构造方法的参数能够实现对不同按钮产生的相同事件做出不
同的响应
4、ColorAction自定义监听器中,由于实现了ActionListener接口,必须要实现其唯一的actionPerformed(ActionEvent e)方
法,由于其中需要设置buttonPanel的颜色变化,需要访问到这个属性,则令ColorAction为内部类。
(5)对上述实例的改进——lambda表达式
lambda表达式使用的好处是不需要建立一个单独的类,去实现要执行的动作。
lambda表达式使用,比如:exitButton.addActionListener( event -> System.exit(0) );
package GUI_Event;
import java.awt.Color;
import java.awt.EventQueue;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
@SuppressWarnings("serial")
public class ButtonFrame3 extends JFrame{
private JPanel buttonPanel;
private static final int DEFAULT_WIDTH = 800;
private static final int DEFAULT_HEIGHT = 600;
public ButtonFrame3() {
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
buttonPanel = new JPanel();
this.makeButton("yellow", Color.YELLOW);
this.makeButton("blue", Color.BLUE);
this.makeButton("red", Color.RED);
add(buttonPanel);
}
public void makeButton(String name, Color backgroundColor) {
JButton button = new JButton(name);
buttonPanel.add(button);
//为当前按钮绑定监听事件
button.addActionListener(event -> buttonPanel.setBackground(backgroundColor));
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new ButtonFrame3();//初始化窗口
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置:关闭窗口,则程序退出
frame.setVisible(true);//显示窗口
}
});
}
}
改进的点在哪?
改进前,自定义一个监听器类,3个按钮就要实例化3个监听器,还要在监听器中实现响应事件的方法。
改进后,多了makeButton方法,取消了自定义的监听器类,makeButton方法的功能是创建一个按钮,添加到buttonPanel中去,再通
过lambda表达式,将对于事件的响应直接注入到一个监听器中。也就是每调用一次makeButton方法,就会产生一个按钮,并且给这
个按钮绑定一个监听器,监听器的响应事件也写好了。代码更简洁,逻辑更清晰,更加面向对象。
(6)另外一种监听事件
刚刚说了按钮产生的是动作事件,会发出ActionEvent对象,对应的监听器需要实现ActionListener接口,该接口中只有一个方法,是
actionPerformed(ActionEvent e),那么自定义监听器类就只需要实现这一个方法。
但是在另外一种情况下,窗口产生的是窗口事件,发出的是WindowEvent对象,对应的监听器就要实现WindowListener接口,而该
接口中却有7个方法。我们在多数情况下只需要实现其中1-2个方法,就能达到自己要求,但却必须实现其它方法,显得代码冗余。在
这种情况下,我们自定义的监听器可以继承WindowAdapter类,它已经实现了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);//窗口变为未激活状态后调用该方法
}
一般结合匿名内部类使用:
//是框架frame产生了窗口事件 frame.addWindowListener( new WindowAdapter(){ public void windowClosing(WindowEvent e){ if(user agrees) System.exit(0); } } );
(7)对上面内容的总结:
1、实现ActionListener接口,去监听动作事件,并将如何响应对应事件的代码封装在actionPerformed方法中;
2、可以取消自定义监听器类,通过lambda表达式代替;lambda表达式会自动的将事件源和某个监听器绑定,将对事件的响
应作为参数;
3、继承适配器类WindowAdapter去实现对窗口事件的监听。
2、事件学习(2)动作
常见的一种情况:点击blueButton和快捷键Ctrl+B都能实现让窗口背景色变蓝。要实现该情况需要用到动作对象,通过实现Action接
口。注意:Action接口扩展于ActionListener接口。其方法如下:
void actionPerformed(ActionEvent e) //封装对事件的响应代码
void setEnabled(blooean b) //设置这个动作是否启用
boolean isEnabled() //检查这个动作当前是否启用
void putValue(String key, Object value) //储存一个键值对到动作对象中
Object getValue(String key) //返回被存储的键值对的值
void addPropertyChangeListener(PropertyChangeListener listener) //让其它对象在动作对象的属性发生变化时得到通告
void removePropertyChangeListener(PropertyChangeListener listener) //让其它对象在动作对象的属性发生变化时得到通告
Action接口也有一个适配器类AbstractAction类,其actionPerformed方法必须重写,其它方法根据需要决定。
package actionTest;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
public class ActionFrame extends JFrame{
private JPanel buttonPanel;
public ActionFrame() {
setSize(300,200);
buttonPanel = new JPanel();
//定义动作对象
Action yellowAction = new ColorAction("Yellow", new ImageIcon(""), Color.YELLOW);
Action blueAction = new ColorAction("Blue", new ImageIcon(""), Color.BLUE);
Action redAction = new ColorAction("Red", new ImageIcon(""), Color.RED);
//设置按钮和动作对象绑定
//创建一个按钮,其中的属性从提供的动作对象中获取
buttonPanel.add(new JButton(yellowAction));
buttonPanel.add(new JButton(blueAction));
buttonPanel.add(new JButton(redAction));
add(buttonPanel);
//设置快捷键和动作对象绑定
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.blue");
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);
}
//内部类,作用是为了访问到buttonPanel这个属性
private 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);
}
@Override
public void actionPerformed(ActionEvent e) {
Color c = (Color) getValue("color");
buttonPanel.setBackground(c);
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new ActionFrame();//初始化窗口
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置:关闭窗口,则程序退出
frame.setVisible(true);//显示窗口
}
});
}
}
3、事件学习(3)鼠标事件
我们在点击按钮等组件时,鼠标的操作会由用户界面中的各种组件内部处理。但是,若要使用鼠标画图,就需要捕获鼠标移动点击和
拖动事件。举例:图形编辑器,允许用户在画布上放置,移动和擦除方块。其中的功能应该有:
(1)单机空白区域,新增方块
(2)双击方块,擦除方块
(3)移动方块
(4)为让效果明显,添加一个改变鼠标指针图标的方法
package actionTest;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
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;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class MouseFrame extends JFrame{
public MouseFrame() {
add(new MouseComponent());
pack();//根据组件的首选大小,调整窗口大小
}
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.setVisible(true);//显示窗口
}
});
}
}
class MouseComponent extends JComponent{
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 200;
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 Dimension getPreferredSize() {
return new Dimension(DEFAULT_WIDTH,DEFAULT_HEIGHT );
}
//绘制组件,也就是将集合中的正方块绘制到MouseComponent组件中;之后的操作都会反复调用该方法
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);
//重新绘制当前组件,即MouseComponent
repaint();
}
//若给出的正方块对象不为空,则让其为空,从集合中删除该对象,并重新绘制组件,刷新视图
public void remove( Rectangle2D s ) {
if( s==null ) return;
if( s==current ) current = null;
squares.remove(s);
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 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( Cursor.getDefaultCursor() );
else setCursor( Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR) );
}
}
}
4、事件学习(4)AWT事件
AWT将事件分为底层事件和语义事件。
java.awt.event包中常用的语义事件类:
ActionEvent(对应按钮点击,菜单选择,选择列表项或文本框中ENTER)
AdjustmentEvent(用户调节滚动条)
ItemEvent(用户从复选框或列表框中选择一项)
java.awt.event包中常用的底层事件类:
KeyEvent(一个键被按下或释放)
MouseEvent(鼠标被按下,释放,移动或拖动)
MouseWheelEvent(鼠标滚轮被转动)
FocusEvent(某个组件获得焦点或失去焦点)
WindowEvent(窗口状态被改变)