
?當程序員向窗體上添加了按鈕等組件之后就能夠操作這些組件,但在20.3小節的各個案例中,雖然在窗體上添加了一些按鈕,但點擊這些按鈕并沒有任何反應,因此這些按鈕也就成了毫無意義的“擺設”。如果希望按鈕等組件能夠在被操作時執行一段代碼并產生一個動作,就必須為組件添加一個監聽器并由監聽器負責處理組件所產生的事件,本小節將詳細講解事件處理的相關原理。
組件被操作時會產生事件,在事件處理的過程中,主要涉及三類對象:?
(資料圖片)
當用戶單擊一個按鈕,或者單擊某個菜單項時,這些動作就會觸發一個相應的事件,該事件會被封裝成相應的事件對象,該事件會觸發事件源上注冊的事件監聽器產生反應,事件監聽器調用對應的事件處理器來做出相應的響應。?
AWT的事件處理機制是一種委派式事件處理方式:作為事件源的普通組件將事件的處理工作委托給特定的對象,也就是事件監聽器。當該事件源發生指定的事件時,就通知所委托的事件監聽器,由事件監聽器來處理這個事件。?
每個組件均可以針對特定的事件指定一個或多個事件監聽對象,每個事件監聽器也可以監聽一個或多個事件源。因為同一個事件源上可能發生多種事件,委派式事件處理方式可以把事件源上可能發生的不同的事件分別授權給不同的事件監聽器來處理,同時也可以讓一類事件都使用同一個事件監聽器來處理。?
委派式事件處理方式類似于人類社會的分工協作,例如某個單位發生了火災,該單位通常不會自己處理該事件,而是將該事件委派給消防局處理,消防局就相當于事件監聽器,同理,如果發生了打架斗毆事件,則委派給公安局處理,公安局相當于事件監聽器。消防局、公安局也會同時監聽多個單位的火災、打架斗毆事件。這種委派式處理方式將事件源和事件監聽器分離,從而提供更好的程序模型,有利于提高程序的可維護性。下面的圖20-6展示了事件處理的流程圖。?
圖20-6事件處理的流程圖?
在圖20-6中,處理程序會以事件對象作為處理事件方法的參數,因此在處理程序中可以獲得事件對象的各項屬性。?
兩個相同事件源產生事件之后其處理過程可能并不相同。例如窗體上某個按鈕被單擊后會向數據庫中插入一條數據,而另一個按鈕被單擊后則刪會除數據庫中的一條數據。由此可以看出:每一個按鈕被單擊后所做的操作各不相同,因此程序員沒有辦法在監聽器中編寫出具體的處理代碼,所以只能把處理按鈕被單擊事件定義成抽象方法,而處理按鈕被單擊的方法又被定義在監聽器內,這樣的話監聽器也只能被定義成抽象類或者是接口類型。實際上,幾乎所有的監聽器都被定義成接口,這是因為接口具有多實現特性。?
事件源添加監聽器的方法名稱通常都是addXXXListener,這些方法都被定義在組件中。事件源可以產生的事件類型很多,這些事件以不同的類進行定義,而監聽這些事件的監聽器名稱也與事件的名稱有高度吻合,下面的表20-3展示了常見事件名稱、事件含義以及處理這些事件監聽器的名稱。?
表20-3 常見事件及對應監聽器?
事件? | 事件意義? | 監聽器? |
ActionEvent? | 動作事件? | ActionListener? |
AdjustmentEvent? | 滾動條調整事件? | AdjustmentListener? |
ContainerEvent? | 容器相關事件? | ContainerListener? |
FocusEvent? | 焦點變化事件? | FocusListener? |
ItemEvent? | 下拉框、列表框選項相關事件? | ItemListener? |
KeyEvent? | 鍵盤操作事件? | KeyListener? |
MouseEvent? | 鼠標操作事件? | MouseListener? |
WindowEvent? | 窗體變化事件? | WindowListener? |
表20-3中,第一行的監聽器ActionListener翻譯成漢語是“動作監聽器”,它的名字太過于抽象了,這個監聽器是Java語言提供的一個處理各種組件發生頻率最高事件的監聽器。比如對于按鈕組件來說發生頻率最高的事件就是被按鈕單擊,那么ActionListener添加到按鈕組件上,就是專門用來處理按鈕單擊事件的,再比如對于菜單組件來講,發生最高頻率的事件就是菜單當中某個菜單項被單擊,那么ActionListener用到菜單組件上,就是處理菜單中某個菜單項被單擊的操作,但是也有一些窗體組件并不支持ActionListener。ActionListener這個監聽器當中只定義了一個actionPerformed()抽象方法,程序員只需要把處理事件的代碼寫到這個方法中就可以。下面的【例20_06】用兩種方式實現了監聽器,它們都能處理按鈕被單擊事件。?
【例20_06 監聽器】
Exam20_06.java?
import java.awt.*;import java.awt.event.*;import javax.swing.*;//定義一個監聽鼠標事件的監聽器class Listener1 implements MouseListener { @Override public void mouseReleased(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseClicked(MouseEvent e) { System.out.println("按鈕1被鼠標單擊!"); }}//定義一個ActionListenerclass Listener2 implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { System.out.println("按鈕2被鼠標單擊!"); }}//定義一個窗體類class Exam20_06Frame extends JFrame{ JButton button1; JButton button2; public Exam20_06Frame( ){ init(); } private void init( ){ button1 = new JButton("按鈕1"); button2 = new JButton("按鈕2"); Container container = this.getContentPane(); //把內容面板設置為空布局 container.setLayout(null); //設置button1的位置為(0,0) button1.setLocation(0, 0); //設置button1的大小為200,100 button1.setSize(200, 100); //為按鈕添加MouseListener button1.addMouseListener(new Listener1());//① //設置button2的位置為(240,50) button2.setLocation(240, 50); //設置button2的大小為100,200 button2.setSize(100, 200); //為按鈕添加ActionListener button2.addActionListener(new Listener2());//② container.add(button1); container.add(button2); }}public class Exam20_06 { public static void main(String[] args) { Exam20_06Frame frame = new Exam20_06Frame(); frame.setSize(400, 300);//設置窗體的大小 frame.setLocationRelativeTo(null);//設置窗體出現在屏幕正中間 frame.setTitle("Exam20_06Frame");//設置窗體的標題 //設置關閉窗體時同時停止程序 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true);//設置窗體可見 }}
【例20_06】中,用兩種方式處理按鈕被單擊的事件。單擊事件由鼠標單擊操作產生,為了監聽鼠標所產生的事件,第一種方式是為按鈕添加代表鼠標事件的MouseListener,由于MouseListener是一個接口,因此需要定義一個MouseListener的實現類Listener1實現監聽器。可以看出:MouseListener接口中定義了很多抽象方法,mouseClicked()方法是鼠標單擊事件的處理方法。程序員只需要在mouseClicked()方法中寫上代碼,這段代碼將會在鼠標單擊時自動執行,本例在mouseClicked()方法里編寫了一條輸出語句,并且在語句①中為按鈕添加了這個監聽器對象,因此在窗體上點擊按鈕時控制臺上會輸出“按鈕1被鼠標單擊!”。此外還可以看出:所有鼠標事件相關的處理方法都以鼠標事件MouseEvent作為參數。?
第二種方式以ActionListener作為監聽器,由于按鈕最常出現的事件就是被鼠標單擊,因此ActionListener能夠處理這個事件。處理事件時在控制臺上輸出“按鈕2被鼠標單擊!”各位讀者可以自行運行本例以觀察按鈕1和按鈕2被單擊后的效果。?
監聽器一般都是以接口形式存在,程序員只要定義一個接口的實現類,并且在實現類中實現接口所定義的抽象方法就可以實現監聽器。監聽器在處理事件時往往要對窗體上的其他組件進行操作,而這些組件往往又是以窗體類屬性的方式存在的,例如在【例20_06】中,窗體上的兩個按鈕組件button1和button2都被定義成了Exam20_06Frame類的屬性,而屬性的操作和訪問又受到訪問權限的影響,因此編程時必須考慮如何合理的實現監聽器。除此之外,監聽器在處理事件時往往還會操作一些重要的數據,所以監聽器的實現方式還直接關系到應用程序的安全性。?
通常情況下,監聽器有以下4種實現方式:?
匿名內部類實現?普通內部類實現?窗體直接充當監聽器?外部類實現?如果窗體上的按鈕對象是button,為這個按鈕添加的監聽器是ActionListener,那么使用匿名內部類對象作為其監聽器的代碼是:?
class MyFrame extends JFrame{ JButton button; private void init( ){ ...... button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { //事件處理代碼 } }); ...... }}
使用匿名內部類實現監聽器能夠非常方便的操作以屬性形式定義的窗體組件以及其他窗體類的屬性,并且匿名內部類實現的監聽器只能被添加到一個組件上,這是因為一個匿名類對象只能出現在一個地方。一個監聽器對象只能出現在一個地方的特性既有優點也有缺點,優點是能夠保證監聽器不被其他組件混用,這能夠提高代碼的安全性,缺點是一個監聽器沒有被重復利用的機會。此外,匿名內部類實現的監聽器會使代碼的結構性變差,雖然如此,由于匿名內部類實現的監聽器安全性最高,所以這種方式是實現監聽器的首選。?
使用普通內部類也能實現監聽器,其代碼結構如下:?
class MyFrame extends JFrame{ JButton button; //普通內部類實現的監聽器 class MyListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { //事件處理代碼 } } private void init( ){ //添加監聽器 button.addActionListener(new MyListener()); }}
普通內部類實現的監聽器也能非常方便的操作以屬性形式定義的窗體組件以及其他窗體類的屬性,并且這種監聽器能夠同時被添加到多個組件上,提高了代碼的可重用性。此外,因為普通內部類的定義和創建對象的代碼是分開的,所以耦合度也會降低,代碼結構清晰,但因為這種監聽器可以被多個組件使用,不屬專于某個組件,因此也會導致安全性降低。?
第三種實現監聽器的方式比較特殊,就是用窗體類直接充當監聽器,這種情況下,窗體類不僅要繼承JFrame類,還要實現監聽器接口,代碼如下:?
class MyFrame extends JFrame implements ActionListener{ JButton button; //普通內部類實現的監聽器 @Override public void actionPerformed(ActionEvent e) { //事件處理代碼 } private void init( ){ //添加監聽器 button.addActionListener(this); }}
使用窗體類直接充當監聽器的優點是:把窗體當作監聽器,處理事件的方法就直接出現在窗體類當中,這樣處理事件的方法無論操作窗體的哪個屬性都不受訪問權限的影響。此外,因為沒有使用內部類,所以代碼結構變得清晰、優美。但這種實現方式的缺點也是顯而易見的:首先,窗體類的角色過于復雜。以上代碼中的MyFrame類本來是表示一個窗體,但是它又充當了監聽器的角色。如果窗體上還存在其他用鍵盤操作的組件,比如說文本框,為了處理這些組件的鍵盤事件就必須讓MyFrame還同時實現KeyListener。以此類推,MyFrame這個類很可能承擔多種類型的角色,這樣的話,它必然會同時實現多個監聽器接口,這也直接導致在窗體內部會出現大量處理事件的代碼。其次,監聽器接口中定義的處理事件的方法都被定義成public,因此窗體充當監聽器也會導致這些方法能夠被公開訪問,降低了安全性。?
監聽器的第4實現種方式是使用外部類定義監聽器,【例20_06】就是用這種方式實現的監聽器。這種方式實現的監聽器不能訪問窗體類中被定義為private的屬性,因此在實際開發過程中很少使用這種方式實現監聽器。?
大部分監聽器接口中都定義了好幾個抽象方法,例如處理鼠標事件的MouseListener接口就定義了5個抽象方法,而程序員在實現監聽器接口時要把這些抽象方法全部都實現了,因為不實現這些抽象方法就無法定義出一個非抽象的類,但有的時候,程序員只是希望實現某種具體的事件類型處理方法,這種情況下為了實現監聽器接口,就不得不在實現類中定義很多空方法。例如:?
class Listener1 implements MouseListener { @Override public void mouseReleased(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseClicked(MouseEvent e) { System.out.println("按鈕1被鼠標單擊!"); }}
以上代碼實現了一個監聽器接口,可以看出:程序員希望只處理鼠標單擊這個事件,但為了實現監聽器接口,不得不以空方法的形式實現其他4個抽象方法,這顯然造成了程序中出現大量無用的空閑代碼。?
為了解決這個問題,Java語言又提供了相應的適配器類。適配器類實際上就是監聽器接口的默認實現類,這些實現類中都提供了監聽器接口中抽象方法的空實現。程序員在實現監聽器的時候,不是去直接實現這些監聽器接口,而是去繼承這些實現類,在繼承實現類的時候,重寫那些真正需要處理事件的方法。?
Java語言中,大部分監聽器接口都有對應的適配器類,但也有一些沒有,監聽器與適配器類的關系如表20-4所示。?
表20-4監聽器與適配器?
監聽器接口? | 適配器類? |
ActionListener? | 無? |
AdjustmentListener? | 無? |
ContainerListener? | ContainerAdapter? |
FocusListener? | FocusAdapter? |
ItemListener? | 無? |
KeyListener? | KeyAdapter? |
MouseListener? | MouseAdapter? |
WindowListener? | WindowAdapter? |
下面的【例20_07】展示了用繼承適配器類的方式定義監聽器。?
【例20_07 適配器】
Exam20_07.java?
import java.awt.*;import java.awt.event.*;import javax.swing.*;//以繼承適配器類的方式實現監聽器class MyMouseAdapter extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { System.out.println("按鈕被鼠標單擊!"); }}//定義一個窗體類class Exam20_07Frame extends JFrame{ JButton button; public Exam20_07Frame( ){ init(); } private void init( ){ button = new JButton("按鈕"); Container container = this.getContentPane(); container.setLayout(null); button.setLocation(0, 0); button.setSize(200, 100); button.addMouseListener(new MyMouseAdapter());//① container.add(button); }}public class Exam20_07{ public static void main(String[] args) { Exam20_07Frame frame = new Exam20_07Frame(); frame.setSize(400, 300);//設置窗體的大小 frame.setLocationRelativeTo(null);//設置窗體出現在屏幕正中間 frame.setTitle("Exam20_07Frame");//設置窗體的標題 //設置關閉窗體時同時停止程序 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true);//設置窗體可見 }}
可以看出:以繼承適配器類的方式實現監聽器能夠大量的減少無用代碼。?
本文字版教程還配有更詳細的視頻講解,小伙伴們可以??點擊這里??觀看。