前言
當用戶點擊圖形界面上的一個按鈕或者其他Component時要有所響應,這才是實現了圖形界面的交互功能。如何做出這些響應我們就需要了解事件的處理機制。下面將分為以下內容介紹AWT(Swing)中事件處理機制:
什么是事件
通俗一點來說就是某種狀態的改變,在我們的圖形界面中就表現為某個按鈕被點擊了,窗口被關閉了等。
什么是事件處理
當某個事件發生時(界面中的某個Component的某個狀態發生改變時),我們希望在這個時機執行一些代碼來做我們希望做的事,這個就是事件處理。如點擊窗口關閉按鈕時,彈出對話框詢問用戶是否保存當前已經修改過的內容。
Java是面向對象的編程語言,Java中使用監聽器類來探測一個事件(改變),使用監聽器類中的方法來在事件發生的時候處理事件。
事件處理中的三要素
事件源:是這個對象的狀態改變引發的事件,事件源通常是Component。
事件:事件源發生的狀態改變。如按鈕被鼠標左擊或者被鼠標右擊等。
事件監聽器:監聽器被安裝在某個Component上,負責監聽這個Component具體狀態被改變了。
AWT中事件處理的流程
外部誘使事件源狀態發生變化,產生事件對象,然后事件監聽器監聽到該事件的發生,做出響應。
- 首先將事件監聽器注冊到事件源上面
- 觸發事件源上的事件(改變狀態)
- 生成事件對象
- 事件監聽器監聽到該事件的發生,生成的事件對象當做參數傳入事件處理器(監聽器類中的方法)
- 調用事件處理器做出響應
舉例點擊事件
鼠標點擊按鈕后,文本框中顯示一行‘按鈕被點擊了'。
先是在按鈕上注冊了事件監聽器,監聽器中設置鼠標被點擊時應該調用的事件處理器是怎么處理的;然后,鼠標點擊按鈕;生成按鈕被點擊的事件對象;事件監聽器監聽到點擊事件發生,就會傳入事件對象到事件處理器;最后,調用事件處理器中做出希望的響應。
public class TestEvent {
public static void main(String[] args) {
JFrame myFrame = new JFrame();
JButton btn = new JButton("點擊我");
JTextField field = new JTextField();
//為field指定寬高
field.setPreferredSize(new Dimension(100, 40));
//使用Jpanel容器裝載JButton和JTextFiedl組件
JPanel jPanel = new JPanel();
jPanel.add(btn);
jPanel.add(field);
myFrame.add(jPanel);
//設置窗口的大小寬高都為300像素以及位置距離屏幕左上方向右300像素以及向下300像素
myFrame.setBounds(300, 300, 300, 300);
//必須設置這個屬性 才可以看見窗口
myFrame.setVisible(true);
//為btn設置事件監聽器 監聽器采用匿名內部類的形式
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
field.setText("Button被點擊了");
}
});
}
}


AWT中事件處理的模板
class XXXXListener implements XXXListener {
@Override
public void xxx(ActionEvent e) {
System.out.println("用戶單擊了按鈕");
}
}
…
yyy.addXXXListener(new XXXXListener());
創建一個監聽器類,該類實現監聽器接口XXXListener
,然后實現監聽器方法使得可以處理對應事件
-
在感興趣的Component上使用
addXXXListener(...)
,添加監聽器即傳入監聽器的實例 -
注意:
不同的Component有不同的事件發生,但是一般添加監聽器的方法都是
addXXXListener(...)
。Component會有不同的事件發生,所以需要對感興趣的事件添加對應的監聽器。有些監聽器內部會有多個方法,可以監聽多種事件,但是這些事件一般都相關。在點擊事件舉例中,我們添加監聽器采用的匿名內部類的形式,可以看出監聽器的實現形式還是有多種。
監聽器(EventListener)的實現形式
監聽器是一種特殊的Java類。在AWT中,監聽器主要有以下幾種實現方式:
監聽器作為外部類
規范易於理解、類本身可以重用;不足在於一般情況下不利於實現事件處理中的功能,因為不易於訪問界面中的屬性和方法
class BtnListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
//使用getSource獲取事件源對象 但是不易訪問到其他組件
JButton btn = (JButton)e.getSource();
btn.setText("我被點擊了");
}
}
public class TestListener {
private JFrame myFrame = new JFrame("測試外部監聽器");
private JButton btn = new JButton("點擊我");
private JTextField field = new JTextField();
//構造函數
public TestListener() {
init();
}
//初始化 為按鈕添加事件監聽器
public void init() {
JPanel jPanel = new JPanel();
jPanel.add(btn);
field.setPreferredSize(new Dimension(100, 40));
jPanel.add(field);
//以創建外部類對象方式添加事件監聽器
btn.addActionListener(new BtnListener());
myFrame.add(jPanel);
myFrame.setBounds(300, 300, 500, 300);
//使得按鈕窗口的關閉按鈕可以關閉窗口
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
//顯示窗口
public void showFrame() {
myFrame.setVisible(true);
}
public static void main(String[] args) {
new TestListener().showFrame();
}
}
監聽器作為內部類
可以方便的訪問主類中的任何屬性和方法,包括私有方法;不足在於使得主類過於復雜、不可以在不同界面中重用
public class TestListener {
//內部類
class BtnListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
//可以方便操作主類的其他屬性
field.setText("Btn被點擊了");
}
}
...
//初始化 為按鈕添加事件監聽器
public void init() {
...
//以創建內部類對象方式添加事件監聽器
btn.addActionListener(new BtnListener());
...
}
...
}
監聽器作為主類本身
可以方便地訪問本類中的任何方法和屬性;不足在於使得本類的方法過多
//主類作為監聽器
public class TestListener implements ActionListener{
private JFrame myFrame = new JFrame("測試主類作為監聽器");
private JButton btn = new JButton("點擊我");
private JTextField field = new JTextField();
//構造函數
public TestListener() {
init();
}
//實現的處理方法
@Override
public void actionPerformed(ActionEvent e) {
field.setText("Btn被點擊了");
}
//初始化 為按鈕添加事件監聽器
public void init() {
JPanel jPanel = new JPanel();
jPanel.add(btn);
field.setPreferredSize(new Dimension(100, 40));
jPanel.add(field);
//主類本身作為監聽器對象傳入
btn.addActionListener(this);
myFrame.add(jPanel);
myFrame.setBounds(300, 300, 500, 300);
//使得按鈕窗口的關閉按鈕可以關閉窗口
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
//顯示窗口
public void showFrame() {
myFrame.setVisible(true);
}
public static void main(String[] args) {
new TestListener().showFrame();
}
}
監聽器作為匿名內部類
可以方便地訪問主類的方法和屬性;不足在於對於每個事件都需要寫匿名內部類,不能重用、不利於理解
//主類作為監聽器
public class TestListener{
private JFrame myFrame = new JFrame("測試主類作為監聽器");
private JButton btn = new JButton("點擊我");
private JTextField field = new JTextField();
//構造函數
public TestListener() {
init();
}
//初始化 為按鈕添加事件監聽器
public void init() {
JPanel jPanel = new JPanel();
jPanel.add(btn);
field.setPreferredSize(new Dimension(100, 40));
jPanel.add(field);
//以匿名內部類的方式創建監聽器
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
field.setText("Btn被點擊了");
}
});
//或者使用lambda表達式
// btn.addActionListener(event->field.setText("Btn被點擊了"));
myFrame.add(jPanel);
myFrame.setBounds(300, 300, 500, 300);
//使得按鈕窗口的關閉按鈕可以關閉窗口
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
//顯示窗口
public void showFrame() {
myFrame.setVisible(true);
}
public static void main(String[] args) {
new TestListener().showFrame();
}
}
關於使用lambda表達式的理解:若某個方法使用的類是“眾所周知“的並且類中的方法是唯一的,我們就只需要給出參數和一些執行代碼代替原來復雜的一堆代碼。因為唯一性,編譯器就會推斷出使用的是什么類以及類中的方法。
上面我們可以使用lamnda表達式event->field.setText(...)
代替了匿名內部類那么多代碼,那是因為編譯器可以推斷出addListener(...)
需要傳入的參數是實現了ActionListener
接口的類,這個類中有唯一的方法就是actionPerformed
以及這個方法只有一個參數而且也知道其參數類型。
使用lambda表達式雖然可以簡化我們的代碼,但是我們需要理清楚其原本應該使用什么類以及什么方法。
若是省略的方法由多個參數則->
左邊的參數需要使用()
括起來如(a,b,c)
,->
后有多行需要執行的代碼則使用{}
將代碼括起來。
事件適配器(EventAdapter)
我們在創建監聽器類時需要實現其實現的相應監聽器接口中的所有方法。前面提到過,某些監聽器接口內會有多個方法,但是我們只對一些方法感興趣,有沒有方法只用實現感興趣的方法其他方法就不實現呢。答案是有的,那就是使用事件適配器。Java內部已經幫我們事先寫了一些適配器,我們只需要實現感興趣的適配器,然后重寫我們感興趣的方法。
若是沒有適配器,我們想監聽鼠標點擊事件,我們先看MouseListener接口的源碼實現:
public interface MouseListener extends EventListener {
/**
* Invoked when the mouse button has been clicked (pressed
* and released) on a component.
*/
public void mouseClicked(MouseEvent e);
/**
* Invoked when a mouse button has been pressed on a component.
*/
public void mousePressed(MouseEvent e);
/**
* Invoked when a mouse button has been released on a component.
*/
public void mouseReleased(MouseEvent e);
/**
* Invoked when the mouse enters a component.
*/
public void mouseEntered(MouseEvent e);
/**
* Invoked when the mouse exits a component.
*/
public void mouseExited(MouseEvent e);
}
我們需要實現mouseClicked方法以外的其他4個方法,可以說是非常麻煩的。我們再看MouseAdapter的源碼實現:
public abstract class MouseAdapter implements MouseListener, MouseWheelListener, MouseMotionListener {
/**
* {@inheritDoc}
*/
public void mouseClicked(MouseEvent e) {}
/**
* {@inheritDoc}
*/
public void mousePressed(MouseEvent e) {}
/**
* {@inheritDoc}
*/
public void mouseReleased(MouseEvent e) {}
/**
* {@inheritDoc}
*/
public void mouseEntered(MouseEvent e) {}
/**
* {@inheritDoc}
*/
public void mouseExited(MouseEvent e) {}
/**
* {@inheritDoc}
* @since 1.6
*/
public void mouseWheelMoved(MouseWheelEvent e){}
/**
* {@inheritDoc}
* @since 1.6
*/
public void mouseDragged(MouseEvent e){}
/**
* {@inheritDoc}
* @since 1.6
*/
public void mouseMoved(MouseEvent e){}
}
可以看出適配器的實現非常巧妙,為每個方法添加一個空的方法體而為我們進行一次封裝,我們只需重寫我們感興趣的方法就可,其他方法也不會受到影響。
AWT中的事件分類
AWT中的事件主要分為兩大類:低級事件和高級事件
低級事件
低級事件就比較底層(比較細節),主要有:
-
ComponentEvent:組件事件,當組件尺寸發生改變、位置發生變化、顯示/隱藏狀態發生改變時,就會觸發該事件
-
ContainerEvent:容器事件,當容器里增加、刪除組件時,就會觸發該事件
-
WindowEvent:窗口事件,當窗口狀態發生改變(打開、關閉、最大化、最小化)時,就會觸發該事件
-
FocusEvent:焦點事件,當組件得到焦點或者失去焦點時,就會觸發該事件
-
KeyEvent:鍵盤事件,當鍵盤按鍵被按下、松開時就會觸發該事件
-
MouseEvent:鼠標事件,當一個組件被鼠標按下、放開、在其上面移動鼠標時,就會觸發該事件
-
PaintEvent:繪制事件,當GUI組件調用update()/paint()方法時觸發該事件,該事件並非專用於事件處理模型中,一般也很少直接監聽該事件
高級事件
高級事件基於語義,並不和特定的動作相關,而依賴於觸發該事件的組件類別。主要分為:
- ActionEvent:動作事件,當按鈕、菜單等能產生Action的項目被單擊,或者在TextField中按下Enter按鈕時,就會觸發該事件
- AdjustmentEvent:調節事件,在滑動條上移動滑塊調節數值時觸發該事件
- ItemEvent:–選項事件,當在有很多項目的組件中,選中或者取消選中了某一個項目,就會觸發該事件
- TextEvent:文本事件,當文本框或者文本域這類具有文本的組件中,文本發生變化時,就會觸發該事件
小結
總結了關於事件處理的大部分基礎,基礎概念有了,重要的還是多寫代碼實踐。另外事件處理中還是涉及到很多技巧,比如使用lambda表達式、使用設計模式的適配器思想去簡化設置監聽器的代碼。在自己寫的過程中才會體會更多