事件处理
GUI程序是事件驱动程序,因此我们需要学习Java的事件处理
常见的事件包括
- 移动鼠标
- 单双击鼠标各个按钮
- 单击按钮
- 在文本字段输入
- …
Swing通过事件对象来包装事件,程序可以通过事件获取事件的有关信息
事件处理的几个要素
-
事件源
- 与用户进行交互的GUI组件,表示事件来自于哪个组件或对象
- 比如要对按钮被按下这个事件编写程序,按钮就是事件源
- 提供注册监听器或取消注册监听器的方法
- 如有事件发生,已注册的监听器就会被通知
- 一个事件源可以注册多个监听器,每个事件监听器又可以响应多种事件
-
事件监听器
- 负责监听事件并做出响应
- 一旦它监视到事件发生,就会自动调用相应的事件处理程序作出响应
- 是一个对象,通过事件源的addxxxListener方法被注册到某个事件源上
- 不同的Swing组件可以注册不同的事件监听器
- 一个事件监听器中可以包含有多种具体事件的专用处理方法
-
事件对象
- 封装了有关已发生的事件的信息
- 例如按钮被按下就是一个要被处理的事件,当用户按下按钮时,就会产生一个事件对象,事件对象中包含事件的相关信息和事件源
- 常用的事件对象有
ActionEvent
,ItemEvent
等等,具体可查阅API文档
上面的基本概念很重要
我们要做的是什么?
- 为事件源注册一个事件监听器
- 实现事件处理方法
接口与适配器
-
事件监听器接口
就是一个抽象类,如果要使用它,必须将其定义的方法都实现
-
事件监听器适配类
如果你不想实现事件监听器接口的所有类,那就继承实现了事件监听器接口的子类(适配器),然后覆盖你想要单独写的事件就可以了
当然,如果你这个类已经继承了其他的类了,这时候没办法再继承这个适配器,你还可以使用匿名的内部类(不用起名,直接New一个,然后向上转型就可以)
看一个程序
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
public class UseInnerClass {
JFrame f;
public UseInnerClass()
{
f = new JFrame();
f.setSize(300,150);
f.show();
f.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e)
{
f.setTitle("点击坐标为("+e.getX()+","+e.getY()+")");
}
});
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new UseInnerClass();
}
}
我们分析一下代码
f.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e)
{
f.setTitle("点击坐标为("+e.getX()+","+e.getY()+")");
}
});
这段代码使用的是内部类的方法,我们前面说过,要注册一个事件监听器,需要一个已经实现事件监听器对象的类,因此我们可以直接定义一个匿名类,匿名类覆盖了想要单独实现的方法同时向上转型成其继承的类
如果你直接在该类继承了适配器或继承了接口并实现了方法,那么你就直接在构造函数那里addxxxListener(this)就可以了,因为你自身的类已经实现了事件监听器接口
f.addMouseListener(new MouseAdapter(this)
事件派发机制
前面讲到的事件处理,一个事件源触发事件后,事件监听器就会执行相应的代码,那么这一个过程是如何实现的?
还有一个问题,我们在对一个事件源组件进行处理时,这时不应该有其他的代码对其进行处理,就比如我们修改一个文本框,如果好几个事件同时修改,那就乱套了,我们是如何保证事件是串行处理的?
Java主要由一个线程,事件派发线程来实现
事件派发机制–事件派发线程
-
Swing中的组件是非线程安全的,在Swing中专门提供了一个事件派发线程EDT用于对组件的安全访问
- 用来执行组件事件处理程序的线程,依次从系统事件队列取出事件并处理,一定要执行完上一个事件的处理程序后,才会执行下一个事件
- 事件监听器的方法都是在事件派发线程中执行的,如ActionListener的actionPerformed方法
事件派发机制–由事件派发线程启动GUI
-
可以调用invokeLater或invokeAndWait请事件分发线程以运行某段代码
- 要将这段代码放入一个Runnable对象的run方法中,并将该Runnable对象作为参数传递给invokeLater
- invokeLater是异步,不用等代码执行完就返回
- invokeAndWait是同步的,要等代码执行完才返回,调用时要避免死锁
看一个程序
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Container;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class CardLayoutDemo implements ItemListener{
private static String BUTTONPANEL = "JPanel with JButton";
private static String TEXTPANEL = "JPanel with JTextField";
private JPanel cards;
public void addComponentToPane(Container pane)
{
JPanel comboBoxPane = new JPanel();
String[] comboBoxItems = {BUTTONPANEL,TEXTPANEL};
JComboBox cb = new JComboBox(comboBoxItems);
cb.setEditable(false);
cb.addItemListener(this);
comboBoxPane.add(cb);
JPanel card1 = new JPanel();
card1.add(new JButton("Button1"));
card1.add(new JButton("Button2"));
card1.add(new JButton("Button3"));
JPanel card2 = new JPanel();
card2.add(new JTextField("TextField",20));
cards = new JPanel(new CardLayout());
cards.add(card1, BUTTONPANEL);
cards.add(card2, TEXTPANEL);
pane.add(comboBoxPane, BorderLayout.PAGE_START);
pane.add(cards,BorderLayout.CENTER);
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("CardLayoutDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
CardLayoutDemo demo = new CardLayoutDemo();
demo.addComponentToPane(frame.getContentPane());
frame.pack();
frame.setVisible(true);
}
@Override
public void itemStateChanged(ItemEvent evt) {
// TODO Auto-generated method stub
CardLayout cl = (CardLayout)(cards.getLayout());
cl.show(cards, (String)evt.getItem());
}
public static void main(String[] args) {
// TODO Auto-generated method stub
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run()
{
createAndShowGUI();
}
});
}
}
我们分析一下关键部分的代码
public class CardLayoutDemo implements ItemListener
这段代码表示该类自身实现了ItemListener接口
public void addComponentToPane(Container pane)
{
JPanel comboBoxPane = new JPanel();
String[] comboBoxItems = {BUTTONPANEL,TEXTPANEL};
JComboBox cb = new JComboBox(comboBoxItems);
cb.setEditable(false);
cb.addItemListener(this);
comboBoxPane.add(cb);
JPanel card1 = new JPanel();
card1.add(new JButton("Button1"));
card1.add(new JButton("Button2"));
card1.add(new JButton("Button3"));
JPanel card2 = new JPanel();
card2.add(new JTextField("TextField",20));
cards = new JPanel(new CardLayout());
cards.add(card1, BUTTONPANEL);
cards.add(card2, TEXTPANEL);
pane.add(comboBoxPane, BorderLayout.PAGE_START);
pane.add(cards,BorderLayout.CENTER);
}
这段代码基本上全是使用一个中间容器来容纳原子组件,然后再将中间容器加到顶层容器中,关于Swing
层次这一部分内容可以看这篇博客Swing层次结构
里面这一行代码cb.addItemListener(this);
就是注册事件监听器
这一行代码cards = new JPanel(new CardLayout());
实现了CardLayout
布局;
public void itemStateChanged(ItemEvent evt) {
// TODO Auto-generated method stub
CardLayout cl = (CardLayout)(cards.getLayout());
cl.show(cards, (String)evt.getItem());
}
这一段代码就是实现事件处理函数,首先定义一个CradLayout cl
得到cards
的布局,然后再通过事件源evt
得到要显示的内容evt.getItem()
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run()
{
createAndShowGUI();
}
});
这一段代码就是将我们的代码放到事件派发线程里面去执行了
参考:
清华大学Java教程p95-p96