今週は画像ファイルをプレゼンテーションする「ミニスライドショー」のアプレットを作成します。スライドショーといっても、今まで作成した画像を表示するアプレットと機能の面では大きな違いはありません。画像を表示する仕組みや、複数の画像を切り替えるやり方は、今まで学習してきた内容で十分対応できます。しかし今回は「Canvasクラス」を用いることで、今後より複雑な機能を持ったアプレットを作成する際に必要な「複数のクラスを自作して連動させる」というやり方を新たに学びます。
11.1 ミニスライドショーのアプレット 11.2 Canvasクラスの利用法 11.3 ミニスライドショーに必要な機能(アプレット本体編) 11.4 ミニスライドショーに必要な機能(キャンバス編) 11.5 スライドショーをつくってみよう 11.6 おまけ。少し複雑なミニスライドショーのソースコード
まずはじめに、今週作成するミニスライドショーのアプレットの例をみてみましょう。たとえば次のアプレットはチョイスボタンによる画像の切り替えと、ボタンによる画像の表示の機能を持ったミニスライドショーの一例です。
このアプレットはチョイスボタンによって2種類の画像群を切り替え、ボタンによって表示を実行します。それぞれの画像群は3種類の画像から構成され、ボタンを押すごとに順番に3種類の画像が表示されてゆきます。この仕組みを応用すれば、たとえばストーリーなどによって順序立てたいくつかの画像を順に表示する、ちょっとしたスライドショーが作成できるでしょう。なおこのアプレットのソースコードは、このテキストの末尾で公開します。
今回はこのアプレットと同じような基本設計を持つ、もう少し簡単なものを作成します。二つのボタンによって表示/非表示を切り替える次のアプレットです。
このアプレットはスライドショーと呼ぶには機能がシンプルすぎるかもしれません。しかしこのアプレットを作成することができれば、先に示したミニスライドショーの設計も自ずと理解できるようになるはずです。
今回はスライドショーを構成するGUIパーツとして新しくCanvasを学びます。これは文字や画像などを自由に描くことのできる部品です。このCanvasをAppletの本体の上に貼り付け、画像の表示を行う部品として活用します。
CanvasはButtonなどと異なり、Canvasクラスの子クラスの定義をあらかじめ作成したうえで、Appletの定義の中でそのオブジェクトを用います。この点を中心に、単に画像を表示するだけの機能を持ったCanvasを1つ貼り付けた次のアプレットを例に取り、Canvasの利用法を説明します。
まずはCanvasの子クラスの機能を定義しましょう。このアプレットの場合、「1枚の画像を表示する」というのが必要な機能です。これを今までアプレットの定義を行っていた時と同じ要領で次のように記述します。
/** * SimpleCanvasの定義 **/ class SimpleCanvas extends Canvas{ Image img1; // SimpleCanvas上に描画する画像を保存する変数 // initメソッド:SimpleCanvasの初期化を行う public void init(){ // 画像を読み込む img1 = getImage( getDocumentBase(), "slide0.gif" ); // キャンバスの表示領域を確保する setSize(210,210); } // paintメソッド:SimpleCanvasの描画を行う public void paint(Graphics g){ g.drawImage( img1, 10, 10, this ); } } |
まずはじめにアプレット本体から独立したCanvasの子クラスとして、クラス名を与える必要があります。従って「class クラス名 extends Canvas」という記述から定義を開始します(ただし先頭にpublicと記述する必要はありません)。今回は「SimpleCanvas」という名前を付けました。
次に{・・・}の中に必要な機能をメソッドとして定義してゆきます。このSimpleCanvasに必要な機能は「1枚の画像を表示する」でした。ここではアプレットを定義する際と同じ要領でinitメソッドとpaintメソッドを作成します。
まずinitメソッドでは、「画像を読み込む」、「キャンバスの表示領域を確保する」という二つの仕事をおこないます。「画像を読み込む」の方は、画像を表示する機能を持ったアプレットを作成する際にこれまでもおこなってきた、おなじみの記述ですね。つぎの「キャンバスの表示領域を確保する」は、アプレット上に貼り付けられたキャンバスが、どれくらいの大きさの領域を持つべきかを決定します。ここでは画像の大きさ(200×200ピクセル)に合わせて210×210ピクセルの大きさの領域を、キャンバスに対して割り当てることにしました。ここで用いられるsetSizeメソッドはCanvasクラスが用意しているメソッドで、引数には縦幅と横幅を表す整数型のデータを順番に記述します。
paintメソッドの方は、Appletクラスのpaintメソッド(すなわち、私たちがいつも書き慣れているpaintメソッド)と同様に、Graphics型を仮引数に取るようにします。その上で、Graphicsのオブジェクト(ここではgという変数名で表される)に対してdrawImageを実行します。これもまた、画像を表示するアプレットを作成する際にいつもおなじみのやり方ですね。
ちなみに、initメソッドは、CanvasクラスおよびCanvasクラスの親クラスたち(Componentクラス→直接の親クラス、Objectクラス→Componentクラスの親クラス)には定義されていないメソッドです。したがって、私たちが勝手に「自作」するメソッドです。それに対してpaintメソッドは、Canvasクラスにあらかじめ定義されているメソッドで、私たちは今回、それを自分たちの手で書き換えることになります(つまり、Appletクラスのpaintメソッドと同じことをしているわけです)。そのことは、Appletクラスのrepaintメソッドとの連携をはかる際に、大きな意味を持ちます(後ほど説明します)。
今度はこのSimpleCanvasを部品としてアプレット本体に貼り付けます。まずは次のように、先ほど定義したSimpleCanvasの記述を、アプレット本体を定義する記述の中に埋め込みます。
/** * 自作Canvasを用いたスライドショーの簡易版のさらに簡易版 * 00.1.4(第11週) **/ import java.awt.*; import java.applet.Applet; public class SlideShow0 extends Applet { /** * SimpleCanvas(自作のCanvas)の定義 **/ class SimpleCanvas extends Canvas{ Image img1; // SimpleCanvas上に描画する画像を保存する変数 // initメソッド:SimpleCanvasの初期化を行う public void init(){ // 画像を読み込む img1 = getImage( getDocumentBase(), "slide0.gif" ); // キャンバスの表示領域を確保する setSize(210,210); } // paintメソッド:SimpleCanvasの描画を行う public void paint(Graphics g){ g.drawImage( img1, 10, 10, this ); } }// SimpleCanvasの定義はここまで } |
アプレット本体のクラス名はSlideShow0にしました。したがって、この記述を保存する際のファイル名は「SlideShow0.java」です。いつもアプレットを作成する際にやるように、import文に続き「public class 〜」でアプレット本体のクラス名を(そしてextends Appletを)記述し、{ }の中に先ほどのSimpleCanvasを定義した記述(赤字の部分)を書き込んでおきます。
次に、SimpleCanvasの定義に引き続き、アプレット本体(SlideShow0)のプログラムを記述してゆきます。この単純に画像を描画したSimpleCanvasをディスプレイするアプレットの場合、必要な機能は「SimpleCanvasのオブジェクトを生成して貼り付ける」ことと「SimpleCanvasを描画すること」の二点に限られます。通常アプレットのプログラムにおいては、GUIパーツの生成・貼り付けはinitメソッドで行い、GUIパーツを含めたアプレット本体の描画はpaintメソッドで行うことになっていました。今回も、基本的にはその考え方を踏まえながらプログラムを作成して行きます。
/** * 自作Canvasを用いたスライドショーの簡易版のさらに簡易版 * 00.1.4(第11週) **/ import java.awt.*; import java.applet.Applet; public class SlideShow0 extends Applet { /** * SimpleCanvas(自作のCanvas)の定義 **/ class SimpleCanvas extends Canvas{ Image img1; // SimpleCanvas上に描画する画像を保存する変数 // initメソッド:SimpleCanvasの初期化を行う public void init(){ // 画像を読み込む img1 = getImage( getDocumentBase(), "slide0.gif" ); // キャンバスの表示領域を確保する setSize(210,210); } // paintメソッド:SimpleCanvasの描画を行う public void paint(Graphics g){ g.drawImage( img1, 10, 10, this ); } }// SimpleCanvasの定義はここまで SimpleCanvas canv; // 自作のCanvas // initメソッド:アプレットの初期化を行う public void init(){ // レイアウトの方法を決定する setLayout( new BorderLayout() ); // SimpleCanvas(自作のCanvas)を用意し、 // 初期化した後、アプレット本体に貼り付ける canv = new SimpleCanvas(); canv.init(); // SimpleCanvasのinitメソッドを実行して、 // キャンバスの初期化を行う add( "Center", canv ); } } |
アプレット本体のプログラムの構造に関しては、今まで作成してきたアプレットと同様に解釈すれば理解できるはずです。その際に、先ほどあらかじめ用意しておいたSimpleCanvasをどのように利用しているか(青字の部分)に注目して下さい。
まずはじめに、あらかじめSimpleCanvasのオブジェクト名(canv)を宣言しています。このオブジェクトは、アプレット本体からSimpleCanvasに対して操作を行うために用いるものです。
initメソッドの中では3つの作業を行っています。まずSimpleCanvasのオブジェクトを生成し、つぎにSimpleCanvasのinitメソッドを実行してキャンバス自体の初期化を行い、最後にこのオブジェクトをアプレットの中央に貼り付けます。「 canv.init(); 」が実行されると、直前にあるSimpleCanvasの定義(赤字の部分)の中に定義されている、SimpleCanvasのinitメソッドの中に書かれている命令が実行されるのです。
つぎに、この講座で作成したほとんどのアプレットの例に従えば、paintメソッドの定義を行うことになりますが、ここには全く記述されていません。このことは、今回のアプレットではpaintメソッドを特に書き換える必要がなく、Appletクラスから継承したpaintメソッドの内容をそのまま実行すればいい、ということを意味しています。その理由は、今回の場合はアプレット本体の上に直接画像・図形・文字などを表示するといった操作を行わないからです。このアプレットでは、画像の表示はアプレット本体の上にではなく、アプレット本体の上に貼り付けてあるSimpleCanvasの上に行います。
ところで、SimpleCanvasの上に行う画像の描画の具体的な命令は、SimpleCanvasクラスのpaintメソッドの中に記述しました。しかし、アプレット本体のプログラムには、SimpleCanvasのpaintメソッドを呼び出す記述は特に明記されていません。それでは、このSimpleCanvasのpaintメソッドは、いったい「いつ」「誰が」呼び出して実行するのでしょうか。
実は、SimpleCanvasのpaintメソッドは、アプレット本体が再描画される度に(つまりアプレット本体のrepaintメソッドが呼び出されるたびに)自動的に呼び出されます。アプレット本体に定義されているrepaintメソッドは、アプレット本体の上に乗っているコンポーネント(CanvasやButtonなど、GUIオブジェクトの総称)に対しても自動的に再描画を要求するように定義されているのです。つまり、特にSimpleCanvasのpaintメソッドの呼び出しを明記しなくとも、「アプレット本体のrepaint→SimpleCanvasのrepaint(親クラスのCanvasから継承)→SimpleCanvasのpaint」という経過をたどり、結果的にSimpleCanvas上の画像の描画が行われることになるのです(先ほどCanvasクラスにはあらかじめpaintメソッドが定義されていることを説明しましたが、そのことによってAppletとCanvas間の「自動的な連携」が可能になっています)。
以上の要領で、自分で作成したCanvasの子クラスの定義をアプレット本体の中で利用します。頭が混乱しそうになっている人は、アプレットにおける命令の実行順序の大原則をもう一度思い出して下さい。アプレットにおいては、最初に(アプレット本体の)initメソッドが実行されます。ブラウザによってアプレットのプログラムが読み込まれたら、真っ先にアプレット自身のinitメソッドが探し出され実行されます。今回の場合SimpleCanvasクラスの定義の中にも同じくinitメソッドがありますが、これはあくまでアプレット本体によって利用されることによりはじめて実行されるもです。つまりアプレット本体のinitメソッドの定義の中で「 canv.init(); 」と記述されていることではじめて実行されるのです。アプレット本体のinitが実行が済んだら、次には必ずアプレット本体にrepaintがかかり、結果的にアプレット本体の描画が行われます。そして、この時同時に、アプレット本体の上にあるSimpleCanvasにもrepaintがかかり、SimpleCanvas上に画像の描画がおこなわるのです。
先ほど例に取り上げた、Canvasを利用して画像を表示するアプレットSlideShow0を実際につくってみなさい。その際、Canvasの子クラスにSimpleCanvas以外の名前をつけてもうまくゆくか試してみましょう。
今度は先ほど11.1で2つめに取り上げた、表示/非表示を二つのボタンで切り替えるスライドショーをつくってみましょう。今回はAppletクラスの子クラス(つまりアプレット本体)を定義することだけでなく、Canvasクラスの子クラスを定義する必要も出てきました。また、先ほど行ったようにこれら二つのクラスのメソッドの間にうまく関連を持たせ、適切な実行順序を実現しなければなりません。このようにある程度の大きさのプログラムを作成する場合、プログラムを書き出す前に、あらかじめ必要な機能に関する目処をつけておく必要があります。そうすることで、プログラムを作成する際の混乱を防ぐことができ、より適切なプログラムを作成することができるのです。従ってここでもまずは、作成するスライドショーに必要な機能に関する整理を行っておきます。
まず、イメージ図や実際に動作させてみた結果と、これまでに作成してきたアプレットのプログラムの定石をもとに、このアプレット本体に必要となりそうな機能を大まかに類推してみましょう。もっとも大まかなレベルでは、次の3つの機能が必要となりそうなことが分かると思います。
これらがそれぞれinitメソッド、paintメソッド、そしてactionPerformedメソッドに対応する機能であることは、だいたい察しがつくと思います。それでは次に、それぞれのメソッドの中ではどのような処理を行う必要があるのかを大まかにリストアップしてみましょう。
だいたいこのようなものでしょうか。これはアプレット本体のプログラムのラフスケッチにあたるものです。このラフスケッチをもとに、もう少し詳細な部分をそれぞれのメソッドについて考えてみると、次のようになります。
initメソッド・・・キャンバスと2つのボタンはBorderLayoutにてCenterとSouthに配置されている。その際、2つのボタンは一つのパネルに貼り付けられて、一つのパーツのようにまとめて扱われている。また、キャンバスの初期化が必要ならば、キャンバスを貼り付ける事前に行っておく。 |
paintメソッド・・・今回は記述しない |
actionPerformedメソッド・・・キャンバスに対して表示/非表示どちらのボタンが押されたのかを何らかの形で知らせる。また、描画の具体的な処理(「表示」ならば画像の描画をし、「非表示」ならば何も描画しない、という処理)をキャンバスに任せることができるならば、アプレットはどちらのボタンが押されたかをキャンバスに知らせた後で、キャンバスの再描画(repaint())を指示すればよい。 |
アプレット本体については、一旦この程度で整理を終えておきます。次はアプレットが利用するキャンバスの機能の整理を行います。
キャンバスに必要そうな機能を、先ほどと同じ要領で整理します。まず、確実に次の二つの機能が必要になるでしょう。
ただしこれだけでは、アプレットのどちらのボタンが押されたのかをキャンバスが知ることができません。したがって、
が必要になってきます。ここでキャンバスの「状態」としたのは、paintメソッドの処理を考えるときに、キャンバスの二つの「状態」(表示/非表示)に応じて適切な処理を実行する、という考え方をすれば便利だからです。ここで、この三つ目の機能(メソッド)にsetStateという名前を付けておきます。これらの結果をまとめ、多少の補足を加えると以下のようになります。
initメソッド・・・描画する画像を読み込み、キャンバスの初期状態を「表示」に設定する |
paintメソッド・・・キャンバスの状態が「表示」の時のみ、画像を描画する |
setStateメソッド・・・アプレット本体からActionEventの結果(つまり表示/非表示どちらのボタンが押されたか)を受け取り、キャンバスの状態として設定する |
setStateメソッドがアプレット本体からActionEventの結果を受け取るためにはどうすればよいのでしょうか。結果を引数として渡してもらえばうまくゆきます。setStateメソッドを定義する際に、たとえば整数型の仮引数を一つだけ定義しておきます。そして、0が渡されたらアプレットの状態を「表示」に、1ならば「非表示」に、キャンバスの状態を設定するように処理を記述しておきます。そしてこのsetStateメソッドが、アプレット本体のactionPerformedメソッドの中で実行されるようにしておきます(11.3のまとめの中にあるactionPerformedメソッドの部分をもう一度読み返して下さい。「キャンバスに対して表示/非表示どちらのボタンが押されたのかを何らかの形で知らせる」と書かれています)。つまり、ボタンが押されるとアプレット本体のactionPerformedメソッドが呼び出されます。そのときにONのボタンが押されたならばsetStateメソッドを呼び出し引数0を、OFFのボタンが押されたならば1を与えるように処理を記述すれば、キャンバスへのActionEventの結果の受け渡しはうまくゆくのです。
それでは、ここまでの内容を手がかりに、ミニスライドショーのプログラムの外枠(クラス名、メソッド名及び仮引数の定義、複数のメソッドで共通して扱う変数の宣言、それぞれについてのコメントまで、メソッドの中の処理の記述は含まない)を作成しなさい。また、それぞれのメソッドの内部で必要となる具体的な処理とその順序(制御構造)を整理しなさい。
以下は先ほどの練習問題11-2の解答に、特に重要な部分の制御構造を加えたものです。キャンバスの定義は赤字で示しておきました。
/** * 自作Canvasを用いたスライドショーの簡易版 * 00.1.2(第11週) **/ import java.awt.event.*; import java.awt.*; import java.applet.Applet; public class SlideShow1 extends Applet implements ActionListener{ /** * SimpleCanvas(自作のCanvas)の定義 **/ class SimpleCanvas extends Canvas{ Image img1; // SimpleCanvas上に描画する画像を保存する変数 boolean flag; // SimpleCanvasの状態を記憶する変数 // initメソッド:SimpleCanvasの初期化を行う public void init(){ } // setStateメソッド:SimpleCanvasの状態を設定する public void setState(int i){ // アプレット本体から受け取った値に応じて // SimpleCanvasの状態を設定する if( i == 0 ){ // 値が0ならば、表示の状態 } else if( i == 1 ){ // 値が1ならば、非表示の状態 } } // paintメソッド:SimpleCanvasの描画を行う public void paint(Graphics g){ // 表示の状態の時のみ、SimpleCanvas上に画像を表示する if ( flag ){ } } }// SimpleCanvasの定義はここまで SimpleCanvas canv; // 自作のCanvas // initメソッド:アプレットの初期化を行う public void init(){ } // actionPerformedメソッド:ボタンが押されたときに発生するイベントを制御する public void actionPerformed( ActionEvent e ){ // コマンド名を手がかりに押されたボタンを判別 // 押されたボタンに応じてSimpleCanvasの状態を設定する if( command.equals( "on" ) ){ } else if( command.equals( "off" ) ){ } } } |
ここから先は、各メソッドの内部について必要となる処理と順序をリストアップし、あらかじめコメントとして書き加えてゆきます。そうすることで、いきなりプログラムを書き始めるよりも、確実に正確なプログラムを作成することができます。すべての必要な処理をコメントとして表現できたならば、最後にプログラムのソースコードを書き、一応完成に至ります。コンパイル時にエラーが発見されたり、できたプログラムの動作に不適切な点がある場合には、もう一度コメントの部分から見直して、作り直し、再度コンパイルにかけます。
というわけで、この段階でミニスライドショーを完成する目処のある人は、早速先に進んで完成させて下さい。もう少し詳しいヒントがほしい人は、メソッド内の処理についてのコメントを記述したもう少し詳しいプログラムの「外枠」が、次に用意されています。それを参考に、処理を考えて下さい。
>> もう少し詳しい「外枠」
先に挙げたヒント(「外枠」)を参考にミニスライドショーを完成しなさい。また、余裕のある人は、11.1で一番最初に示したもう少し複雑な機能を持ったスライドショーの作成にチャレンジしてみましょう。
最初に11.1で示した、少し複雑な方のミニスライドショーのソースコードを公開しておきます。原理は先ほど作成したものと全く同じですが、GUIパーツが多い(つまり扱うイベントが多い)分だけ、アプレット本体とキャンバスの連携が複雑になっています。その点に注意しながらソースコードをみて下さい。
>> ソースコード
>> 目次ページへ