2014年2月27日 星期四

深入研究JavaFX 2

花了半年編寫的「深入研究JavaFX 2」,終於進入二校,期待新書的發表。章節包括:

1. JavaFX
2. JavaFX程式
3. Layout Pane
4. 標籤與文字
5. 按鈕
6. 選項項目
7. 捲軸、滑動軸、進度指示器與進度列
8. 選單
9. 文字編輯物件
10. 窗格 (Pane)
11. 對話盒
12. Web
13. 樹
14. 表格
15. 圖表 (Chart)
16. JavaFX事件
17. 繪圖
18. 多媒體

大部份的範例都已上網,可參考:

JavaFX Demo: http://webmail.ntcb.edu.tw/~leohuang/zh-tw/javafx.html 




My first JavaFX book in Traditional Chinese will be published in March, 2014. The topics cover:

1. Introduction of JavaFX
2. JavaFX Program
3. Layout Pane
4. Label and Text
5. Button, Check Box, Toggle Button, Radio Button, MenuButton, Split Menu Button, Tool Bar
6. Choice Box, Combo Box, List View
7. Scroll Bar, Slider, Progress Bar, Progress Indicator
8. Menu
9. Text Field, Password Field, Text Area
10. Scroll Pane, Tab Pane, Split Pane, Titled Pane, Accordion, Pagination
11. Directory Chooser, File Chooser, Popup, Color Picker
12. Web
13. Tree
14. Table
15. Chart
16. JavaFX Event
17. Canvas, Graph
18. Media

JavaFX Demo: http://webmail.ntcb.edu.tw/~leohuang/zh-tw/javafx.html 

© Chia-Hui Huang

2014年2月24日 星期一

JavaFX Event (3) Mouse Event

滑鼠最早是在1962年由Stanford Research Institute的Douglas Engelbart教授所發明,如下圖所示 (資料來源Wikipedia):
在此並向Douglas Engelbart教授致敬。
1971年Stanford Research Institute將滑鼠授權給Xerox (全錄) 的Palo Alto Research Center研究中心,1984年Apple Macintosh正式問世,主機附有單鍵滑鼠,如下圖所示 (資料來源Wikipedia)
發展至今,滑鼠的樣式日新月異,如軌跡球、Touchpad、無線滑鼠、光學滑鼠等,此外滑鼠按鍵分別有一、二、三個按鍵或滾輪等樣式:
滑鼠事件是指當使用滑鼠時所產生的事件,包括移動滑鼠 (Mouse Moved)、按下滑鼠按鍵 (Mouse Pressed)、放開滑鼠按鍵 (Mouse Released)、拖曳滑鼠 (Mouse Drag Entered) 等,並分為滑鼠事件與滑鼠拖曳事件,類別分別為MouseEventMouseDragEvent,以下為相關的設定方法:
  • setOnMouseClicked():按下並放開滑鼠按鍵。
  • setOnMouseDragged():在物件上方按住滑鼠按鍵並拖曳。
  • setOnMouseEntered():滑鼠移至物件上方。
  • setOnMouseExited():滑鼠離開物件。
  • setOnMouseMoved():滑鼠在物件上方移動。
  • setOnMousePressed():按下滑鼠按鍵。
  • setOnMouseReleased():放開滑鼠按鍵。
  • setOnMouseDragEntered():滑鼠拖曳至物件。
  • setOnMouseDragExited():滑鼠拖曳離開物件。
  • setOnMouseDragOver():滑鼠拖曳至物件上方。
  • setOnMouseDragReleased():拖曳中放開滑鼠按鍵。
滑鼠與滑鼠拖曳事件為Node抽象類別與Scene類別所定義之事件,因此凡繼承Node抽象類別的物件,均會產生滑鼠與滑鼠拖曳事件。

滑鼠事件的類別為MouseEvent,繼承自InputEvent類別,MouseEvent類別定義以下的事件類型,以做為註冊Event Handler與Event Filter之用:
  • MouseEvent.DRAG_DETECTED:偵測滑鼠拖曳。
  • MouseEvent.MOUSE_CLICKED:按下並放開滑鼠按鍵。
  • MouseEvent.MOUSE_DRAGGED:在物件上方按住滑鼠按鍵並拖曳。
  • MouseEvent.MOUSE_ENTERED:滑鼠移至物件上方。
  • MouseEvent.MOUSE_ENTERED_TARGET:滑鼠移至物件上方。
  • MouseEvent.MOUSE_EXITED:滑鼠離開物件。
  • MouseEvent.MOUSE_EXITED_TARGET:滑鼠離開物件。
  • MouseEvent.MOUSE_MOVED:滑鼠在物件上方移動。
  • MouseEvent.MOUSE_PRESSED:按下滑鼠按鍵。
  • MouseEvent.MOUSE_RELEASED:放開滑鼠按鍵。
  • MouseEvent.ANY:代表上述任一種事件類型。
請參考以下範例,介紹如何處理滑鼠事件,分別以相關方法設定滑鼠事件的Event Handler函式。

此外,並分別以getButton()getX()getY()方法取得產生滑鼠事件時所按下的滑鼠按鍵、滑鼠相對於物件的水平與垂直座標位置:


// Mouse Clicked
scene.setOnMouseClicked(new EventHandler<MouseEvent>() {
  @Override public void handle(MouseEvent e) {
    label.setText("Mouse Clicked: " + e.getButton());
  }
});

// Mouse Entered
scene.setOnMouseEntered(new EventHandler<MouseEvent>() {
  @Override public void handle(MouseEvent e) {
    label.setText("Mouse Entered: X: " + e.getX() + ", Y: " + e.getY());
  }
});

// Mouse Exited
scene.setOnMouseExited(new EventHandler<MouseEvent>() {
  @Override public void handle(MouseEvent e) {
    label.setText("Mouse Exited: X: " + e.getX() + ", Y: " + e.getY());
  }
});

// Mouse Moved
scene.setOnMouseMoved(new EventHandler<MouseEvent>() {
  @Override public void handle(MouseEvent e) {
    label.setText("Mouse Moved: X: " + e.getX() + ", Y: " + e.getY());
  }
});

// Mouse Pressed
scene.setOnMousePressed(new EventHandler<MouseEvent>() {
  @Override public void handle(MouseEvent e) {
    label.setText("Mouse Pressed: " + e.getButton());
  }
});

// Mouse Released
scene.setOnMouseReleased(new EventHandler<MouseEvent>() {
  @Override public void handle(MouseEvent e) {
    label.setText("Mouse Released: " + e.getButton());
  }
});


除了上述語法之外,亦可以建立類別的方式處理,請參考以下範例,當滑鼠移動時,則移動圖像:


// Mouse Moved
scene.setOnMouseMoved(onMouseMovedEventHandler);
...

EventHandler onMouseMovedEventHandler = new EventHandler<MouseEvent>() {
  @Override public void handle(MouseEvent e) {
    imageview.setX(e.getX());
    imageview.setY(e.getY());
  }
};


以下範例示範以addEventHandler()方法註冊事件的Event Handler,並分別處理按下滑鼠按鍵 (MouseEvent.MOUSE_PRESSED) 與在物件上方按住滑鼠按鍵並拖曳 (MouseEvent.MOUSE_DRAGGED) 之事件。當在圖像上方按住滑鼠按鍵並拖曳時,則移動圖像:


// Mouse Pressed
imageview.addEventHandler(MouseEvent.MOUSE_PRESSED, 
  new EventHandler<MouseEvent>() {
  @Override public void handle(MouseEvent e) {
    orgSceneX = e.getSceneX();
    orgSceneY = e.getSceneY();
    orgTranslateX = ((ImageView)(e.getSource())).getTranslateX();
    orgTranslateY = ((ImageView)(e.getSource())).getTranslateY();
  }
});

// Mouse Dragged
imageview.addEventHandler(MouseEvent.MOUSE_DRAGGED, 
  new EventHandler<MouseEvent>() {
  @Override public void handle(MouseEvent e) {
    double offsetX = e.getSceneX() - orgSceneX;
    double offsetY = e.getSceneY() - orgSceneY;
    double newTranslateX = orgTranslateX + offsetX;
    double newTranslateY = orgTranslateY + offsetY;

    ((ImageView)(e.getSource())).setTranslateX(newTranslateX);
    ((ImageView)(e.getSource())).setTranslateY(newTranslateY);
  }
});


接著說明滑鼠拖曳事件。

滑鼠拖曳事件的類別為MouseDragEvent,繼承自MouseEvent類別,MouseDragEvent類別定義以下的事件類型,以做為註冊Event Handler與Event Filter之用:
  • MouseDragEvent.MOUSE_DRAG_ENTERED:滑鼠拖曳至物件。
  • MouseDragEvent.MOUSE_DRAG_ENTERED_TARGET:滑鼠拖曳至物件。
  • MouseDragEvent.MOUSE_DRAG_EXITED:滑鼠拖曳離開物件。
  • MouseDragEvent.MOUSE_DRAG_EXITED_TARGET:滑鼠拖曳離開物件。
  • MouseDragEvent.MOUSE_DRAG_OVER:滑鼠拖曳至物件上方。
  • MouseDragEvent.MOUSE_DRAG_RELEASED:拖曳中放開滑鼠按鍵。
  • MouseDragEvent.ANY:代表上述任一種事件類型。
但由於MouseDragEvent類別並未提供與拖放剪貼簿 (Drag-and-Drop Clipboard) 間之互動,因此在實作上常使用DragEvent類別取代MouseDragEvent類別。

【參考資料】

[1] 黃嘉輝,深入研究JavaFX 2。
[2] 黃嘉輝,深入研究Java Swing。
[3] Java Official Web Site:http://www.oracle.com/technetwork/java/index.html
[4] JavaFX:http://www.oracle.com/technetwork/java/javafx
[5] JavaFX 2.2 API Specification.
[6] Java Platform, Standard Edition 7 API Specification.

© Chia-Hui Huang

2014年2月23日 星期日

JavaFX Sudoku

數獨 (Sudoku),日語為すうどく,源自於拉丁方陣 (Latin Square),拉丁方陣由瑞士數學家Leonhard Paul Euler以拉丁字母做為方陣中的元素而命名,拉丁方陣為一n階矩陣,在矩陣中恰有n種不同的元素,每一橫列或直行中同一元素只能出現一次,例如以下範例:
1895年法國報紙La France發表名為Carré Magique Diabolique的九階遊戲,與現在的數獨十分類似。1979年拉丁方陣在美國發展,由Howard GarnsNumber Place為名發表於Dell Magazines上。

1984Number Place流傳至日本,由ニコリ遊戲公司在其「パズル通信ニコリ」雜誌中首次發表,由其社長かじ まき (鍜治 真起) 將此遊戲命名為數獨 (すうどく),原文為「数字は独身に限る」,意思是每一格只能有一個數字鍜治 真起因此亦被稱為數獨之父。

1997年紐西蘭籍的香港高等法院法官Wayne Gould在日本旅遊時,無意間發現數獨,將其帶回英國,並於2004年首次刊登於The Times (泰晤士報),Wayne Gould並以電腦程式編寫數獨,發表於網站上,因此數獨很快就在全世界流行。

數獨於2005年由中國時報引進臺灣,於2009年成立臺灣數獨發展協會 (Taiwan Sudoku Association),並成為世界解謎聯盟(World Puzzle Federation)的會員之一。此外,尤怪之家為臺灣有關數獨資源最豐富的網站,網站並提供相當多的數獨遊戲與討論數獨的解題方法,相當值得參考。 

最早的數獨為一種九階方格的填數字遊戲,並分成九等份,每一等份由3×3的格子組成,遊戲規則很簡單,每一格子只能填入1至9其中一個數字,此外,每一數字在橫列、直行與3×3格子也只能出現一次。 

數獨盛行多年,發展出一系列的解題方法,依據臺灣數獨發展協會與尤怪之家的整理,包括直觀法、餘數法、摒餘法、唯一法、二餘法、三餘法、四餘法、數對唯餘法、數對摒除法、雙候選數佔位法、區塊數對唯餘法、宮摒餘法、行列摒餘法、基礎摒除法、區塊摒除法、單元摒除法、矩形摒除法等,相當豐富。 

數獨發展至今,有以下之變形: 

  • 迷你數獨 (Mini Sudoku):由六階方格所組成,並分成六等份,每一等份由3×2的格子組成。
  • 殺手數獨 (Killer Sudoku):與數獨同為與九階方格的填數字遊戲,並分成九等份,每一等份由3×3的格子組成,殺手數獨並結合數獨與數和 (Kakuro,日語為カックロ) 遊戲。
  • 字母數獨 (Alphabetical Sudoku):又稱為Wordoku,將數字改為英文字母,特殊之處在於除了需符合數獨的基本規則之外,最後必須在其對角線找出正確的英文單字。
  • 超數獨 (Hypersudoku):與數獨同為與九階方格的填數字遊戲,並分成九等份,每一等份由3×3的格子組成,除了需符合數獨的基本規則之外,此外,每一數字在額外的3×3顏色格子也只能出現一次。
  • 巨形數獨 (Dodeka Sudoku):由九階方格組成,並分成十二等份,每一等份由4×3的格子組成,其餘規則如同傳統數獨。
  • 武士數獨 (Samurai Sudoku):由41個3×3的格子組成。 

數獨沒有牽涉到很複雜的數學,僅需要單純的直覺邏輯推理則可完成,因此很適合各年齡層的玩家。

自2009年筆者買了第一支iPod Touch之後,數獨一直是我最喜歡的遊戲,我深信常玩數獨會減少老人癡呆症罹患的機率,我也一直希望能自己寫一個數獨遊戲。

數獨是一個典型的矩陣遊戲,此次以JavaFX設計數獨,並以Apple iPad的Sudoku為範本,難度分為Novice, Easy, Medium, Hard與Master。
Level: Hard

Example: Link

© Chia-Hui Huang

JavaFX Scene Builder與FXML

首篇刊登於CodeData網站,關於JavaFX Scene Builder與FXML:

http://www.codedata.com.tw/java/javafx-scene-builder-fxml/

還有在JavaFX Coummunity上輪播。

© Chia-Hui Huang

2014年2月22日 星期六

JavaFX MineSweeper

踩地雷(MineSweeper)應該不需多加介紹與解釋,凡是Microsoft Windows的使用者,想必都玩過踩地雷這個有趣的遊戲,Microsoft Windows的踩地雷最早原是由Curt Johnson在IBM OS/2上所開發的版本,後來由Robert Donner與Curt Johnson移植至Microsoft Windows 3.0上,一直延續至今。

踩地雷遊戲的目的是在不掀開任何藏有地雷方格的情況下,以最快速度找出所有的地雷,如果掀起含有地雷的方格時,就算輸了。

遊戲的玩法與策略如下:
  • 踩地雷提供初級、中級、高級與自訂等四種不同等級的遊戲模式,分別為9×9、16×16、30×16等之地雷方格,並預設藏有10、40、99個地雷,並隨機分佈於地雷區四周。
  • 當以滑鼠左鍵點選地雷方格時,則算掀開方格,若為空白則代表安全,若方格下藏有地雷則算輸,另外,若掀開為數字時,則代表此方格四周含有之地雷數目。
  • 若要標示某方格可能藏有地雷時,可以在方格上按一下滑鼠右鍵,則會標示一旗子,以代表可能藏有地雷,此動作稱為Flag(插旗)。
  • 若不確定某方格是否藏有地雷時,可以在方格上連按兩下滑鼠右鍵,則會標示一問號,以代表不確定是否藏有地雷,此動作稱為Question Mark(標記問號)。
  • 若已找出所有圍繞在數字方格的地雷時,則可同時在該數字的方格上,按一下滑鼠左鍵和右鍵,以掀開四周剩餘的方格。如果已標記數字的方塊周圍不全是地雷,當滑鼠二個按鈕同時按一下數字標記的方格時,則會按下其他未掀開的或未標記的方格。
此次以JavaFX 2設計MineSweeper,並以Microsoft Windows的WinMine為範本,難度分為Basic, Medium與Master,並加入LED顯示時間與剩餘地雷數。
Basic Level

Medium Level

Master Level

2001年由於International Campaign to Ban WinMine(國際禁止踩地雷運動)認為此遊戲冒犯了地雷受難者,因此建議Microsoft將WinMine改為WinFlower,以花朵代替地雷,本遊戲亦入境隨俗,加入花朵圖像,可選擇地雷或花朵: 
Example: Link

© Chia-Hui Huang

JavaFX Event (2) Key Event

接著上一篇JavaFX Event (1) Introduction介紹JavaFX處理事件基本概念,本篇將說明按鍵事件

按鍵事件 (Key Event) 是當使用者按下或放開鍵盤按鍵時所產生的事件,除了A~Z與0~9等按鍵之外,最常處理的按鍵包括上下左右鍵、Page Up、Page Down、Alt、Shift、Ctrl等特殊功能鍵。

按鍵事件包括按下鍵盤按鍵 (Pressed)、放開鍵盤按鍵 (Released) 與按下並放開鍵盤按鍵 (Typed) 等三種,前兩者為低階 (Low-Level) 並與作業平台及鍵盤配置有關的事件,後者則是高階 (High-Level) 並與作業平台及鍵盤配置無關的事件。

JavaFX按鍵事件的類別為KeyEvent,繼承自InputEvent類別,按鍵事件為Node抽象類別與Scene類別所定義之事件,因此凡繼承Node抽象類別的物件,均會產生按鍵事件。

按鍵事件可分別以setOnKeyPressed()setOnKeyReleased()setOnKeyTyped()方法設定按下鍵盤按鍵、放開鍵盤按鍵與按下並放開鍵盤按鍵的Event Handler函式。

KeyEvent類別定義以下的事件類型,以做為註冊Event Handler與Event Filter之用:
  • KeyEvent.KEY_PRESSED:按下鍵盤按鍵。
  • KeyEvent.KEY_RELEASED:放開鍵盤按鍵。
  • KeyEvent.KEY_TYPED:按下並放開鍵盤按鍵。
  • KeyEvent.ANY:代表上述任一種事件類型。
請參考以下範例,介紹如何處理鍵盤按鍵事件,範例分別以setOnKeyPressed()setOnKeyReleased()setOnKeyTyped()方法設定按下鍵盤按鍵、放開鍵盤按鍵與按下並放開鍵盤按鍵的Event Handler函式。

setOnKeyPressed()設定為例,分別以isAltDown()isControlDown()isShiftDown()方法判斷是否按下Alt鍵、Ctrl鍵與Shift鍵,此外並以getCode()方法取得按鍵值,判斷是否按下上下左右鍵、上一頁與下一頁按鍵,藉此移動視窗:


// Key Pressed
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
  @Override public void handle(KeyEvent e) {
    int dx = 0;
    int dy = 0;

    // 判斷是否按下Alt鍵
    if (e.isAltDown()) 
      label.setText("Press Alt key") ;

    // 判斷是否按下Ctrl鍵
    if (e.isControlDown()) 
      label.setText("Press Control key") ;

    // 判斷是否按下Shift鍵
    if (e.isShiftDown()) 
      label.setText("Press Shift key") ;

    label.setText("Press " + e.getCode().getName()) ;
    
    // 取得按鍵按鍵值,判斷是否按下上下左右鍵、上一頁與下一頁按鍵
    if (e.getCode() == KeyCode.UP)
      dy = -5;
    else if (e.getCode() == KeyCode.DOWN) 
      dy = 5;
    else if (e.getCode() == KeyCode.LEFT) 
      dx = -5;
    else if (e.getCode() == KeyCode.RIGHT) 
      dx = 5;
    else if (e.getCode() == KeyCode.PAGE_UP) 
      dy = -100;
    else if (e.getCode() == KeyCode.PAGE_DOWN) 
      dy = 100;
    
    stage.setX(stage.getX() + dx);
    stage.setY(stage.getY() + dy);
  }
});


除了上述語法之外,亦可以建立類別的方式處理,請參考以下範例:


// Key Pressed
scene.setOnKeyPressed(onKeyPressedEventHandler);
...

EventHandler onKeyPressedEventHandler = new EventHandler<KeyEvent>() {
  @Override public void handle(KeyEvent e) {
    int dx = 0;
    int dy = 0;

    // 判斷是否按下Alt鍵
    if (e.isAltDown()) 
      label.setText("Press Alt key") ;

    // 判斷是否按下Ctrl鍵
    if (e.isControlDown()) 
      label.setText("Press Control key") ;

    // 判斷是否按下Shift鍵
    if (e.isShiftDown()) 
      label.setText("Press Shift key") ;

    label.setText("Press " + e.getCode().getName()) ;
    
    // 取得按鍵按鍵值,判斷是否按下上下左右鍵、上一頁與下一頁按鍵
    if (e.getCode() == KeyCode.UP)
      dy = -5;
    else if (e.getCode() == KeyCode.DOWN) 
      dy = 5;
    else if (e.getCode() == KeyCode.LEFT) 
      dx = -5;
    else if (e.getCode() == KeyCode.RIGHT) 
      dx = 5;
    else if (e.getCode() == KeyCode.PAGE_UP) 
      dy = -100;
    else if (e.getCode() == KeyCode.PAGE_DOWN) 
      dy = 100;
    
    stage.setX(stage.getX() + dx);
    stage.setY(stage.getY() + dy);
  }
};


第二種方式是以addEventHandler()方法註冊事件的Event Handler,請參考以下範例,以按下鍵盤按鍵事件為例,其程式如下:


// Key Pressed
scene.addEventHandler(KeyEvent.KEY_PRESSED, 
  new EventHandler<KeyEvent>() {
  @Override public void handle(KeyEvent e) {
    int dx = 0;
    int dy = 0;

    // 判斷是否按下Alt鍵
    if (e.isAltDown()) 
      label.setText("Press Alt key") ;

    // 判斷是否按下Ctrl鍵
    if (e.isControlDown()) 
      label.setText("Press Control key") ;

    // 判斷是否按下Shift鍵
    if (e.isShiftDown()) 
      label.setText("Press Shift key") ;

    label.setText("Press " + e.getCode().getName()) ;
    
    // 取得按鍵按鍵值,判斷是否按下上下左右鍵、上一頁與下一頁按鍵
    if (e.getCode() == KeyCode.UP)
      dy = -5;
    else if (e.getCode() == KeyCode.DOWN) 
      dy = 5;
    else if (e.getCode() == KeyCode.LEFT) 
      dx = -5;
    else if (e.getCode() == KeyCode.RIGHT) 
      dx = 5;
    else if (e.getCode() == KeyCode.PAGE_UP) 
      dy = -100;
    else if (e.getCode() == KeyCode.PAGE_DOWN) 
      dy = 100;
    
    stage.setX(stage.getX() + dx);
    stage.setY(stage.getY() + dy);
  }
});


第三種方式是以addEventFilter()方法註冊事件的Event Filter,請參考以下範例,幾乎與上述範例一樣:


// Key Pressed
scene.addEventFilter(KeyEvent.KEY_PRESSED, 
  new EventHandler<KeyEvent>() {
  @Override public void handle(KeyEvent e) {
    int dx = 0;
    int dy = 0;

    // 判斷是否按下Alt鍵
    if (e.isAltDown()) 
      label.setText("Press Alt key") ;

    // 判斷是否按下Ctrl鍵
    if (e.isControlDown()) 
      label.setText("Press Control key") ;

    // 判斷是否按下Shift鍵
    if (e.isShiftDown()) 
      label.setText("Press Shift key") ;

    label.setText("Press " + e.getCode().getName()) ;
    
    // 取得按鍵按鍵值,判斷是否按下上下左右鍵、上一頁與下一頁按鍵
    if (e.getCode() == KeyCode.UP)
      dy = -5;
    else if (e.getCode() == KeyCode.DOWN) 
      dy = 5;
    else if (e.getCode() == KeyCode.LEFT) 
      dx = -5;
    else if (e.getCode() == KeyCode.RIGHT) 
      dx = 5;
    else if (e.getCode() == KeyCode.PAGE_UP) 
      dy = -100;
    else if (e.getCode() == KeyCode.PAGE_DOWN) 
      dy = 100;
    
    stage.setX(stage.getX() + dx);
    stage.setY(stage.getY() + dy);
  }
});


【參考資料】

[1] 黃嘉輝,深入研究JavaFX 2。
[2] 黃嘉輝,深入研究Java Swing。
[3] Java Official Web Site:http://www.oracle.com/technetwork/java/index.html
[4] JavaFX:http://www.oracle.com/technetwork/java/javafx
[5] JavaFX 2.2 API Specification.
[6] Java Platform, Standard Edition 7 API Specification.

© Chia-Hui Huang

2014年2月21日 星期五

JavaFX Event (1) Introduction

欲建立JavaFX程式與使用者間的互動,需處理物件的事件,所謂事件 (Event) 是指當物件其狀態改變時所觸發產生的相關動作,例如按下滑鼠按鍵、滑鼠拖曳、按下鍵盤按鍵等,均會觸發其相對應的事件。

首先回顧Java處理事件的程式架構。

在Java AWT與Java Swing中,處理事件的方式有兩種,第一種方式為實作處理事件的Listener介面,各Listener介面提供處理事件的方法。其次為處理事件的類別,並分為Event類別與Adapter抽象類別,以定義物件所產生的事件。

以實作Listener介面為例,程式需實作介面所提供的全部方法,不論是否使用到,均需全部實作於程式中,若需處理某一事件,則將Listener介面所提供的方法內容加以覆寫 (Override)。此外,Listener介面需以下列方法處理:
  • addXXXListener():註冊事件的Listener,以處理物件所產生的相關事件。
  • getXXXListeners():取得物件所註冊的Listener,並以陣列回傳所註冊的所有相關事件。
  • removeXXXListener():移除物件所註冊的事件Listener。
以下為以JFrame類別實作MouseListener介面以處理滑鼠事件的程式架構:


import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

import javax.swing.event.*;

// 實作MouseListener介面

public class JavaEventDemo extends javax.swing.JFrame 
  implements MouseListener {

  // 建構函式

  public JavaEventDemo() {
    ...  
    // 註冊MouseListener
    this.addMouseListener(this);
  }

  // 實作MouseListener介面的方法

  public void mouseClicked(MouseEvent e) {...}
  public void mouseEntered(MouseEvent e) {...}
  public void mouseExited(MouseEvent e) {...}
  public void mousePressed(MouseEvent e) {...}
  public void mouseReleased(MouseEvent e) {...}
  ...  
}


在實作Listener介面時,即使未使用到該事件的方法,仍需將介面所提供的方法全部描述一遍,並將方法的內容以空白表示,欲處理事件則覆寫該介面的方法。

除了實作介面之外,另一種處理事件的方法為事件類別,並分為Event類別與Adapter抽象類別,以定義不同物件所產生的事件。Event類別繼承自java.util.EventObject類別,並對應於Listener介面,例如以MouseListener介面為例,其Event類別則為MouseEvent

其次,Adapter抽象類別為Listener介面相對應的類別,同樣提供與Listener介面一樣的方法,但不同的是,使用上是以繼承Adapter抽象類別,但僅處理所發生的方法,而未發生的方法則不需重新描述一遍,因此可精簡程式。以下是以Inner Class的方式使用Adapter類別:


import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

import javax.swing.event.*;

public class JavaEventDemo extends javax.swing.JFrame


  // 建構函式

  public JavaEventDemo() {
    ...
    // 以Inner Class的方式使用滑鼠的Adapter類別
    this.addMouseListener(new MouseInputAdapter() {
      // 處理滑鼠事件
      public void mousePressed(MouseEvent e) {
        ...
      }
    });
  }
}


以下為繼承Adapter抽象類別的程式架構:


import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

import javax.swing.event.*;

public class JavaEventDemo extends javax.swing.JFrame

  // 建構函式
  public JavaEventDemo() {
    ...
    // 以自訂MouseAdapter類別建立MouseListener
    this.addMouseListener(new MyMouseAdapter());
    ...
  }
}

// 繼承MouseAdapter

class MyMouseAdapter extends MouseInputAdapter {

  // 建構函式

  public MyMouseAdapter() {
    ...
  }
  // 處理滑鼠事件
  public void mousePressed(MouseEvent e) { 
    ...
  }
}


接著說明JavaFX處理事件的程式架構。

JavaFX簡化處理事件的方式,不像Java以不同的Listener介面處理各類事件,JavaFX僅以javafx.event.EventHandler介面處理各類事件,且介面僅提供handle()方法

至於如何以單一的EventHandler介面處理各類事件呢?這點可說是JavaFX的一項重大改變。

JavaFX以事件類型 (Event Type) 定義各類事件,其基礎類別為javafx.event.Event,繼承自Event類別的事件類別分別如下所示,其中較特殊的是手勢與觸控事件,由於目前大部份裝置均支援觸控螢幕 (Touch Screen) 與觸控板 (Touch Pad),因此JavaFX增加處理多點觸控 (Multi-Touch) 的事件類別:
  • ActionEvent:動作事件。
  • ContextMenuEvent:快顯選單事件。
  • DragEvent:拖曳事件,適用於滑鼠與觸控裝置。
  • GestureEvent:手勢事件,適用於觸控裝置。
  • InputEvent:輸入事件。
  • InputMethodEvent輸入方法事件。
  • KeyEvent按鍵事件。
  • MediaErrorEvent多媒體錯誤事件。
  • MouseEvent滑鼠事件。
  • MouseDragEvent滑鼠拖曳事件,有別於DragEvent。
  • RotateEvent旋轉事件,適用於觸控裝置。
  • ScrollEvent捲動事件,適用於滑鼠與觸控裝置。
  • SwipeEvent滑動事件,適用於觸控裝置。
  • TouchEvent觸控事件,適用於觸控裝置。
  • WebEventWeb Engine事件。
  • WindowEvent視窗事件。
  • WorkerStateEventWorker狀態改變時之事件。
  • ZoomEvent縮放事件,適用於觸控裝置。
JavaFX處理事件的第一種方式是以物件的setOnXXX()方法設定處理事件的Event Handler函式,其語法如下,其中[Event_TYPE]為上述之事件類別:


[object].setOnXXX(new EventHandler<[Event_TYPE]>() {
  @Override public void handle([Event_TYPE] e) {
    ...
  }
});


以按鈕的動作事件為例,其設定Event Handler函式的方法為setOnAction()、事件類別為ActionEvent


Button button = new Button();

button.setOnAction(new EventHandler<ActionEvent>() {

  @Override public void handle(ActionEvent e) {
    ...
  }
});


以按鈕的滑鼠移動事件為例,其設定方法為setOnMouseMoved()、事件類別為MouseEvent


Button button = new Button();

button.setOnMouseMoved(new EventHandler<MouseEvent>() {

  @Override public void handle(MouseEvent e) {
    ...
  }
});


由上述說明可以瞭解,JavaFX以單一EventHandler介面與不同事件類別的組合處理事件,相較於Java以實作處理事件的Listener介面、或使用Event類別與Adapter抽象類別,JavaFX處理事件的方式更為簡單。

除了上述語法之外,亦可以建立類別的方式處理,以按鈕的動作事件為例:


Button button = new Button();
button.setOnAction(onActionEventHandler);
...

EventHandler onActionEventHandler = new EventHandler<ActionEvent>() {

  @Override public void handle(ActionEvent e) {
    ...
  }
};


除了以setOnXXX()方法設定處理事件的Event Handler函式之外,JavaFX處理事件的第二種方式是以物件的addEventHandler()方法註冊事件的Event Handler,其語法如下,其中[eventType]代表事件類型、[Event_TYPE]為前述之事件類別:


[object].addEventHandler([eventType], new EventHandler<[Event_TYPE]>() {
  @Override public void handle([Event_TYPE] e) {
    ...
  }
});


以按鈕的動作事件為例,其事件類型為ActionEvent.ACTION、事件類別為ActionEvent,註冊Event Handler的程式架構如下:


Button button = new Button();

button.addEventHandler(ActionEvent.ACTION,

  new EventHandler<ActionEvent>() {
    @Override public void handle(ActionEvent e) {
      ...
    }
  }
);


除了上述語法之外,亦可以建立類別的方式處理,以按鈕的動作事件為例:


Button button = new Button();
button.addEventHandler(ActionEvent.ACTION, onActionEventHandler);
...

EventHandler onActionEventHandler = new EventHandler<ActionEvent>() {

  @Override public void handle(ActionEvent e) {
    ...
  }
};


JavaFX處理事件的第三種方式是以物件的addEventFilter()方法註冊事件的Event Filter,其語法如下,與Event Handler幾乎一樣,其中[eventType]代表事件類型、[Event_TYPE]為前述之事件類別:


[object].addEventFilter([eventType], new EventHandler<[Event_TYPE]>() {
  @Override public void handle([Event_TYPE] e) {
    ...
  }
});


以按鈕的動作事件為例,其事件類型為ActionEvent.ACTION、事件類別為ActionEvent,註冊Event Filter的程式架構如下:


Button button = new Button();

button.addEventFilter(ActionEvent.ACTION,

  new EventHandler<ActionEvent>() {
    @Override public void handle(ActionEvent e) {
      ...
    }
  }
);


除了上述語法之外,亦可以建立類別的方式處理,以按鈕的動作事件為例:


Button button = new Button();
button.addEventFilter(ActionEvent.ACTION, onActionEventFilter);
...

EventHandler onActionEventFilter = new EventHandler<ActionEvent>() {

  @Override public void handle(ActionEvent e) {
    ...
  }
};


【參考資料】

[1] 黃嘉輝,深入研究JavaFX 2。
[2] 黃嘉輝,深入研究Java Swing。
[3] Java Official Web Site:http://www.oracle.com/technetwork/java/index.html
[4] JavaFX:http://www.oracle.com/technetwork/java/javafx
[5] JavaFX 2.2 API Specification.
[6] Java Platform, Standard Edition 7 API Specification.

© Chia-Hui Huang