1.33k likes | 1.57k Views
圖形程式設計簡介. 簡介. 圖形介面 (Graphical User Interface ;簡稱 GUI ) GUI 程式的開發主要使用 java.awt (簡稱 AWT )或者 javax.swing (簡稱 Swing ) 目前的主流是 Swing ,最主要原因有幾個: 一個是 AWT 的類別並不是全部都使用 Java 來開發的,所以在不同的作業系統上執行的時候,同一個程式可能會有不同的行為,這對於當初設計 Java 的理念 —“ 開發一次、到處都可用” — 有衝突 因此到了設計 Swing 的時候,其全部的類別都是以 Java 開發而成
E N D
簡介 • 圖形介面 (Graphical User Interface;簡稱 GUI) • GUI 程式的開發主要使用 java.awt(簡稱AWT)或者javax.swing(簡稱Swing) • 目前的主流是 Swing,最主要原因有幾個: • 一個是 AWT 的類別並不是全部都使用 Java 來開發的,所以在不同的作業系統上執行的時候,同一個程式可能會有不同的行為,這對於當初設計 Java 的理念—“開發一次、到處都可用”—有衝突 • 因此到了設計 Swing 的時候,其全部的類別都是以 Java開發而成 • 另一個主因是 Swing 提供的圖形介面比 AWT 更豐富、也更細膩
視窗程式設計 • 兩大主軸: • GUI 元件之間的關係,以及 GUI 元件的使用 • 事件處理機制 • 事件處理機制大多以 inner class 來完成
GUI元件 • 上圖左邊的視窗就是一個 JFrame 的物件 • 而一個 JFrame 的物件可以包含一個工具列的物件以及一個 Content Pane 的物件 • Content Pane 物件就像是一個容器,在這個容器中,我們可以加上各種 GUI 元件,例如標籤(JLabel)、文字欄位(JTextField)、按鈕(JButton)等 • 在 Content Pane 裡面,我們也可以放置其他的容器元件,例如 JPanel • 也就是說,一個 Content Pane 的物件可以包含多個像是JPanel 的元件,而每一個 JPanel 元件內,又可以放上像是標籤、文字欄位、或者按鈕類的 GUI 元件
JFrame 類別 • 一個視窗是一個JFrame 的物件,一個最簡單的視窗程式如下所示 01 import javax.swing.*; 02 03 public class TestFrame1 { 04 public static void main( String[] args ) { 05 JFrame f = new JFrame(); 06 f.setVisible(true); 07 } 08 }
setDefaultCloseOperation() • 前一個視窗,按 ”X” ,視窗不見了,卻無法把程式停掉! • 原因:缺少了”事件處理機制” import javax.swing.*; public class TestFrame2 { public static void main( String[] args ) { JFrame f = new JFrame(); f.setVisible(true); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } }
不用setDefaultCloseOperation()卻能關閉視窗程式的方式 01 import javax.swing.*; 02 import java.awt.event.*; 03 04 public class TestFrame3 { 05 public static void main( String[] args ) { 06 JFrame f = new JFrame(); 07 f.setVisible(true); 08 09 f.addWindowListener( 10 new WindowAdapter() { // inner class and adapter class 11 public void windowClosing( WindowEvent e ) { 12 // 可加入檔案關閉、視窗位置等敘述 13 System.exit( 0 ); } } ); 14 15 } 16 } 大多數的 Swing 程式都採用這種方式,因為比較有彈性。我們晚一點解釋
視窗標題的設定、視窗的大小、以及視窗出現的位置視窗標題的設定、視窗的大小、以及視窗出現的位置 01 import javax.swing.*; 03 public class TestFrame4 { 04 public static void main( String[] args ) { 05 JFrame f = new JFrame(); 06 07 f.setTitle("Hello World"); 08 f.setSize( 400, 300); 09 f.setLocation(100, 150); 11 f.setVisible(true); 12 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 13 } 14 }
View 類別 • 常見的視窗程式設計的方式:設計 View 類別(JFrame 的子類別),其主要的功能在於提供輸入或者輸出的界面 import javax.swing.*; public class TestFrame5 extends JFrame { public TestFrame5() { setTitle("Hello World"); setSize( 400, 300); setLocation(100, 150); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main( String[] args ) { TestFrame5 f = new TestFrame5(); f.setVisible(true); } }
範例 • 設計一個視窗程式讓使用者輸入一個 Worker 的工作時數,而且在工作時數輸入完成後,由 Worker 物件計算薪資,最後由程式把薪資顯示到螢幕上 • 我們需要在把 GUI 元件加到 JFrame 內,用到的 GUI 元件包含標籤(JLabel)、文字欄位(JTextField)、以及按鈕(JButton) • 如 GUI 元件架構的說明,我們不能把 GUI 元件直接加到 JFrame 上,而是要加到 JFrame的 content pane 上
GUI 容器 • GUI 容器都有自己的 content pane • 例如 JFrame、JApplet、以及JDialog • 找出該容器的 content pane 需經由呼叫該容器的 getContentPane() 取得 • 例如,若 f 代表一個 JFrame 的物件,則 • Container cp = f.getContentPane(); • 在取得 content pane 的物件後,我們就可以經由 content pane 的 add() 方法把 GUI 元件放到視窗內
範例 04 public class TestGUI1 extends JFrame { 05 private Container cp; 06 private JLabel inpLabel, outLabel; 07 private JTextField input, output; 08 private JButton button; 09 10 public TestGUI1() { 11 setTitle("薪資計算"); 12 setSize(250, 200); 13 setLocation(100, 150); 14 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 15 16 cp = getContentPane(); 17 inpLabel = new JLabel("工作時數"); 18 input = new JTextField(20); 19 outLabel = new JLabel("薪資"); 20 output = new JTextField(20); 21 button = new JButton("計算薪資");
範例 22 23 cp.setLayout(new FlowLayout()); //default:BorderLayout 24 cp.add(inpLabel); 25 cp.add(input); 26 cp.add(outLabel); 27 cp.add(output); 28 cp.add(button); 29 } 30 31 public static void main( String[] args ) { 32 TestGUI1 f = new TestGUI1(); 33 f.setVisible(true); 34 } 35 } 續上頁程式
範例 • 執行 TestGUI1 • 我們可以在”工作時數”欄位輸入資料 • 但是,不論是按 Enter 或者是在按鈕上點一下,並不會發生任何事情! • 對於版面管理員(Layout Manager)有興趣的,可以試著變更視窗的大小,並觀察 GUI 元件擺放位置的變化。 • JFrame 的預設 Layout 是 BorderLayout。
事件處理機制 • 我們先解釋事件(event) • 所謂的事件,指的是所有發生在 GUI 元件上的使用者行為 • 例如,在按鈕上按(click)一下,在文字欄位在按”Enter”鍵,在視窗上移動滑鼠等,都是一個個的事件 • 這種由於發生事件的不同,而決定程式行為的模式,一般稱之為”事件驅動模式”(event-driven)
委任式的事件處理模式(Delegation-based event model) • 在委任式的事件處理模式中,有兩種主要的參與者 • 一種是原始事件物件(event source objects) • 還有一個隱藏的監控者 • 另一種是事件聆聽者物件(event listener objects) • 所謂的原始事件物件指的是事件發生的那個 GUI物件 • 例如,使用者在按鈕 A 上按了一下,則按鈕 A 即為原始事件物件 • 例如,使用者在文字欄位上輸入資料後,按了”Enter”鍵,則該文字欄位就是原始事件物件
委任式的事件處理模式 • 在應用程式中,原始事件物件可能會有多個,但是並不是每一個原始事件物件都要參與事件處理 • 例如:我們的範例中,我們可以只讓按鈕參與。 • 想要參與事件處理的原始事件物件必須註冊 • 註冊的時候,我們必須同時註明處理該事件的事件聆聽者物件
委任式的事件處理模式 • 事件聆聽者物件是一個繼承 EventListener 的物件 • java.util.EventListener 是一個 Interface,是所有事件聆聽者物件(或者類別)的父類別 • 事件聆聽者物件的資料型態會隨著事件的不同,而有不同的資料型態 • 例如:若原始事件物件是一個 JButton 的物件,則事件的類別是 java.awt.event.ActionEvent,而對應於 ActionEvent 的事件聆聽者物件是一個 java.awt.event.ActionListener 的物件
委任式的事件處理模式 • JButton 的事件聆聽者物件必須繼承ActionListener;由於 ActionListener 是一個 Interface,且該介面型態含有一個 actionPerformed()的方法,因此該事件聆聽者物件也必須實作出 actionPerformed() • 請記住,每一個 GUI 元件都可以有一個以上的事件聆聽者物件,至於該事件聆聽者物件屬於何種 EventListener,在 Java 中都有一定的規定,而且每一種 EventListener 都有其特定的抽象方法必須實作出來
監控者(monitor) • 負責隨時監控事件的發生 • 一旦一個事件發生了,它知道”事件發生在哪一個原始事件物件”,然後依據之前註冊的原始事件物件呼叫對應的事件聆聽者物件方法 • 由於事件的處理是由事件聆聽者物件來進行,所以才稱為委任式事件處理模式(由監控者委任事件聆聽者物件完成事件處理) • 可以把監控者物件當成 JVM 的一部份功能
JButton 註冊的方式 • 由於 JButton 的事件聆聽者物件屬於ActionListener,所以註冊的方式如下 • button.addActionListener(ActionListener 物件); • button 是 JButton 物件 • “ActionListener 物件”的對應類別必須是一個 implements ActionListener 的類別
JButton 的事件聆聽者 • 我們希望在使用者在 JButton 上點一下的時候,能夠”取得使用者輸入的工作時數,並經由 Worker 物件計算出薪資“ • 經由之前的討論,我們知道這些動作必須由 actionPerformed() 來完成。 • 假設 JButton 的事件聆聽者的類別名稱為SalaryHandler,其設計方式如下頁。
JButton 的事件聆聽者 • SalaryHandler 01 import javax.swing.*; 02 import java.awt.*; 03 import java.awt.event.*; 04 05 public class SalaryHandler implements ActionListener { 06 public void actionPerformed(ActionEvent e) { 07 JButton button = (JButton) e.getSource(); 08
JButton 的事件聆聽者 • 續上頁的程式 09 // 從 button 找出它所屬的 content pane 10 JRootPane rp = button.getRootPane(); 11 Container cp = rp.getContentPane(); 13 JTextField input = (JTextField) cp.getComponent(1); 14 int h = Integer.parseInt(input.getText()); 15 Worker w = new Worker(h); 16 JTextField output = (JTextField) cp.getComponent(3); 17 output.setText("薪資為 " + w.computeSalary()); 18 } 19 }
Root Pane vs. Content Pane 資料來源:http://java.sun.com/docs/books/tutorial/uiswing/components/toplevel.html#rootpane
GUI 元件之間的關係 • 經由 ActionEvent,我們可以知道事件發生在 JButton • 但是 JButton 和 JTextField 彼此獨立;也就是說 JButton 不知道 JTextField 是否存在 • 目前只有 Content Pane 知道它自己包含了哪些 GUI 元件 • 還記得我們之前將 GUI 元件加到 Content Pane 的過程? • 但是,JButton 無法直接取得 Content Pane,但卻可以取得 Root Pane;而因為 Root Pane 管理 Content Pane,因此可以經由 Root Pane 取得 Content Pane。
範例 • TestGUI2,其程式碼如下: 01 import javax.swing.*; 02 import java.awt.*; 03 04 public class TestGUI2 extends JFrame { 05 private Container cp; 06 private JLabel inpLabel, outLabel; 07 private JTextField input, output; 08 private JButton button; 09 10 public TestGUI2() { 11 setTitle("薪資計算"); 12 setSize(250, 200); 13 setLocation(100, 150); 14 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
16 inpLabel = new JLabel("工作時數"); 17 input = new JTextField(20); 18 outLabel = new JLabel("薪資"); 19 output = new JTextField(20); 20 button = new JButton("計算薪資"); 21 22 cp = getContentPane(); 23 cp.setLayout(new FlowLayout()); 24 cp.add(inpLabel); 25 cp.add(input); 26 cp.add(outLabel); 27 cp.add(output); 28 cp.add(button); 29 30 // register 31 button.addActionListener(new SalaryHandler()); 32 } 33 34 public static void main( String[] args ) { 35 TestGUI2 f = new TestGUI2(); 36 f.setVisible(true); 37 } 38 } 範例 續上頁的程式
事件聆聽者的設計方法 • 單獨設計一個獨立的事件聆聽者雖然可以得到”模組化“的好處,但是在取得視窗內 GUI 元件的方式卻稍嫌繁雜! • 設計事件聆聽者的方式還有兩種: • 一種是利用 inner class 的架構(這是最常見的方式) • 另一種是把原始事件物件以及事件聆聽者物件結合的架構
inner class • inner class 的成員類別(member class) • 使用成員類別的目的就是希望把之前的SalaryHandler 的類別宣告在 TestGUI2 內 • 為了清楚說明這個用法,我們把 TestGUI2修改成 TestGUI3,然後把 SalaryHandler宣告成 TestGUI3 的成員
範例 只呈現出重點程式碼 40 private class SalaryHandler implements ActionListener { 41 public void actionPerformed(ActionEvent e) { 42 int h = Integer.parseInt(input.getText()); 43 Worker w = new Worker(h); 44 output.setText("薪資為 " + w.computeSalary()); 45 } 46 } 主要的差異:使用 inner class,可以簡化取得 GUI 中其他元件的步驟。
第三種方式 • TestGUI 是 JFrame,同時也是ActionListener 05 public class TestGUI4 extends JFrame implements ActionListener { ….. 31 // register 32 button.addActionListener(this); 40 public void actionPerformed(ActionEvent e) { 41 int h = Integer.parseInt(input.getText()); 42 Worker w = new Worker(h); 43 output.setText("薪資為 " + w.computeSalary()); 44 }
第二種方式的變形 註冊 WindowListener 01 import javax.swing.*; 02 import java.awt.event.*; 03 04 public class TestFrame3 { 05 public static void main( String[] args ) { 06 JFrame f = new JFrame(); 07 f.setVisible(true); 08 09 f.addWindowListener( 10 new WindowAdapter() { // inner class and adapter class 11 public void windowClosing( WindowEvent e ) { 12 // 可加入檔案關閉、視窗位置等敘述 13 System.exit( 0 ); }} ); 14 15 } 16 } 使用匿名 inner class
練習題 • 請使用匿名的內隱類別的方式來改寫 TestGUI3 程式。 • 請修改 TestGUI 的畫面,使得使用者可以選擇 H/S (HourlyWorker/SalaryWorker) 並為該物件計算薪資?
練習題 • 繼續之前 Circle/Rectangle 的範例, • 請設計一套視窗系統,使得使用者可以從檔案 shape.txt 讀取任意一個 Circle 或者 Rectangle 物件,檔案格式如下: • C 4.2 • R 1.2 4.5 • C 3.1 • C 代表 cirlce,而 R 代表 rectangle;請依照之前的範例,將讀取的物件的面積、周長等資料列印出來 • 執行後的第一個畫面會出現第一筆,畫面底下有”下一筆”和”上一筆”的按鈕,讓使用者可以檢視所有資料 • 在第一筆的時候,能不能按”上一筆”? • 在最後一筆的時候,能不能按”下一筆”?
兩個以上的事件處理 • TestGUI 還有改進的地方: • 若需要再次計算薪資,之前的資料還在,能不能有個”清除”按鈕來清除資料? • 我們每一次輸入完資料後,一定要按”計算薪資”按鈕才會計算薪資,可不可以把它設計成輸入完資料後直接按”Enter”鍵即可計算? • 我們先以第二種方式來說明
兩個以上的事件處理 • 按鈕的部分: • 設計 compButton 和 clrButton 兩個 JButton 物件分別代表”計算薪資”和”清除”按鈕 • 必須將 clrButton 註冊 • 會發生在 compButton 和 clrButton 上的事件都是 ActionEvent,而處理該種事件的事件聆聽者物件的資料型態是 ActionListener,而ActionListener 的事件處理方法為actionPerformed()
範例 (JTextField) • 文字欄位(JTextField)部分 • 可以發生在 JTextField 的事件為何? • 在下兩頁表格(課本的第 14 章附錄)的第二欄我們知道 JTextField 的事件聆聽者物件的資料型態是ActionListener • 從第三個欄位,我們知道該事件介面的事件處理方法是 actionPerformed(),而從事件處理方法的參數,就可以知道該事件的資料型態是 ActionEvent • 從第四欄位知道,該事件常用的方法有 getSource()和 getActionCommand()
範例 • 三個原始事件物件都需要註冊 • 事件聆聽者物件的資料型態都是ActionListener,因此可以設計三個ActionListener 的子類別;分別是 • InputHandler • CompButtonHandler • 以及 ClrButtonHandler
InputHandler private class InputHandler implements ActionListener { public void actionPerformed(ActionEvent e) { int h = Integer.parseInt(input.getText()); Worker w = new Worker(h); output.setText("薪資為 " + w.computeSalary()); } }
CompButtonHandler private class CompButtonHandler implements ActionListener { public void actionPerformed(ActionEvent e) { int h = Integer.parseInt(input.getText()); Worker w = new Worker(h); output.setText("薪資為 " + w.computeSalary()); } }