オブジェクト指向とGUIプログラミング
オブジェクト指向のプログラミングの基礎の話も終わり、 UMLによるプログラム設計の記述方法や、最後にソフトウェアの作成の流れ などを説明できたので、テストまでの今日を含めあと3回。 オブジェクト指向で分かりやすくなったGUIプログラミングについて解説を行う。
オブジェクト指向がないGUIプログラミング
技術の比較として、オブジェクト指向を使わないGUIプログラミングの例として、 unix の GUI 環境でもある X11 における Xlib を使ったプログラミングを最初に紹介。
なお、以下のプログラム例では、概念を示すために初期化や関数の引数は省略されており、 実際にはもう少し記述量が必要。
void main() { Display* display = ディスプレィの初期化 ; Window w1 = ウィンドウの初期化 ; Window w2 = ウィンドウの初期化 ; while(1) { // イベントが発生するまで待つ XNextEvent( display , &event ) ; // 発生したイベントを分類するswitch文 switch( event.type ) { case Expose: // ウィンドウの露出イベント // 対象となるウィンドウを調べて各種処理 if ( event.xany.window == w1 ) { w1の描画処理 ; } else if ( event.xany.window == w2 ) { w2の描画処理 ; } break ; case ButtonPress : // マウスが押されたイベント if ( event.xany.window == w1 ) { マウス処理 ; } break ; //------------------------ // ここが延々と巨大化 //------------------------ } } }
この方式では、XNextEvent() の後の何をすべきかの イベントの分岐命令が巨大化するし、対応するウィンドウを 探す if 文なども煩雑。
これではプログラムが大変なので、Xt(Xlib toolkit)では、コールバック関数を 用いて分岐までをスムーズに記載できるようになる。 コールバック関数とは、そのイベントが発生した時の処理関数を、 関数ポインタで登録しておき、イベント時に呼び出してもらう関数。
void w1_expose_callback( ... ) { w1の描画処理 ; } void w2_expose_callback( ... ) { w2の描画処理 } void w1_button_press_callback( ... ) { マウス処理 ; } //-------------------- // ここが延々と巨大化 //-------------------- void main() { Xtの初期化 ; // ウィジェットは、ウィンドウ上の部品を管理するデータ構造 Widget w1 = ウィンドウを初期化 ; Widget w2 = ウィンドウを初期化 ; // Xtにコールバック関数を登録する // (引数に関数名だけが書いてあることに注目) XtAddCallback( w1 , ExposureMask , w1_expose_callback , ... ) ; XtAddCallback( w2 , ExposureMask , w2_expose_callback , ... ) ; XtAddCallback( w1 , XtNcallback , w1_button_press_callback , ... ) ; // Xtに処理を完全に任せる... XtAppMainLoop( ... ) ; }
Xt では、ウィンドウの管理を行う toolkit に、自分の作ったウィンドウで、 必要なイベントが発生した時に実行してほしい処理を、 関数ポインタで登録しておく方法をとる。 この後、XtAppMainLoop() を呼び出して、この関数の中から必要な ウィンドウのイベントが発生する度に、コールバック関数を呼び出してくれる。
JavaアプレットのGUIプログラミング例
基本的には、ウィンドウ処理を記載する場合には、ウィンドウ画面を管理するための Applet と呼ばれる基底クラスを用いる。 自分で作るウィンドウの処理を記載する場合には、Applet クラスから 自分の処理のクラスを派生させ、その中で 描画処理を行う paint() や、 マウス処理を行う mousePressed() 等を記述すればいい。 そうすると、 Applet クラスが、仮想関数のメカニズムで、ユーザのクラスの paint() や mousePressed() などを、必要なタイミングで呼び出してくれる。 自分にとって特に記述の必要がなければ、イベント関係のメソッドを記述しなければ良い。 そうすれば親クラスのメソッド等が呼び出され、必要な処理を行なってくれる。
public class MyApplet extends Applet { // ウィンドウの露出に合わせて呼び出される。 public void paint( Graphics g ) { 描画処理 ; } // マウスが押された時に呼び出される。 public void mousePressed( MouseEvent e ) { マウス処理 ; } // 他にも、init , start , stop , destroy , // paint , repaint , update 等のメソッドなどを // 必要に応じて定義すればいい */ }
画面にボタンや、別の部品が必要な時は、ActionListener というインタフェース(interface)を実装(implement)する。 インタフェースとは、純粋仮想基底クラスのような物で、 Javaには多重継承の概念が無いため、インタフェースを実装するという形で、 プログラムを記述する。 特に、ActionListener インタフェースは、ウィンドウのイベントを処理する仮想関数を 記述するために使われる。
public class MyApplet extends Applet implements ActionListener { // クラス変数(Applet内のボタン等を記述する) Button b1 = new Button( "ボタン1" ) ; Button b2 = new Button( "ボタン2" ) ; // Appletの初期化関数 public void init() { b1.ボタンの配置を設定() ; b2.ボタンの配置を設定() ; // ボタンが押された時に呼び出してほしい事を、 // ActionListenerに登録する。 b1.actionListener( this ) ; b2.actionListener( this ) ; } // ウィンドウが露出した時に呼び出される。 public void paint( Graphics g ) { 描画処理 ; } // ボタンが押されるなどのタイミングで呼び出される。 public void actionPerformed( ActionEvent e ) { if ( e.getSource() == b1 ) { ボタン1の処理 ; } else if ( e.getSource() == b2 ) { ボタン2の処理 ; } } }
これらの方式は、基本的な考え方であり、最近の Java では、 さらに進んだイベント処理の考え方が取り入れられていたりするので、 あくまで、このプログラムの例は参考です。