第14回 GUIの構成とイベント・ドリブン ~GUIを使ったプログラム(Ⅰ)~
学習目標 イベント・ドリブンプログラミングの利点を説明できる Swingを利用して簡単なGUIプログラムが書ける 簡単なカスタムウインドウを表示できる イベントハンドラの生成・登録ができる ModelとViewを分離する設計について議論できる
GUIアプリケーション 今回はいよいよGraphical User Interfaceに挑戦します 管理画面 購入画面
14.1 SwingAPIを使う 14.1.1 Swing概要 14.1.2 Swingを使ってみよう
14.1.1 Swing概要 JavaでGUIのプログラムを書くためのAPI ①ウインドウを出す ②カスタムウインドウを作る GUIに関するクラス・ライブラリ群は「Swing」と呼ばれている ①ウインドウを出す ②カスタムウインドウを作る
Swing概要 すべてがオブジェクト 「コンポーネント」という JFrame JLabel JTextField JButton
14.1.2 Swingを使ってみよう ①ウインドウを出す 例題14-1 (Example14_1.java) import javax.swing.*;//swingクラスライブラリの利用を宣言する import java.awt.*;//swingクラスライブラリの利用を宣言する //ウインドウを表示する public class Example14_1 { //プログラム・メイン //フレームを起動する public static void main(String args[]){ JFrame frame = new JFrame();//Swingで提供されるJFrameオブジェクト生成 frame.setTitle("初めてのウインドウ");//タイトル設定 frame.setSize(200,200);//大きさ設定 frame.setLocation(50,50);//位置設定 frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);//ウインドウが閉じたときに プログラムが終了するように設定 frame.setVisible(true);//表示する }
実行結果 次はこのウインドウにボタンを 乗せてみましょう!
②カスタムウインドウを作る ウインドウにボタンを乗せる JFrameを継承して、カスタムウインドウを作ります Swing プログラム 表示する() 再描画()など Swing プログラム
ButtonFrameのリスト 例題14-2 (ButtonFrame.java) import javax.swing.*;//swingクラスライブラリの利用を宣言する import java.awt.*;//swingクラスライブラリの利用を宣言する //JFrameクラスを継承したカスタムフレーム(ウインドウ)クラス //ボタンが一つ乗せられている public class ButtonFrame extends JFrame{ //コンストラクタ public ButtonFrame(){ getContentPane().setLayout(null);//ウインドウに載せられるすべてのオブジェクトの位置 を自分で設定できるようにする。 //ボタンを設定する JButton button = new JButton();//ボタンをインスタンス化 button.setText("初めてのボタン");//ボタンのラベル名設定 button.setSize(150,20);//ボタンの大きさ設定 button.setLocation(20,50);//ボタンの位置設定(ウインドウからの相対位置) //ボタンをウインドウに乗せる getContentPane().add(button); }
ButtonFrameを表示するMain 例題14-2 (Example14_2.java) //ウインドウにボタンを乗せる //カスタムフレーム(ウインドウ)の作成 public class Example14_2 { //プログラム・メイン //フレームを起動する public static void main(String args[]){ JFrame frame = new ButtonFrame();//カスタムフレームオブジェクト生成 frame.setTitle("初めてのウインドウ");//タイトル設定 frame.setSize(200,200);//大きさ設定 frame.setLocation(50,50);//位置設定 frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);//ウインドウが閉じたときに プログラムが終了するように設定 frame.setVisible(true);//表示する }
実行結果 ボタンが乗せられました。 しかし、押しても何も起こりません。 次は、ボタンが押されたとき、プログラムを 終了するようにしましょう!
14.2 イベント・ドリブンプログラミング 14.2.1 ボタンが押された時の処理を考える 14.2.2 Javaによるイベント・ドリブンプログラミング
14.2.1 ボタンが 押された時の処理を考える ①原始的な方法 ポーリング 一定期間ごとにハードウエアの状態を調べる
ハードウエアの状態を調べる 何処にいる? プログラム 動いてないよ! 押されてる? プログラム “a”が押されてます
ハードウエアを調べるプログラム 下記のメソッドを一定期間ごとに動かす //Javaではない仮想言語 public void ボタンが押されたかどうかを調べて、押されていたら終了する(){ マウスの位置を調べる(); if(マウスの位置がButtonの中にある){ マウスの状態を調べる(); if(マウスが押されている){ if(押されているのは、マウスの左ボタンである){ プログラムを終了する }
ポーリングの問題点 考えてみよう
ポーリングの問題点 効率が悪い(CPUに負担をかける) プログラミングが大変 動いていないかもしれないのに調べる必要がある マウスの正確な動きを捉えるには、相当細かい期間ごとに調べなければならない プログラミングが大変 ややこしいメソッドを用意する必要がある ボタンがたくさんあったら大変
②イベント・ドリブン プログラミング イベント・ドリブン ポーリングの問題点を解決するためのプログラミングスタイル 処理をイベント毎に分割して記述して、 必要に応じて呼びだされ, 処理される
イベント・ドリブンの考え方 ハードウエアを 常に見張っている 唯一のプログラム 押されたよ! プログラム プログラム 常に見張っている
イベント・ドリブンの用語 イベント・ディスパッチャー イベント イベント・ハンドラ ハードウエアを 常に見張っている 唯一のプログラム 押されたよ! プログラム
イベント・ドリブンプログラミング イベントドリブン型プログラムを作る時は、ただひたすら、イベントハンドラをどう書くかに集中すれば良い public void ボタンが押されたときの処理(){ System.exit(0); } これだけのメソッドを書けばよくなる
14.2.2 Javaによる イベント・ドリブンプログラミング ②イベントハンドラの作成 ③イベントハンドラの登録 Javaテクニック ④インナークラス ⑤テキストフィールドと連動するプログラム
①Javaによるイベント・ドリブン JavaのSwingフレームワークはこの機能を標準で持っている JavaではイベントハンドラのことをEventListenerと呼び、オブジェクトとして表現 Javaではイベントもオブジェクトとして扱う(EventObject)
Javaによるイベント・ドリブン(2) イベント・ディスパッチャー イベント イベント・ハンドラ Swingが提供 EventObject ハードウエアを 常に見張っている 唯一のプログラム EventListener 押されたよ プログラム
(ExitButtonListener.java) ②イベントハンドラの作成 例題14-3 (ExitButtonListener.java) import java.awt.event.*;//eventクラスライブラリの利用を宣言する //ボタンを押したときのイベントハンドラ クラス public class ExitButtonListener implements ActionListener{ //ボタンが押されたときのイベントハンドラ public void actionPerformed(ActionEvent e){ System.exit(0);//プログラムを終了する } ActionListenerはEventListenerの サブインターフェイス メソッド名はインターフェイスが 決めているので間違えないように ActionEventはEventObjectの サブクラスで、イベントを表すクラス
③イベントハンドラの登録 イベントハンドラを生成し、 addActionListener()メソッドを使って 登録する 例題14-3 //JFrameクラスを継承したカスタムフレーム(ウインドウ)クラス public class ButtonFrame extends JFrame{ //コンストラクタ public ButtonFrame(){ getContentPane().setLayout(null); //ボタンを設定する JButton button = new JButton(); button.setText("初めてのボタン"); button.setSize(150,20); button.setLocation(20,50); //イベントハンドラを設定する ExitButtonListener listener = new ExitButtonListener();//イベントハンドラをインスタンス化 button.addActionListener(listener);//イベントハンドラを受信者として登録する //ボタンをウインドウに乗せる getContentPane().add(button); } 例題14-3 (ButtonFrame.java) イベントハンドラを生成し、 addActionListener()メソッドを使って 登録する
実行してみましょう! ボタンを押すと、ウインドウが 閉じて終了するはずです
(Javaテクニック) ④インナークラス 例題14-4 (ButtonFrame.java) //JFrameクラスを継承したカスタムフレーム(ウインドウ)クラス public class ButtonFrame extends JFrame{ //コンストラクタ public ButtonFrame(){ //ボタンを設定する //途中割愛 //イベントハンドラを設定する ExitButtonListener listener = new ExitButtonListener();//イベントハンドラをインスタンス化 button.addActionListener(listener);//イベントハンドラを受信者として登録する //ボタンをウインドウに乗せる getContentPane().add(button); } //ボタンを押したときのイベントハンドラ インナークラス class ExitButtonListener implements ActionListener{ //ボタンが押されたときのイベントハンドラ public void actionPerformed(ActionEvent e){ System.exit(0);//プログラムを終了する 例題14-4 (ButtonFrame.java)
インナークラス クラスの中にクラスを書くことができる Javaの便利な仕組み インナークラスを使う利点は何でしょうか?
⑤テキストフィールド と連動するプログラム ボタンが押された時、テキストフィールドの内容を変更するプログラム ボタンが押された
テキストフィールド と連動するプログラムのリスト //JFrameクラスを継承したカスタムフレーム(ウインドウ)クラス public class ButtonFrame extends JFrame{ private JTextField textField = new JTextField();//テキストフィールド //コンストラクタ public ButtonFrame(){ //ボタンを設定する(割愛) //イベントハンドラを設定する(割愛) //ボタンをウインドウに乗せる(割愛) //テキストフィールドを設定する textField.setText("ボタンは押されていません"); textField.setBounds(20,100,150,20);//位置、大きさ設定(x,y,width,height) getContentPane().add(textField);//テキストフィールドをウインドウに載せる } //ボタンを押したときのイベントハンドラ インナークラス public class TextFieldChangeButtonListener implements ActionListener{ //ボタンが押されたときのイベントハンドラ public void actionPerformed(ActionEvent e){ textField.setText("ボタンが押されました"); テキストフィールドを 生成する テキストフィールドの 内容を変更する
インナークラスの利点 簡単に他のコンポーネントと連動できるようになる もちろん、イベントハンドラに参照を渡すようにして、プログラムを分割してもできる プログラムを分割する必要がある場合はそうする
14.3 GUI自動販売機の構成 14.3.1 GUI自動販売機概要 14.3.2 ModelとViewの分離
14.3.1 GUI自動販売機概要 自動販売機用コンポーネントを利用する 基本構造 管理者用ウインドウ(AdminFrameクラス) CUI版CUIAdminAppの機能を備えている ユーザ用ウインドウ(UserFrameクラス) CUI版CUIUserAppの機能を備えている
基本構造 ユーザウインドウ 管理者ウインドウ
14.3.2 ModelとViewの分離 ①CUI自動販売機の再利用 ②ModelとViewの分離 ③現状のModelの問題点 ④display()メソッドの廃止
①CUI自動販売機の再利用 CUIアプリケーションのオブジェクトは再利用できるものがある 再利用可能
GUI自動販売機の構成 GUIになっても構造は変わらない
②ModelとViewの分離 再利用できるものとできないものの違いを考える
ModelとView 再利用できるもの 再利用できないもの 自動販売機における実体や概念の構造に関わる部分→Model (アプリケーションロジックに関わる部分)
ModelとViewの分離 ModelとViewを分離しておくことによって、表示の変更が容易になる 分離していなかったらGUIは始めから作り直し Viewクラス群 表示役 Modelクラス群 実体や概念
③現状のModelの問題点 ItemTypeListクラスのdisplay()メソッドは、CUI専用のコードになっている 例題11-1 (ItemTypeList.java) #display() //商品種類を表示する public void display(){ for(int i=0;i<size;i++){ //商品種類を表示 System.out.println(itemTypeArray[i].getId()+":"+itemTypeArray[i].getName()+":" +itemTypeArray[i].getPrice()+"円"); } ※ItemStockクラスも同様
display()メソッド CUIとGUIでは異なる表示にしたい 現状のdisplay()メソッドではGUIに対応できない Modelは構造だけに責任をもつ 1001:cola:120円 1002:soda:120円 CUI商品種類表示 GUI商品種類表示
④display()メソッドの廃止 表示の責任はViewが持つべきだから、display()メソッドは廃止する 変わりにCUI、GUIがそれぞれの表示をできるようにメソッドを追加する int size() 要素数を取得する ItemType get(int index) index番目の商品種類を取得する
display()はViewに任せる size()メソッドと get(index)メソッド があれば、表示可能 例題11-2 (CUIAdminApp.java) # showItemTypeList() //商品種類リストを閲覧する public void showItemTypeList(){ itemTypeList.display(); } //商品種類リストを閲覧する public void showItemTypeList(){ int len = itemTypeList.size(); for(int i=0;i<len;i++){ ItemType itemType = itemTypeList.get(i); System.out.println(itemType.getId()+":" +itemType.getName()+":" +itemType.getPrice()+"円"); } size()メソッドと get(index)メソッド があれば、表示可能
Model-View分離 ModelとViewで責任を分離したプログラムの利点 表示の変更が容易に行える 様々な表示(CUI,GUI)に対応したModelプログラムが書ける 重複コードがなくなる
14.3.3 GUI自動販売機プログラミング ①自動販売機用コンポーネント ②GUI自動販売機の起動 ③UserFrame解説 ④100円投入ボタンを追加する
① 自動販売機用コンポーネント(1) 代表的なクラス BackgroundPanelクラス 背景用のコンポーネントです。 この上に他の自販機コンポーネントを乗せる ことができます。 ItemImagePanelクラス 商品種類を表示するための コンポーネントです。
① 自動販売機用コンポーネント(2) BuyButtonクラス 購入ボタンです ProductOutletクラス 商品取り出し口です CoinDepositクラス 投入金額を表示します
②GUI自動販売機の起動 itemTypeListと Accountを生成して 2つのウインドウに渡している 例題14-6 (Example14_6.java) //GUI自動販売機を起動するためのメイン・クラス public class Example14_6 { /** * プログラム・メイン * 操作するための2つのウインドウ(管理ウインドウ,購入ウインドウ) * を生成、表示する */ public static void main(String args[]){ ItemTypeList itemTypeList = new ItemTypeList(); //商品種類リストを生成 Account account = new Account(); //投入金勘定を生成 //管理ウインドウを生成して表示する AdminFrame adminFrame = new AdminFrame(itemTypeList,account);//生成 adminFrame.setVisible(true);//表示 //購入ウインドウを生成して表示する UserFrame userFrame = new UserFrame(itemTypeList,account);//生成 userFrame.setVisible(true);//表示 } itemTypeListと Accountを生成して 2つのウインドウに渡している
③UserFrame解説 コンストラクタ 更新メソッド(3種類) イベント・ハンドラ 各種コンポーネントを生成して、ウインドウに貼り付ける 例題14-6 (UserFeame.java) コンストラクタ 各種コンポーネントを生成して、ウインドウに貼り付ける 更新メソッド(3種類) Modelの状態を反映して、表示を更新するために使う イベント・ハンドラ ボタンが押された時のイベントハンドラを記述
④100円投入ボタンを追加する 100円投入できるようにする ボタンが押されたら
100円投入ボタンを追加する ボタン生成 ボタン設定 イベント・ハンドラ 例題14-7 (UserFeame.java) //ユーザフレーム(商品購入メインウインドウ)クラス //説明に不要な部分を割愛してある public class UserFrame extends JFrame{ public JButton button100 = new JButton();//100円ボタン //コンストラクタ public UserFrame(ItemTypeList newItemTypeList,Account newAccount){ //100円投入ボタン設定 button100.setText("100円"); button100.setBounds(new Rectangle(385, 240, 79, 27)); button100.addActionListener(new Button100_ActionListener()); background.add(button100, null); } //100円投入ボタン・イベントハンドラ class Button100_ActionListener implements java.awt.event.ActionListener{ public void actionPerformed(ActionEvent e) { account.insert(new Money(100));//100円投入する stateUpdate();//状態を更新する ボタン生成 ボタン設定 イベント・ハンドラ
イベント・ハンドラ解説 投入金勘定への金の追加 stateUpdate()を呼ぶ CUIと同様 //100円投入ボタン・イベントハンドラ class Button100_ActionListener implements java.awt.event.ActionListener{ public void actionPerformed(ActionEvent e) { account.insert(new Money(100));//100円投入する stateUpdate();//状態を更新する }
課題ヒント 課題14-3:購入ボタンのイベント・ハンドラを実装せよ 購入ボタン用のイベントハンドラが用意されている //ユーザフレーム(商品購入メインウインドウ)クラス //購入ボタンのイベントハンドラ以外は割愛 public class ShoppingFrame extends JFrame{ //購入ボタン・イベントハンドラ //押されたボタンと、そのボタンが対象とする商品の種類が引数 public void buyButton_pressed(BuyButton buyButton,ItemType itemType){ } 購入ボタン用のイベントハンドラが用意されている
購入イベント・ハンドラの実装 取り出し口に出すプログラム itemは取り出した商品 構造が変化したら、状態を更新する プログラムの目的(コメント)は、CUIプログラムと同様になる コメントに合わせてプログラムを書く //購入ボタン・イベントハンドラ //押されたボタンと、そのボタンが対象とする商品の種類が引数 public void buyButton_pressed(BuyButton buyButton,ItemType itemType){ //買えるかどうか確認する //在庫の確認をする //投入されている金が価格より高いか確認をする //購入する //保管庫から商品を取り出す productOutlet.addItem(item); //取り出し口に出す //おつりをだす System.out.println("おつりは~~円です"); //おつりの表示 //投入金勘定をリセットする stateUpdate();//状態を更新する } 取り出し口に出すプログラム itemは取り出した商品 構造が変化したら、状態を更新する