內部類是新的事件模型,並且事實上舊的事件模型連同新庫的特征都被它好的支持,依賴老式的編程方法無疑增加了一個新的混亂的因素。現在有更多不同的方法為我們編寫討厭的代碼。湊巧的是,這種代碼顯現在本書中和程序樣本中,並且甚至在文件和程序樣本中同SUN公司區別開來。在這一節中,我們將看到一些關於我們會和不會運行新AWT的爭執,並由向我們展示除了可以原諒的情況,我們可以隨時使用接收器類去解決我們的事件處理需要來結束。因為這種方法同樣是最簡單和最清晰的方法,它將會對我們學習它構成有效的幫助。
在看到任何事以前,我們知道盡管Java 1.1向後兼容Java 1.0(也就是說,我們可以在1.1中編譯和運行1.0的程序),但我們並不能在同一個程序裡混合事件模型。換言之,當我們試圖集成老的代碼到一個新的程序中時,我們不能使用老式的action()方法在同一個程序中,因此我們必須決定是否對新程序使用老的,難以維護的方法或者升級老的代碼。這不會有太多的競爭因為新的方法對老的方法而言是如此的優秀。
1. 准則:運行它的好方法
為了給我們一些事物來進行比較,這兒有一個程序例子演示向我們推薦的方法。到現在它會變得相當的熟悉和舒適。
//: GoodIdea.java // The best way to design classes using the new // Java 1.1 event model: use an inner class for // each different event. This maximizes // flexibility and modularity. import java.awt.*; import java.awt.event.*; import java.util.*; public class GoodIdea extends Frame { Button b1 = new Button("Button 1"), b2 = new Button("Button 2"); public GoodIdea() { setLayout(new FlowLayout()); b1.addActionListener(new B1L()); b2.addActionListener(new B2L()); add(b1); add(b2); } public class B1L implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println("Button 1 pressed"); } } public class B2L implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println("Button 2 pressed"); } } public static void main(String[] args) { Frame f = new GoodIdea(); f.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e){ System.out.println("Window Closing"); System.exit(0); } }); f.setSize(300,200); f.setVisible(true); } } ///:~
這是頗有點微不足道的:每個按鈕有它自己的印出一些事物到控制台的接收器。但請注意在整個程序中這不是一個條件語句,或者是一些表示“我想要知道怎樣使事件發生”的語句。每塊代碼都與運行有關,而不是類型檢驗。也就是說,這是最好的編寫我們的代碼的方法;不僅僅是它更易使我們理解概念,至少是使我們更易閱讀和維護。剪切和粘貼到新的程序是同樣如此的容易。
2. 將主類作為接收器實現
第一個壞主意是一個通常的和推薦的方法。這使得主類(有代表性的是程序片或幀,但它能變成一些類)執行各種不同的接收器。下面是一個例子:
//: BadIdea1.java // Some literature recommends this approach, // but it's missing the point of the new event // model in Java 1.1 import java.awt.*; import java.awt.event.*; import java.util.*; public class BadIdea1 extends Frame implements ActionListener, WindowListener { Button b1 = new Button("Button 1"), b2 = new Button("Button 2"); public BadIdea1() { setLayout(new FlowLayout()); addWindowListener(this); b1.addActionListener(this); b2.addActionListener(this); add(b1); add(b2); } public void actionPerformed(ActionEvent e) { Object source = e.getSource(); if(source == b1) System.out.println("Button 1 pressed"); else if(source == b2) System.out.println("Button 2 pressed"); else System.out.println("Something else"); } public void windowClosing(WindowEvent e) { System.out.println("Window Closing"); System.exit(0); } public void windowClosed(WindowEvent e) {} public void windowDeiconified(WindowEvent e) {} public void windowIconified(WindowEvent e) {} public void windowActivated(WindowEvent e) {} public void windowDeactivated(WindowEvent e) {} public void windowOpened(WindowEvent e) {} public static void main(String[] args) { Frame f = new BadIdea1(); f.setSize(300,200); f.setVisible(true); } } ///:~
這樣做的用途顯示在下述三行裡:
addWindowListener(this);
b1.addActionListener(this);
b2.addActionListener(this);
因為Badidea1執行動作接收器和窗中接收器,這些程序行當然可以接受,並且如果我們一直堅持設法使少量的類去減少服務器檢索期間的程序片載入的作法,它看起來變成一個不錯的主意。但是:
(1) Java 1.1版支持JAR文件,因此所有我們的文件可以被放置到一個單一的壓縮的JAR文件中,只需要一次服務器檢索。我們不再需要為Internet效率而減少類的數量。
(2) 上面的代碼的組件更加的少,因此它難以抓住和粘貼。注意我們必須不僅要執行各種各樣的接口為我們的主類,但在actionPerformed()方法中,我們利用一串條件語句測試哪個動作被完成了。不僅僅是這個狀態倒退,遠離接收器模型,除此之外,我們不能簡單地重復使用actionPerformed()方法因為它是指定為這個特殊的應用程序使用的。將這個程序例子與GoodIdea.java進行比較,我們可以正好捕捉一個接收器類並粘貼它和最小的焦急到任何地方。另外我們可以為一個單獨的事件注冊多個接收器類,允許甚至更多的模塊在每個接收器類在每個接收器中運行。
3. 方法的混合
第二個bad idea混合了兩種方法:使用內嵌接收器類,但同樣執行一個或更多的接收器接口以作為主類的一部分。這種方法無需在書中和文件中進行解釋,而且我可以臆測到Java開發者認為他們必須為不同的目的而采取不同的方法。但我們卻不必——在我們編程時,我們或許可能會傾向於使用內嵌接收器類。
//: BadIdea2.java // An improvement over BadIdea1.java, since it // uses the WindowAdapter as an inner class // instead of implementing all the methods of // WindowListener, but still misses the // valuable modularity of inner classes import java.awt.*; import java.awt.event.*; import java.util.*; public class BadIdea2 extends Frame implements ActionListener { Button b1 = new Button("Button 1"), b2 = new Button("Button 2"); public BadIdea2() { setLayout(new FlowLayout()); addWindowListener(new WL()); b1.addActionListener(this); b2.addActionListener(this); add(b1); add(b2); } public void actionPerformed(ActionEvent e) { Object source = e.getSource(); if(source == b1) System.out.println("Button 1 pressed"); else if(source == b2) System.out.println("Button 2 pressed"); else System.out.println("Something else"); } class WL extends WindowAdapter { public void windowClosing(WindowEvent e) { System.out.println("Window Closing"); System.exit(0); } } public static void main(String[] args) { Frame f = new BadIdea2(); f.setSize(300,200); f.setVisible(true); } } ///:~
因為actionPerformed()動作完成方法同主類緊密地結合,所以難以復用代碼。它的代碼讀起來同樣是凌亂和令人厭煩的,遠遠超過了內部類方法。不合理的是,我們不得不在Java 1.1版中為事件使用那些老的思路。
4. 繼承一個組件
創建一個新類型的組件時,在運行事件的老方法中,我們會經常看到不同的地方發生了變化。這裡有一個程序例子來演示這種新的工作方法:
//: GoodTechnique.java // Your first choice when overriding components // should be to install listeners. The code is // much safer, more modular and maintainable. import java.awt.*; import java.awt.event.*; class Display { public static final int EVENT = 0, COMPONENT = 1, MOUSE = 2, MOUSE_MOVE = 3, FOCUS = 4, KEY = 5, ACTION = 6, LAST = 7; public String[] evnt; Display() { evnt = new String[LAST]; for(int i = 0; i < LAST; i++) evnt[i] = new String(); } public void show(Graphics g) { for(int i = 0; i < LAST; i++) g.drawString(evnt[i], 0, 10 * i + 10); } } class EnabledPanel extends Panel { Color c; int id; Display display = new Display(); public EnabledPanel(int i, Color mc) { id = i; c = mc; setLayout(new BorderLayout()); add(new MyButton(), BorderLayout.SOUTH); addComponentListener(new CL()); addFocusListener(new FL()); addKeyListener(new KL()); addMouseListener(new ML()); addMouseMotionListener(new MML()); } // To eliminate flicker: public void update(Graphics g) { paint(g); } public void paint(Graphics g) { g.setColor(c); Dimension s = getSize(); g.fillRect(0, 0, s.width, s.height); g.setColor(Color.black); display.show(g); } // Don't need to enable anything for this: public void processEvent(AWTEvent e) { display.evnt[Display.EVENT]= e.toString(); repaint(); super.processEvent(e); } class CL implements ComponentListener { public void componentMoved(ComponentEvent e){ display.evnt[Display.COMPONENT] = "Component moved"; repaint(); } public void componentResized(ComponentEvent e) { display.evnt[Display.COMPONENT] = "Component resized"; repaint(); } public void componentHidden(ComponentEvent e) { display.evnt[Display.COMPONENT] = "Component hidden"; repaint(); } public void componentShown(ComponentEvent e){ display.evnt[Display.COMPONENT] = "Component shown"; repaint(); } } class FL implements FocusListener { public void focusGained(FocusEvent e) { display.evnt[Display.FOCUS] = "FOCUS gained"; repaint(); } public void focusLost(FocusEvent e) { display.evnt[Display.FOCUS] = "FOCUS lost"; repaint(); } } class KL implements KeyListener { public void keyPressed(KeyEvent e) { display.evnt[Display.KEY] = "KEY pressed: "; showCode(e); } public void keyReleased(KeyEvent e) { display.evnt[Display.KEY] = "KEY released: "; showCode(e); } public void keyTyped(KeyEvent e) { display.evnt[Display.KEY] = "KEY typed: "; showCode(e); } void showCode(KeyEvent e) { int code = e.getKeyCode(); display.evnt[Display.KEY] += KeyEvent.getKeyText(code); repaint(); } } class ML implements MouseListener { public void mouseClicked(MouseEvent e) { requestFocus(); // Get FOCUS on click display.evnt[Display.MOUSE] = "MOUSE clicked"; showMouse(e); } public void mousePressed(MouseEvent e) { display.evnt[Display.MOUSE] = "MOUSE pressed"; showMouse(e); } public void mouseReleased(MouseEvent e) { display.evnt[Display.MOUSE] = "MOUSE released"; showMouse(e); } public void mouseEntered(MouseEvent e) { display.evnt[Display.MOUSE] = "MOUSE entered"; showMouse(e); } public void mouseExited(MouseEvent e) { display.evnt[Display.MOUSE] = "MOUSE exited"; showMouse(e); } void showMouse(MouseEvent e) { display.evnt[Display.MOUSE] += ", x = " + e.getX() + ", y = " + e.getY(); repaint(); } } class MML implements MouseMotionListener { public void mouseDragged(MouseEvent e) { display.evnt[Display.MOUSE_MOVE] = "MOUSE dragged"; showMouse(e); } public void mouseMoved(MouseEvent e) { display.evnt[Display.MOUSE_MOVE] = "MOUSE moved"; showMouse(e); } void showMouse(MouseEvent e) { display.evnt[Display.MOUSE_MOVE] += ", x = " + e.getX() + ", y = " + e.getY(); repaint(); } } } class MyButton extends Button { int clickCounter; String label = ""; public MyButton() { addActionListener(new AL()); } public void paint(Graphics g) { g.setColor(Color.green); Dimension s = getSize(); g.fillRect(0, 0, s.width, s.height); g.setColor(Color.black); g.drawRect(0, 0, s.width - 1, s.height - 1); drawLabel(g); } private void drawLabel(Graphics g) { FontMetrics fm = g.getFontMetrics(); int width = fm.stringWidth(label); int height = fm.getHeight(); int ascent = fm.getAscent(); int leading = fm.getLeading(); int horizMargin = (getSize().width - width)/2; int verMargin = (getSize().height - height)/2; g.setColor(Color.red); g.drawString(label, horizMargin, verMargin + ascent + leading); } class AL implements ActionListener { public void actionPerformed(ActionEvent e) { clickCounter++; label = "click #" + clickCounter + " " + e.toString(); repaint(); } } } public class GoodTechnique extends Frame { GoodTechnique() { setLayout(new GridLayout(2,2)); add(new EnabledPanel(1, Color.cyan)); add(new EnabledPanel(2, Color.lightGray)); add(new EnabledPanel(3, Color.yellow)); } public static void main(String[] args) { Frame f = new GoodTechnique(); f.setTitle("Good Technique"); f.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e){ System.out.println(e); System.out.println("Window Closing"); System.exit(0); } }); f.setSize(700,700); f.setVisible(true); } } ///:~
這個程序例子同樣證明了各種各樣的發現和顯示關於它們的信息的事件。這種顯示是一種集中顯示信息的方法。一組字符串去獲取關於每種類型的事件的信息,並且show()方法對任何圖像對象都設置了一個句柄,我們采用並直接地寫在外觀代碼上。這種設計是有意的被某種事件重復使用。
激活面板代表了這種新型的組件。它是一個底部有一個按鈕的彩色的面板,並且它由利用接收器類為每一個單獨的事件來引發捕捉所有發生在它之上的事件,除了那些在激活面板過載的老式的processEvent()方法(注意它應該同樣調用super.processEvent())。利用這種方法的唯一理由是它捕捉發生的每一個事件,因此我們可以觀察持續發生的每一事件。processEvent()方法沒有更多的展示代表每個事件的字符串,否則它會不得不使用一串條件語句去尋找事件。在其它方面,內嵌接收類早已清晰地知道被發現的事件。(假定我們注冊它們到組件,我們不需要任何的控件的邏輯,這將成為我們的目的。)因此,它們不會去檢查任何事件;這些事件正好做它們的原材料。
每個接收器修改顯示字符串和它的指定事件,並且調用重畫方法repaint()因此將顯示這個字符串。我們同樣能注意到一個通常能消除閃爍的秘訣:
public void update(Graphics g) {
paint(g);
}
我們不會始終需要過載update(),但如果我們寫下一些閃爍的程序,並運行它。默認的最新版本的清除背景然後調用paint()方法重新畫出一些圖畫。這個清除動作通常會產生閃爍,但是不必要的,因為paint()重畫了整個的外觀。
我們可以看到許多的接收器——但是,對接收器輸入檢查指令,但我們卻不能接收任何組件不支持的事件。(不像BadTechnuque.java那樣我們能時時刻刻看到)。
試驗這個程序是十分的有教育意義的,因為我們學習了許多的關於在Java中事件發生的方法。一則它展示了大多數開窗口的系統中設計上的瑕疵:它相當的難以去單擊和釋放鼠標,除非移動它,並且當我們實際上正試圖用鼠標單擊在某物體上時開窗口的會常常認為我們是在拖動。一個解決這個問題的方案是使用mousePressed()鼠標按下方法和mouseReleased()鼠標釋放方法去代替mouseClicked()鼠標單擊方法,然後判斷是否去調用我們自己的以時間和4個像素的鼠標滯後作用的“mouseReallyClicked()真實的鼠標單擊”方法。
5. 蹩腳的組件繼承
另一種做法是調用enableEvent()方法,並將與希望控制的事件對應的模型傳遞給它(許多參考書中都曾提及這種做法)。這樣做會造成那些事件被發送至老式方法(盡管它們對Java 1.1來說是新的),並采用象processFocusEvent()這樣的名字。也必須要記住調用基礎類版本。下面是它看起來的樣子。
//: BadTechnique.java // It's possible to override components this way, // but the listener approach is much better, so // why would you? import java.awt.*; import java.awt.event.*; class Display { public static final int EVENT = 0, COMPONENT = 1, MOUSE = 2, MOUSE_MOVE = 3, FOCUS = 4, KEY = 5, ACTION = 6, LAST = 7; public String[] evnt; Display() { evnt = new String[LAST]; for(int i = 0; i < LAST; i++) evnt[i] = new String(); } public void show(Graphics g) { for(int i = 0; i < LAST; i++) g.drawString(evnt[i], 0, 10 * i + 10); } } class EnabledPanel extends Panel { Color c; int id; Display display = new Display(); public EnabledPanel(int i, Color mc) { id = i; c = mc; setLayout(new BorderLayout()); add(new MyButton(), BorderLayout.SOUTH); // Type checking is lost. You can enable and // process events that the component doesn't // capture: enableEvents( // Panel doesn't handle these: AWTEvent.ACTION_EVENT_MASK | AWTEvent.ADJUSTMENT_EVENT_MASK | AWTEvent.ITEM_EVENT_MASK | AWTEvent.TEXT_EVENT_MASK | AWTEvent.WINDOW_EVENT_MASK | // Panel can handle these: AWTEvent.COMPONENT_EVENT_MASK | AWTEvent.FOCUS_EVENT_MASK | AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.CONTAINER_EVENT_MASK); // You can enable an event without // overriding its process method. } // To eliminate flicker: public void update(Graphics g) { paint(g); } public void paint(Graphics g) { g.setColor(c); Dimension s = getSize(); g.fillRect(0, 0, s.width, s.height); g.setColor(Color.black); display.show(g); } public void processEvent(AWTEvent e) { display.evnt[Display.EVENT]= e.toString(); repaint(); super.processEvent(e); } public void processComponentEvent(ComponentEvent e) { switch(e.getID()) { case ComponentEvent.COMPONENT_MOVED: display.evnt[Display.COMPONENT] = "Component moved"; break; case ComponentEvent.COMPONENT_RESIZED: display.evnt[Display.COMPONENT] = "Component resized"; break; case ComponentEvent.COMPONENT_HIDDEN: display.evnt[Display.COMPONENT] = "Component hidden"; break; case ComponentEvent.COMPONENT_SHOWN: display.evnt[Display.COMPONENT] = "Component shown"; break; default: } repaint(); // Must always remember to call the "super" // version of whatever you override: super.processComponentEvent(e); } public void processFocusEvent(FocusEvent e) { switch(e.getID()) { case FocusEvent.FOCUS_GAINED: display.evnt[Display.FOCUS] = "FOCUS gained"; break; case FocusEvent.FOCUS_LOST: display.evnt[Display.FOCUS] = "FOCUS lost"; break; default: } repaint(); super.processFocusEvent(e); } public void processKeyEvent(KeyEvent e) { switch(e.getID()) { case KeyEvent.KEY_PRESSED: display.evnt[Display.KEY] = "KEY pressed: "; break; case KeyEvent.KEY_RELEASED: display.evnt[Display.KEY] = "KEY released: "; break; case KeyEvent.KEY_TYPED: display.evnt[Display.KEY] = "KEY typed: "; break; default: } int code = e.getKeyCode(); display.evnt[Display.KEY] += KeyEvent.getKeyText(code); repaint(); super.processKeyEvent(e); } public void processMouseEvent(MouseEvent e) { switch(e.getID()) { case MouseEvent.MOUSE_CLICKED: requestFocus(); // Get FOCUS on click display.evnt[Display.MOUSE] = "MOUSE clicked"; break; case MouseEvent.MOUSE_PRESSED: display.evnt[Display.MOUSE] = "MOUSE pressed"; break; case MouseEvent.MOUSE_RELEASED: display.evnt[Display.MOUSE] = "MOUSE released"; break; case MouseEvent.MOUSE_ENTERED: display.evnt[Display.MOUSE] = "MOUSE entered"; break; case MouseEvent.MOUSE_EXITED: display.evnt[Display.MOUSE] = "MOUSE exited"; break; default: } display.evnt[Display.MOUSE] += ", x = " + e.getX() + ", y = " + e.getY(); repaint(); super.processMouseEvent(e); } public void processMouseMotionEvent(MouseEvent e) { switch(e.getID()) { case MouseEvent.MOUSE_DRAGGED: display.evnt[Display.MOUSE_MOVE] = "MOUSE dragged"; break; case MouseEvent.MOUSE_MOVED: display.evnt[Display.MOUSE_MOVE] = "MOUSE moved"; break; default: } display.evnt[Display.MOUSE_MOVE] += ", x = " + e.getX() + ", y = " + e.getY(); repaint(); super.processMouseMotionEvent(e); } } class MyButton extends Button { int clickCounter; String label = ""; public MyButton() { enableEvents(AWTEvent.ACTION_EVENT_MASK); } public void paint(Graphics g) { g.setColor(Color.green); Dimension s = getSize(); g.fillRect(0, 0, s.width, s.height); g.setColor(Color.black); g.drawRect(0, 0, s.width - 1, s.height - 1); drawLabel(g); } private void drawLabel(Graphics g) { FontMetrics fm = g.getFontMetrics(); int width = fm.stringWidth(label); int height = fm.getHeight(); int ascent = fm.getAscent(); int leading = fm.getLeading(); int horizMargin = (getSize().width - width)/2; int verMargin = (getSize().height - height)/2; g.setColor(Color.red); g.drawString(label, horizMargin, verMargin + ascent + leading); } public void processActionEvent(ActionEvent e) { clickCounter++; label = "click #" + clickCounter + " " + e.toString(); repaint(); super.processActionEvent(e); } } public class BadTechnique extends Frame { BadTechnique() { setLayout(new GridLayout(2,2)); add(new EnabledPanel(1, Color.cyan)); add(new EnabledPanel(2, Color.lightGray)); add(new EnabledPanel(3, Color.yellow)); // You can also do it for Windows: enableEvents(AWTEvent.WINDOW_EVENT_MASK); } public void processWindowEvent(WindowEvent e) { System.out.println(e); if(e.getID() == WindowEvent.WINDOW_CLOSING) { System.out.println("Window Closing"); System.exit(0); } } public static void main(String[] args) { Frame f = new BadTechnique(); f.setTitle("Bad Technique"); f.setSize(700,700); f.setVisible(true); } } ///:~
的確,它能夠工作。但卻實在太蹩腳,而且很難編寫、閱讀、調試、維護以及再生。既然如此,為什麼還不使用內部接收器類呢?