第20章 Windowsプログラミング(Java)

20.1 Window とアプレット
20.1.1 アプリケーション( Window の生成・消滅とイベント処理)
(プログラム例 20.1 ) 2 つの整数の和( Window の生成)
(プログラム例 20.2 ) 2 つの整数の和( Window の生成と消滅)
20.1.2 アプレット
(プログラム例 20.3 ) アプレット
20.2 グラフィックスと AWT
20.2.1 グラフィックス
(プログラム例 20.4 ) 2 つの整数の和(グラフィックスの利用)
(プログラム例 20.5 ) マウスによる描画
(プログラム例 20.14 ) キーイベントとゲーム
20.2.2 Java AWT
(プログラム例 20.13 ) 2 つの整数の和( AWT の利用)
20.3 アニメーション
20.3.1 アニメーションの開始と停止
(プログラム例 20.15 ) アニメーションの開始と停止
20.3.2 アニメーション作成方法
(プログラム例 20.16 ) ボールの運動(描画)
(プログラム例 20.17 ) ボールの運動(外部画像)
(プログラム例 20.18 ) 花火
20.3.3 バッファリング
(プログラム例 20.19 ) バッファリング
20.4 ネットワーク
(プログラム例 20.20 ) URL へリンク
(プログラム例 20.21 ) URL データの読み込み
(プログラム例 20.22 ) URL(サーバ)との会話
20.5 その他
(プログラム例 20.6 ) 8 / 15 パズル
(プログラム例 20.7 ) 剛体振り子の運動
(プログラム例 20.8 ) TSP を自分で解く
(プログラム例 20.9 ) グラフの表示
(プログラム例 20.10 ) 2次方程式の根
(プログラム例 20.11 ) GAのステップ実行
(プログラム例 20.12 ) 関数の最大値
  この章においては,Java による Windows プログラミングについて概説します.非常に基本的なことに対してだけの説明ですので,詳細については,他の書籍及び付録の Java のクラスとメソッド等を参照してください.

20.1 Window とアプレット

  今まで示したほとんどの例は,プログラム例 3.1 のように,コマンドプロンプト上で起動し,キーボードからデータを入力し,かつ,結果をコマンドプロンプト上に表示するためのものでした.プログラム例 7.18 のように,main 関数の引数としてデータを与える方法もありますが,コマンドプロンプト上で実行することには変わりありません.

  プログラムを使いやすくするためには,ボタン,チェックボックスなどのグラフィカルなユーザインタフェース( GUI )を利用してデータを入力し,その結果も GUI 上に表示する,場合によっては,グラフなど,より視覚的に分かりやすい形で結果を表示することが望まれます.残念ながら,C/C++ の仕様にはそのような機能が含まれていません.そこで,この章においては,Java による GUI を利用したプログラムについて説明します.

  Java において GUI を利用する方法として,2 つの方法が考えられます.一つは,通常のアプリケーションプログラムとして作成し,そのプログラム内で Window を生成し,GUI を利用する方法です.従って,プログラムの起動自体はコマンドプロンプトから行いますが,入力や結果の出力は,Window 上で行います(結果が,数値や文字である場合は,コマンドプロンプト上に出力することも可能).あと一つは,Web ページ上に Java で作成したプログラム(アプレットと呼びます)を埋め込む方法です.この場合は,Internet Explorer などのブラウザによって起動することになります.以下,2 つの方法について説明していきます.

20.1.1 アプリケーション( Window の生成・消滅とイベント処理)

(プログラム例 20.1 ) 2 つの整数の和( Window の生成)

  ここで示すのは,Frame クラスを使用して Window を生成し,2 つの整数の和を計算した例です.ただし,Java で利用できる GUI について全く説明してありませんので,プログラム例 7.18 と同様,2 つの整数を main 関数の引数として受け取り,結果をコマンドプロンプトに出力しています.そのため,あまり意味のあるプログラムになっていません.なお,AWT については,次の節( 20.2 節)で説明します.

  このプログラムをコンパイルし,コマンドプロンプトから,例えば,
	java Test 10 20
		
のように入力すると,コマンドプロンプトに,
	和は 30
		
のような結果が表示されます.
/****************************/
/* Window の生成            */
/*      coded by Y.Suganuma */
/****************************/
import java.awt.*;   // Frame も AWT の一種であるため必要

public class Test {
	public static void main (String[] args)
	{
		int a = Integer.parseInt(args[0]);
		int b = Integer.parseInt(args[1]);
		Win win = new Win("整数の和", a, b);
	}
}

/*******************/
/* クラスWinの定義 */
/*******************/
class Win extends Frame {

	/******************/
	/* コンストラクタ */
	/******************/
	Win (String name, int a, int b)
	{
					// Frameクラスのコンストラクタ(Windowのタイトルを引き渡す)
		super(name);
					// Windowの大きさ
		setSize(300, 200);
					// ウィンドウを表示
		setVisible(true);
					// 出力
		System.out.println("和は = " + (a + b));
	}
}
		
  上に示したプログラムには,さらに問題があります.Window の右上にある「×」ボタンをクリックしても Window を閉じることができません.なぜなら,「×」ボタンをクリックしたときの処理が全く記述されていないからです.

  マウスがクリックされた,ボタンが押された,Window が開始された,等のことをイベント(事象)と言います.このようなイベントが発生したとき行う処理をイベント処理といいます.まず,Java において,イベント処理がどのようにして行われているかについて説明ます.

  イベントは,イベントの種類毎に,クラスに分類されています. イベントが発生すると,イベントソースは対応するイベントを記述するイベントオブジェクトを生成します.これを,イベントリスナに送出して対応する処理を行うわけですが,そのためには,イベントリスナがそのイベントを「聞く」準備ができていなくてはなりません.その準備を行うのがイベント登録メソッド(イベント削除メソッドもあります)です.

  イベントの送出は,リスナのインタフェース(または,イベントアダプタ)に定義されているイベント応答メソッド(ハンドラメソッド)の内1つを呼び出し,引数としてイベントソースが生成したイベントオブジェクトを渡すといった方法で行われます.

(プログラム例 20.2 ) 2 つの整数の和( Window の生成と消滅)

  イベントリスナはインタフェースとして提供されています.これは,各イベントの処理がアプリケーションによって異なる場合が多いからです.しかし,インタフェースを実装する際には,インタフェースに定義されているすべてのメソッド(ハンドラメソッド)を実装しない限り,抽象クラスとなり,インスタンスを生成できなくなります.実際にすべてのメソッドを必要とする場合は問題ありませんが,以下の例に示すように,一部のメソッドだけを使用したい場合においても,必要としないメソッドまですべて定義しなければならず,余分な作業が必要になります.
/****************************/
/* Window の生成と消滅      */
/*      coded by Y.Suganuma */
/****************************/
import java.awt.*;   // Frame も AWT の一種であるため必要
import java.awt.event.*;   // イベント処理を行う場合に必要

public class Test {
	public static void main (String[] args)
	{
		int a = Integer.parseInt(args[0]);
		int b = Integer.parseInt(args[1]);
		Win win = new Win("整数の和", a, b);
	}
}

/*******************/
/* クラスWinの定義 */
/*******************/
class Win extends Frame implements WindowListener {

	/******************/
	/* コンストラクタ */
	/******************/
	Win (String name, int a, int b)
	{
					// Frameクラスのコンストラクタ(Windowのタイトルを引き渡す)
		super(name);
					// Windowの大きさ
		setSize(300, 200);
					// ウィンドウを表示
		setVisible(true);
					// イベントの登録
		addWindowListener(this);
					// 出力
		System.out.println("和は = " + (a + b));
	}

	/************/
	/* 終了処理 */
	/************/
	public void windowClosing(WindowEvent e) {
		System.exit(0);
	}

	/********************************/
	/* イベントリスナの他のメソッド */
	/********************************/
	public void windowActivated(WindowEvent e) {}
	public void windowDeactivated(WindowEvent e) {}
	public void windowOpened(WindowEvent e) {}
	public void windowClosed(WindowEvent e) {}
	public void windowIconified(WindowEvent e) {}
	public void windowDeiconified(WindowEvent e) {}
}
		
  イベントアダプタクラスを利用すれば,この煩わしさを避けることが可能です.イベントアダプタは,すべてのメソッドに対する標準的処理を実装しているため,このクラスのサブクラスとしてクラスを定義すれば,必要なメソッドの実装だけで済むことになります.しかし,Java においては多重継承が許されませので,内部クラスを定義してイベント処理を行うのが通常の方法です.次のプログラムは,上と同じ例に対しイベントアダプタを使用して実行しています. 
/****************************/
/* Window の生成と消滅      */
/*      coded by Y.Suganuma */
/****************************/
import java.awt.*;   // Frame も AWT の一種であるため必要
import java.awt.event.*;   // イベント処理を行う場合に必要

public class Test {
	public static void main (String[] args)
	{
		int a = Integer.parseInt(args[0]);
		int b = Integer.parseInt(args[1]);
		Win win = new Win("整数の和", a, b);
	}
}

/*******************/
/* クラスWinの定義 */
/*******************/
class Win extends Frame {

	/******************/
	/* コンストラクタ */
	/******************/
	Win (String name, int a, int b)
	{
					// Frameクラスのコンストラクタ(Windowのタイトルを引き渡す)
		super(name);
					// Windowの大きさ
		setSize(300, 200);
					// ウィンドウを表示
		setVisible(true);
					// イベントの登録
		addWindowListener(new WinEnd());
					// 出力
		System.out.println("和は = " + (a + b));
	}

	/************/
	/* 終了処理 */
	/************/
	class WinEnd extends WindowAdapter
	{
		public void windowClosing(WindowEvent e) {
			System.exit(0);
		}
	}
}
		
  この例では問題ありませんが,一般に,プログラムが大きくなると多くのクラスファイルが生成されます.そのため,作成したプログラムを配布するような場合に面倒になります.そこで,Java では,jar コマンドを使用して,複数のファイルを圧縮しながら 1 つのファイルにまとめることができます.そのためには,まず,メインメソッドを含むクラスを指定するため,次の 1 行からなるマニフェストファイル(ファイル名は任意ですが,ここでは「 manifest.txt 」としておきます)を作成します(必ず改行しておくこと).
	Main-Class: Test
		
次に,jar コマンドによって JAR ファイル(名称は任意,ここでは,window.jar )を生成します.
	jar cvfm window.jar manifest.txt *.class
		
なお,jar コマンドの一般形は以下に示す通りであり,クラスファイルだけでなく,画像等のファイルを含めることも可能です.
	jar [オプション] JARファイル [マニフェストファイル] [圧縮するファイル ・・・]
		
  以上の手続きによって作成された JAR ファイルをダブルクリックするか,または,以下のコマンドを入力することによってプログラムを実行することができます.
	java -jar window.jar
		
20.1.2 アプレット

  Java プログラムを Web ページ内で実行させるには,HTML の APPLET 要素を使用します.また,APPLET 要素内で PARAM 要素を使用することによって,ホームページから Java プログラムへパラメータを引き渡すことが出来ます.

  APPLET 要素と PARAM 要素の一般的使用方法を記述すれば,以下のようになります.以下の説明において,"[" と "]" で囲まれた部分はオプションです.
	<APPLET 
		CODE = 実行する Java プログラムの名前(〜.class ) 
		HEIGHT = アプレットの高さ 
		WIDTH = アプレットの幅 
		[ARCHIVE = jar ファイル名]
		[ALT = アプレットの代わりに表示するテキスト] 
		[CODEBASE = 〜.class ファイルが存在する URL] 
	>
		[<PARAM NAME = パラメータ1 VALUE = 値1>] 
		[<PARAM NAME = パラメータ2 VALUE = 値2>] 
		    ・・・・・ 
	</APPLET>
		
(プログラム例 20.3 ) 2 つの整数の和(アプレット)

  ここで示すプログラム例は,先に示した 2 つの整数の和を計算する例をアプレットに書き換えたものです(表示方法1).アプリケーションの場合と同様,GUI に対する説明がまだですので,入力は HTML の PARAM 要素を通して行われ,また,出力は Java コンソールに表示されます.従って,アプレット自身には何も表示されません.GUI の使用方法に関しては,アプリケーションとして作成した場合とほとんど同じですので,以下に示す多くの例では,アプレットを基本に説明していきます.なお,アプレットから,上の例で示したような Window を生成することも可能です.

  この例からも明らかなように,Web ページが閉じられればアプレットも終了しますので,Window を閉じるための記述は必要ありません.ただし,アプレットを使用すると,下に示す Applet クラスの 4 つのメソッドが自動的に呼ばれます.必要な関数をオーバーライド(書き直す)する必要があります.下の例では,init だけをオーバーライドしています.
import java.io.*;
import java.applet.*;

public class Test extends Applet {
    public void init()
    {
        int a, b;

        a = Integer.parseInt(getParameter("x"));
        b = Integer.parseInt(getParameter("y"));

        System.out.println("和は " + (a+b));
    }
}
		
  アプレットの場合も,クラスの数が多くなった場合は,
	jar cvf test.jar *.class
		
のような方法で jar ファイルを作成して ARCHIVE に指定することによって,サーバとの交信を減らすことが可能です.

  また,上の例においては,他の数値の加算を行いたい場合は,html ファイルを書き直さざるを得ませんが,JavaScript を利用して,表示方法2のようにすれば,任意の整数の加算が可能になります( 2 つのフィールドに数字を入力した後,「加算」ボタンをクリック).もちろん,このような例に対しては,好ましい方法ではありません.

20.2 グラフィックスと AWT

20.2.1 グラフィックス

  Java には,直線,四角形,円などを描画する機能があります.次の例では,プログラム例 20.3 の計算結果,及び,四角形と円を画面に描画しています.ここをクリックすると,表示されます.

(プログラム例 20.4 ) 2 つの整数の和(グラフィックスの利用)
01	import java.awt.*;
02	import java.applet.*;
03
04	public class Test_1 extends Applet {
05		int wa;
06						// 初期設定(計算)
07		public void init()
08		{
09			int a, b;
10			a  = Integer.parseInt(getParameter("x"));
11			b  = Integer.parseInt(getParameter("y"));
12			wa = a + b;
13			setBackground(Color.white);
14		}
15						// 描画
16		public void paint (Graphics g)
17		{
18			Font f = new Font("TimesRoman", Font.BOLD, 30);   // 30ポイント
19			g.setFont(f);   // フォントの設定
20			g.drawString("和は " + wa + " です", 10, 30);   // 文字列と開始位置(左下)
21			g.setColor(new Color(0, 0, 255));   // 色の設定
22			g.fillRect(40, 40, 50, 50);   // 塗りつぶした正方形(左上,幅,高さ)
23			g.setColor(Color.red);   // 色の設定
24			g.drawOval(115, 40, 50, 50);   // 円(左上,幅,高さ)
25		}
26	}
		
1 行目

  Font クラス,及び,Color クラスを使用するには,この行が必ず必要です.

5 行目

  変数 wa は,12,及び,20 行目で使用していますので,クラス Test のメンバー変数として,ここで定義しておく必要があります.

16〜25 行目

  Applet クラスのメソッド paint をオーバーライドしています,その中で,Graphics クラスのメソッドを使用して,文字列,正方形,及び円を描いています.

(プログラム例 20.5 ) マウスによる描画

  アプレットの画面上でマウスをドラッグすると,その動きに沿った自由曲線が描画されます.MouseMotionListener を利用し,マウスがドラッグされると,時々刻々のマウス位置( Point クラスで表現)をArrayList クラスのオブジェクトに保存し,repaint メソッドによって再描画することによって描画しています.また,mouseMoved メソッド(マウスがドラッグしないで移動したときに呼ばれる)によって,曲線と曲線の間に点 (-1, -1) を入れ,曲線を分離しています.ここをクリックすると,表示されますので,適当な図を描いてみてください.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.util.*;

public class Test_2 extends Applet implements MouseMotionListener {
	ArrayList <Point> v = new ArrayList <Point> ();
					// 初期設定(計算)
	public void init()
	{
		setBackground(Color.white);
		addMouseMotionListener(this);
	}
					// マウスイベントの処理
	public void mouseDragged(MouseEvent e)
	{
		v.add(new Point(e.getX(), e.getY()));
		repaint();
	}
	public void mouseMoved(MouseEvent e)
	{
		int sw = 0;
		if (v.size() > 0) {
			Point p = (Point)v.get(v.size()-1);
			if (p.x >= 0)
				v.add(new Point(-1, -1));
		}
	}
					// 描画
	public void paint (Graphics g)
	{
		int i1;
		Point p1, p2;
		if (v.size() > 0) {
			p1 = (Point)v.get(0);
			for (i1 = 1; i1 < v.size(); i1++) {
				p2 = (Point)v.get(i1);
				if (p1.x >= 0 && p2.x >= 0)
					g.drawLine(p1.x, p1.y, p2.x, p2.y);
				p1 = p2;
			}
		}
	}
}
		

(プログラム例 20.14 ) キーイベントとゲーム

  このアプレットは,キーイベントを使用した簡単なゲームです.左右の矢印キーによって下中央に描かれた黒い矩形(砲台)を左右に動かすことができます.Shift キーをクリックするとレーザ砲が発射され,ターゲット(緑の円)に命中すると,ターゲットの色が一時的にピンクに変化し消滅します.また,ターゲットが,黒い矩形に当たるとゲームオーバになります.以下に示すのが,その Java プログラムです.
001	import java.awt.*;
002	import java.awt.event.*;
003	import java.applet.*;
004	import java.util.*;
005
006	public class Key extends Applet implements Runnable
007	{
008		boolean state = true, fire = false, game = true;
009		int xt, yt, vx, vy, x, y, r = 25, sp = 5, target = 0;
010		Dimension d;
011		Thread th;
012		Random rn;
013
014		public void init() {
015						// 背景色
016			setBackground(new Color(238, 255, 238));
017						// 初期設定
018			d  = getSize();
019			x  = d.width / 2 - 10;
020			y  = d.height - 20;
021			rn = new Random();
022						// キーリスナの付加
023			addKeyListener(new Key_e());
024						// スレッドの生成
025			th = new Thread(this);
026			th.start();
027		}
028
029		public Insets getInsets()
030		{
031			return new Insets(0, 0, 0, 0);
032		}
033
034		public void stop()
035		{
036			state = false;
037		}
038
039		public void run()
040		{
041			double ang, x1, y1;
042
043			while (state) {
044				try {
045					th.sleep(33);
046				}
047				catch (InterruptedException e) {}
048							// ターゲットの移動と消滅
049				if (target == 1) {
050									// ゲームオーバ
051					x1 = xt + r - (x + 10);
052					y1 = yt + r - (y + 10);
053					if (Math.sqrt(x1*x1+y1*y1) < r+10)
054						game = false;
055									// 命中
056					else if (fire && xt <= x+10 && xt > x+10-2*r)
057						target = 2;
058									// 命中しない
059					else {
060						xt += vx;
061						yt += vy;
062						if (xt < -2*r || xt > d.width || yt > d.height)
063							target = 0;
064					}
065				}
066							// ターゲットの生成
067				else if (target == 0){
068					target = 1;
069					xt     = (int)((d.width - 2 * r) * rn.nextDouble());
070					yt     = -r;
071					ang    = 0.5 * Math.PI * rn.nextDouble() + 0.25 * Math.PI;
072					vx     = (int)(sp * Math.cos(ang));
073					vy     = (int)(sp * Math.sin(ang));
074				}
075							// 再描画
076				repaint();
077			}
078		}
079
080		public void paint (Graphics g)
081		{
082							// ゲーム中
083			if (game) {
084				g.fill3DRect(x, y, 20, 20, true);
085									// ターゲットの表示
086				if (target > 0) {
087					if (target == 1)
088						g.setColor(Color.green);
089					else {
090						g.setColor(Color.pink);
091						target = 0;
092					}
093					g.fillOval(xt, yt, 2*r, 2*r);
094				}
095									// レーザ砲の発射
096				if (fire){
097					g.setColor(Color.red);
098					g.drawLine(x+9, 0, x+9, d.height-10);
099					g.drawLine(x+10, 0, x+10, d.height-10);
100					g.drawLine(x+11, 0, x+11, d.height-10);
101					fire = false;
102				}
103			}
104							// ゲームオーバ
105			else {
106				Font f = new Font("TimesRoman", Font.BOLD, 50);
107				g.setFont(f);
108				g.drawString("Game Over", d.width/2-130, d.height/2);
109			}
110		}
111
112		public boolean isFocusable() { return true; }
113
114		class Key_e extends KeyAdapter {
115			public void keyPressed(KeyEvent e)
116			{
117				if (e.getKeyCode() == 37)  // 左矢印
118					x -= 20;
119				else if (e.getKeyCode() == 39)   // 右矢印
120					x += 20;
121				if (e.isShiftDown())   // Shift キー
122					fire = true;
123			}
124		}
125	}
		

18〜21 行目

  表示画面のサイズを得( 18 行目),砲台の初期位置を決めています.また,乱数の初期設定も行っています.

23 行目

  キーリスナを追加しています.

51〜54 行目

  ターゲットと砲台の位置を比較し,それらが衝突した場合にゲームオーバとしています.

56〜57 行目

  レーザ砲のターゲットへの命中判定を行っています.命中した場合は,target = 2 とし,その色を変更します.

60〜63 行目

  ターゲットを移動処理を行い,もし表示画面の外に出た場合は,ターゲットを消滅させています.

68〜73 行目

  ターゲットを生成し,その初期位置(画面の上端で,横位置はランダム),及び,進行方向( [π/4 〜 3π/4] 区間でランダム)を決定しています.この際,[ 0. 1 ] 区間の一様乱数を発生する Random クラスのメソッドを利用しています.

84 行目

  砲台を描画しています.

87〜93 行目

  ターゲットを描画しています.target = 2 の場合(レーザ砲が命中した場合)は,ターゲットは描画された後,削除されます.

97〜101 行目

  レーザ砲による光線を描画し,発射状態を元に戻しています.

112 行目

  キーイベントを受け付けるようにする(フォーカスを受けることができるようにする)ための処理です.キーイベントを処理する場合は,必ず必要です.

117〜122 行目

  KeyEvent クラスのメソッドを利用して,押されたキーを判別し,対応する処理を行っています..

20.2.2 Java AWT

  Java には,Window の生成,ボタンの作成,チェックボックスの作成といった GUI を作成できるようなクラスライブラリが含まれています.その一つが,AWT ( Absract Window Toolkit )です.Java AWT には,GUI 作成用の様々な部品があります.そのすべてについて説明するわけにはいきませんが,主要なものは以下に示す通りです.

  1. Frame: タイトルと境界を持ったトップレベルの Window
  2. Button: ボタン
  3. Checkbox: チェックボックス(複数項目から複数選択)
  4. CheckboxGroup: ラジオボタン(複数項目から1つを選択)
  5. Choice: ドロップダウンリスト(矢印をクリックと,選択項目が表示される)
  6. List: 縦に並んだ項目から選択
  7. Label: ラベル(文字列を表示)
  8. Scrollbar: スクロールバー
  9. TextArea: 複数行にわたる文字列の編集と表示
  10. TextField: 1行の文字列の編集と表示
  11. MenuBar: メニューバー
  12. MenuItem: メニューバーの各メニューが押されたときに現れるメニュー項目

  上記のコンポーネントを画面上に並べる方法(レイアウト)も重要です.AWT には,レイアウトに関して,以下に述べるようなクラスが準備されています.「setLayout(null)」を使用して,レイアウトマネージャを使用しない方法もあります.

  1. BorderLayout: コンテナ(画面)を5つの領域(上,下,左,中,右)に分けて,コンポーネントを配置
  2. CardLayout: 同じ領域に複数のコンポーネントを重ねて配置
  3. FlowLayout: コンポーネントを可能な限り横1行に配置
  4. GridLayout: コンポーネントを格子状に配置
  5. GridBagLayout: 格子状のセルにコンポーネントを柔軟に配置(配置方法は,GridBagConstraints クラスのメソッドで指定)

(プログラム例 20.13 ) 2 つの整数の和( AWT の利用)

  この例では,先に述べた整数の加算の問題を AWT を利用して書いてみました.左側の 2 つのテキストフィールドにデータを入力した後,「=」ボタンをクリックすると右側のテキストフィールドに結果が表示されます.ここをクリックして実行してみてください.
01	import java.awt.*;
02	import java.awt.event.*;
03	import java.applet.*;
04
05	public class Test extends Applet implements ActionListener {
06
07		Button bt;
08		TextField tx1, tx2, tx3;
09
10		/************/
11		/* 初期設定 */
12		/************/
13		public void init()
14		{
15						// レイアウトの変更(フローレイアウト)
16			setLayout(new FlowLayout(FlowLayout.CENTER));
17						// フォントと背景色の設定
18			Font f = new Font("MS 明朝", Font.PLAIN, 20);
19			setFont(f);
20			setBackground(Color.cyan);
21						// テキストフィールドとボタンの追加
22							// テキストフィールド
23			tx1 = new TextField(10);
24			tx1.setBackground(Color.white);
25			add(tx1);
26							// ラベル
27			Label lb = new Label("+");
28			lb.setBackground(Color.cyan);
29			add(lb);
30							// テキストフィールド
31			tx2 = new TextField(10);
32			tx2.setBackground(Color.white);
33			add(tx2);
34							// ボタン
35			bt = new Button("=");
36			bt.setBackground(Color.pink);
37			bt.addActionListener(this);   // リスナー
38			add(bt);
39							// テキストフィールド
40			tx3 = new TextField(10);
41			tx3.setBackground(Color.white);
42			add(tx3);
43		}
44
45		/******************************/
46		/* 上,左,下,右の余白の設定 */
47		/******************************/
48		public Insets getInsets()
49		{
50			return new Insets(10, 10, 10, 10);
51		}
52
53		/******************************/
54		/* ボタンが押されたときの処理 */
55		/******************************/
56		public void actionPerformed(ActionEvent e)
57		{
58			int a, b;
59			if (e.getSource() == bt) {
60				a = Integer.parseInt(tx1.getText());
61				b = Integer.parseInt(tx2.getText());
62				tx3.setText(Integer.toString(a+b));
63			}
64		}
65	}
		
5 行目

  ActionListener を継承しています.

16 行目

  フローレイアウトに変更し,各部品を中央に配置するようにしています.

18〜20 行目

  Font クラスを利用して,フォントを設定しています.また,アプレットの背景色も設定しています.

23〜25,31〜33,40〜42 行目

  テキストフィールドを付加しています.その際,背景色も指定しています.

27〜29 行目

  「+」というラベルを付加しています.その際,背景色も指定しています.

35〜38 行目

  「=」というラベルを持ったボタンを付加しています.その際,背景色も指定しています.37 行目では,ボタンがクリックされたときの処理を行うため,ボタンに対し ActionListener を設定しています.

48〜51 行目

  Insets クラスを利用して,アプレット周囲の余白を設定しています.

56〜64 行目

  ActionListener のメソッド actionPerformed 内で,ActionEvent クラスのメソッドを使用し,ボタンがクリックされたときの処理を記述しています.59 行目の if 文は,どのボタンが押されたかを判断するためのものです(このプログラムでは,必要ありませんが).

20.3 アニメーション

  アニメーションの基本は,Thread クラスを使用し,sleep メソッドによって一定時間毎に何らかの処理を行うことです.

20.3.1 アニメーションと開始と停止

(プログラム例 20.15 ) アニメーションと開始と停止

  この例においては,100 ms 毎に半径の異なる円を描画し,アニメーションを作成しています.また,ボタンをクリックすることによって,スレッドの停止・開始の制御を行うことができます.ここをクリックして表示してみてください.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class Ani_1 extends Applet implements Runnable, ActionListener
{
	boolean state;
	Button b_start, b_stop;
	D_Panel dp;
	Thread th;

	public void init() {
					// レイアウト,背景色,フォント
		setLayout(new BorderLayout(5, 10));
		setBackground(new Color(225, 255, 225));
		setFont(new Font("TimesRoman", Font.BOLD, 20));
					// 上のパネル(ボタンの設定)
		Panel pn1 = new Panel();
		pn1.setLayout(new FlowLayout(FlowLayout.CENTER));
		add(pn1, BorderLayout.NORTH);
		b_start = new Button("開始");
		b_start.addActionListener(this);
		pn1.add(b_start);
		b_stop = new Button("停止");
		b_stop.addActionListener(this);
		pn1.add(b_stop);
					// 中央のパネル(描画領域)
		dp = new D_Panel();
		add(dp, BorderLayout.CENTER);
					// スレッドの生成
		th = new Thread(this);
	}

	public Insets getInsets()
	{
		return new Insets(10, 10, 10, 10);
	}

	public void stop()   // 他ページへ移動の際,一時的にスレッドを停止
	{
		state = false;
	}

	public void run()
	{
		while (state) {
			dp.count++;
			if (dp.count > 10)
				dp.count = 0;
			dp.repaint();   // 再描画
			try {
				th.sleep(100);   // 100 ms 毎の描画
			}
			catch (InterruptedException e) {}
		}
	}

	public void actionPerformed(ActionEvent e)
	{
		if (e.getSource() == b_start) {   // 開始
			state = true;
			th.start();
		}
		else {   // 停止
			state    = false;
			th       = new Thread(this);
			dp.count = 0;
			dp.repaint();
		}
	}
}

class D_Panel extends Panel
{
	int count;
	D_Panel()
	{
		setBackground(Color.white);
		count = 0;
	}
	public void paint (Graphics g)   // 描画
	{
		int i1, r;
		r = 10;
		for (i1 = 0; i1 < count; i1++) {
			g.drawOval(0, 0, 2*r, 2*r);
			r = (int)(1.5 * r);
		}
	}
}
		
20.3.2 アニメーション作成方法

(プログラム例 20.16 ) ボールの運動(描画)

  先に述べたように,一定時間毎に何らかの処理を行うことによってアニメーションを作成することができます.上の例では,「半径の異なる円を順に描く」という方法で作成しました.以下に示す例も同様の方法で作成してあります.ボールをクッリクすると停止し,もう一度クリックすると再び動き出します.ここをクリックして表示してみてください.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class Ani_2 extends Applet implements Runnable
{
	private boolean state = true;
	private double g = 9.8;
	private double v0 = 0;
	private double v = 0;
	private double t = 0;
	private double h0, x, y;
	private int sw = 1;
	private Dimension d;
	private Thread th;

	public void init() {
					// 背景色
		setBackground(new Color(238, 255, 238));
					// 初期設定
		d  = getSize();
		h0 = d.height + 40;
		x  = -40;
		y  = -40;
		addMouseListener(new Mouse());
					// スレッドの生成
		th = new Thread(this);
		th.start();
	}

	public Insets getInsets()
	{
		return new Insets(0, 0, 0, 0);
	}

	public void stop()   // 他ページへ移動の際,一時的にスレッドを停止
	{
		state = false;
	}

	public void run()
	{
		while (state) {
			try {
				th.sleep(33);
			}
			catch (InterruptedException e) {}
			if (x < d.width + 80 && sw > 0) {
				x += 1.5;
				t += 0.1;
				v  = -g * t + v0;
				y  = d.height - (-0.5 * g * t * t + v0 * t + h0);
				if (y >= d.height - 80 && v < 0) {
					y  = d.height - 80;
					v0 = -0.8 * v;
					h0 = 80;
					t  = 0;
				}
				System.out.println("position " + x + " " + y);
				repaint();
			}
		}
	}

	public void paint (Graphics g)
	{
		g.setColor(Color.green);
		g.fillOval((int)x, (int)y, 80, 80);
	}

	class Mouse extends MouseAdapter {
		public void mouseClicked(MouseEvent e)
		{
			int mx, my;
			double x1, y1, r;

			mx = e.getX();
			my = e.getY();
			x1 = x + 40 - mx;
			y1 = y + 40 - my;
			r  = Math.sqrt(x1 * x1 + y1 * y1);
			if (r < 40) {
				if (sw > 0)
					sw = 0;
				else
					sw = 1;
			}
		}
	}
}
		
(プログラム例 20.17 ) ボールの運動(外部画像)

  複雑な画像を描きたいような場合は,上に示したような方法では難しくなります.この例では,円に対応する外部画像( ball.gif )を,Image クラスのオブジェクトとして読み込み,それを画面に表示しています.再帰の例と同様,ボールをクッリクすると停止し,もう一度クリックすると再び動き出します.ここをクリックして表示してみてください.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class Ani_3 extends Applet implements Runnable
{
	private boolean state = true;
	private double g = 9.8;
	private double v0 = 0;
	private double v = 0;
	private double t = 0;
	private double h0, x, y;
	private int sw = 1;
	private Dimension d;
	private Thread th;
	private Image im;

	public void init() {
					// 背景色
		setBackground(new Color(238, 255, 238));
					// 初期設定
		d  = getSize();
		h0 = d.height + 40;
		x  = -40;
		y  = -40;
		im = getImage(getCodeBase(), "ball.gif");
		addMouseListener(new Mouse());
					// スレッドの生成
		th = new Thread(this);
		th.start();
	}

	public Insets getInsets()
	{
		return new Insets(0, 0, 0, 0);
	}

	public void stop()
	{
		state = false;
	}

	public void run()
	{
		while (state) {
			try {
				th.sleep(33);
			}
			catch (InterruptedException e) {}
			if (x < d.width + 80 && sw > 0) {
				x += 1.5;
				t += 0.1;
				v  = -g * t + v0;
				y  = d.height - (-0.5 * g * t * t + v0 * t + h0);
				if (y >= d.height - 80 && v < 0) {
					y  = d.height - 80;
					v0 = -0.8 * v;
					h0 = 80;
					t  = 0;
				}
				System.out.println("position " + x + " " + y);
				repaint();
			}
		}
	}

	public void paint (Graphics g)
	{
		g.drawImage(im, (int)x, (int)y, this);
	}

	class Mouse extends MouseAdapter {
		public void mouseClicked(MouseEvent e)
		{
			int mx, my;
			double x1, y1, r;

			mx = e.getX();
			my = e.getY();
			x1 = x + 40 - mx;
			y1 = y + 40 - my;
			r  = Math.sqrt(x1 * x1 + y1 * y1);
			if (r < 40) {
				if (sw > 0)
					sw = 0;
				else
					sw = 1;
			}
		}
	}
}
		
(プログラム例 20.18 ) 花火

  アニメーションを作成する方法としては,今まで述べた図形を描画する方法や外部から読み込んだ図形を順に表示する方法の他に,MemoryImageSource クラスを利用して,イメージのピクセル値を直接編集する方法が考えられます(もちろん,これらの方法を同時に使用する方法も考えられます).この例は,ピクセル値を直接編集する方法によって作成しています. ここをクリックして表示してみてください.
import java.awt.*;
import java.awt.image.*;
import java.applet.*;
import java.util.Random;

public class Ani_4 extends Applet implements Runnable
{
	private int max = 20;   // 花火の数
	private int m_pr = 7;   // 打ち上げ間隔の最大値
	private int m_cl = 10;   // 花火の色の最大値
	private int f_l = 300;   // 花火の直径
	private int count = 0, next = 0, height, width, size, f_size, k[], x[], y[], pixels[][], color[], cl[];
	private boolean state = true;
	private Thread th;
	private MemoryImageSource mis[];
	private Image im[];
	private Random rn;

	public void init() {
		int i1;
					// 背景色
		setBackground(new Color(0, 0, 0));
					// 初期設定
		width  = getSize().width;
		height = getSize().height;
		size   = width * height;
		f_size = f_l * f_l;
		pixels = new int [max][f_size];
		k      = new int [max];
		x      = new int [max];
		y      = new int [max];
		cl     = new int [max];
		mis    = new MemoryImageSource [max];
		im     = new Image [max];
		for (i1 = 0; i1 < max; i1++)
			k[i1] = -1;
		rn    = new Random();
		color = new int [m_cl];
		color[0] = 0xffff0000;
		color[1] = 0xff00ff00;
		color[2] = 0xff0000ff;
		color[3] = 0xffffff00;
		color[4] = 0xffff00ff;
		color[5] = 0xff00ffff;
		color[6] = 0xffeeffee;
		color[7] = 0xffffaaaa;
		color[8] = 0xffaaffaa;
		color[9] = 0xffaaaaff;
					// スレッドの生成
		th = new Thread(this);
		th.start();
	}

	public Insets getInsets()
	{
		return new Insets(0, 0, 0, 0);
	}

	public void stop()
	{
		state = false;
	}

	public void run()
	{
		double ang, s;
		int i0, i1, i2, i3, kx, ky, kxy, sw;

		while (state) {
			try {
				th.sleep(200);
			}
			catch (InterruptedException e) {}

			sw = 0;
			for (i0 = 0; i0 < max; i0++) {
				if (k[i0] < 0) {
					if (count >= next && sw == 0) {
						sw     = 1;
						count  = 0;
						cl[i0] = (int)(m_cl * rn.nextDouble());
						if (cl[i0] >= m_cl)
							cl[i0] = m_cl - 1;
						for (i1 = 0; i1 < f_size; i1++)
							pixels[i0][i1] = 0x00000000;
						x[i0]   = (int)(width * rn.nextDouble()) - f_l / 2;
						y[i0]   = (int)(height * rn.nextDouble()) - f_l / 2;
						k[i0]   = 0;
						mis[i0] = new MemoryImageSource(f_l, f_l, pixels[i0], 0, f_l);
						mis[i0].setAnimated(true);
						im[i0] = createImage(mis[i0]);
						next   = (int)(m_pr * rn.nextDouble());
						if (next <= 0)
							next = 1;
					}
				}
				else {
					k[i0]++;
					if (k[i0] > m_pr)
						k[i0] = -1;
					else {
						s   = Math.PI / 6;
						ang = 0;
						for (i1 = 0; i1 < 12; i1++) {
							kx = f_l / 2 + (int)(20 * k[i0] * Math.cos(ang));
							ky = f_l / 2 + (int)(20 * k[i0] * Math.sin(ang));
							for (i2 = kx-5; i2 < kx+5; i2++) {
								for (i3 = ky-5; i3 < ky+5; i3++) {
									kxy = f_l * i2 + i3;
									if (kxy >= 0 && kxy < f_size)
										pixels[i0][kxy] = color[cl[i0]];
								}
							}
							pixels[i0][f_l*(kx-1)+ky-6] = color[cl[i0]];
							pixels[i0][f_l*kx+ky-6] = color[cl[i0]];
							pixels[i0][f_l*(kx+1)+ky-6] = color[cl[i0]];
							pixels[i0][f_l*(kx-1)+ky+5] = color[cl[i0]];
							pixels[i0][f_l*kx+ky+5] = color[cl[i0]];
							pixels[i0][f_l*(kx+1)+ky+5] = color[cl[i0]];
							pixels[i0][f_l*(kx-6)+ky-1] = color[cl[i0]];
							pixels[i0][f_l*(kx-6)+ky] = color[cl[i0]];
							pixels[i0][f_l*(kx-6)+ky+1] = color[cl[i0]];
							pixels[i0][f_l*(kx+5)+ky-1] = color[cl[i0]];
							pixels[i0][f_l*(kx+5)+ky] = color[cl[i0]];
							pixels[i0][f_l*(kx+5)+ky+1] = color[cl[i0]];
							ang += s;
						}
						im[i0] = createImage(mis[i0]);
					}
				}
			}
			count++;
			repaint();
		}
	}

	public void paint (Graphics g)
	{
		int i0;
		for (i0 = 0; i0 < max; i0++) {
			if (k[i0] >= 0)
				g.drawImage(im[i0], x[i0], y[i0], this);
		}
	}
}
		
20.3.3 バッファリング

  今まで述べた例のような簡単なアニメーションの場合は問題になりませんが,一度に描く量が多く,かつ,描画速度が速くなると画面のちらつきが発生するようになります.

(プログラム例 20.19 ) バッファリング

  この例においては,朝顔の背景の上に,2 つの文字列と,直径 10 ピクセルの円をランダムな位置に 10 ms 毎に描いています.ここをクリックして表示してみてください.時々画面が崩れ,ちらつくのが分かると思います.
import java.awt.*;
import java.applet.*;
import java.util.Random;

public class Ani_5 extends Applet implements Runnable {

	int w, h, x, y;
	boolean win_state = true;
	Image hana;
	Thread th = null;

	public void init()
	{
					// イメージの読み込み
		hana = getImage(getCodeBase(), "hana.gif");
					// スクリーンのサイズ
		w    = getSize().width;
		h    = getSize().height;
	}

	public void start()
	{
		th = new Thread(this);
		th.start();
	}

	public void run()
	{
					// ランダム変数の初期化
		Random rand = new Random();
					// 点の生成と描画
		while (win_state) {
			try {
				th.sleep(10);
			}
			catch (InterruptedException e) {}
			x = (int)(rand.nextDouble() * w);
			y = (int)(rand.nextDouble() * h);
			repaint();
		}
	}

	public void paint (Graphics g)
	{
		g.setColor(new Color(0, 255, 255));
		g.drawImage(hana, 0, 0, this);
		Font f = new Font("TimesRoman", Font.BOLD, 20);
		g.setFont(f);
		g.drawString("背景", 140, 50);
		g.drawString("朝顔", 140, 80);
		g.fillOval(x, y, 10, 10);
	}
}
		
  次に示す例には,上と全く同じアニメーションですが,バッファリングを行っています.ここをクリックして表示してみてください.正常に表示されるのが分かると思います.なお,下の例においては,MediaTracker クラスを使用して,すべての画像を完全に読み込むまで次に進まないようにしています.この例では必要ありませんが,大量の画像を読み込む場合は,このような方法を採る必要があります.
import java.awt.*;
import java.applet.*;
import java.util.Random;

public class Ani_6 extends Applet implements Runnable {

	int w, h;
	boolean win_state = true;
	Image hana, Buf;
	Graphics g_b;
	Thread th = null;

	public void init()
	{
					// イメージの読み込み
		hana = getImage(getCodeBase(), "hana.gif");
		MediaTracker trk = new MediaTracker(this);
		trk.addImage(hana, 0);
		try {
			trk.waitForID(0);
		}
		catch (InterruptedException e) {}
					// スクリーンバッファの生成
		w   = getSize().width;
		h   = getSize().height;
		Buf = createImage(w, h);     //バッファ生成
		g_b = Buf.getGraphics();    //グラフィックコンテキスト取得
	}

	public void start()
	{
		th = new Thread(this);
		th.start();
	}

	public void run()
	{
		int x, y;
					// ランダム変数の初期化と色の設定
		Random rand = new Random();
		g_b.setColor(new Color(0, 255, 255));
					// 背景画像の生成
		Image Back = createImage(w, h);
		Graphics g = Back.getGraphics();
		g.drawImage(hana, 0, 0, this);
		Font f = new Font("TimesRoman", Font.BOLD, 20);
		g.setFont(f);
		g.drawString("背景", 140, 50);
		g.drawString("朝顔", 140, 80);
		g.dispose();   //グラフィックコンテキスト破棄

		while (win_state) {
			try {
				th.sleep(10);
			}
			catch (InterruptedException e) {}
			g_b.drawImage(Back, 0, 0, this);  //背景画像をバッファに描画
			x = (int)(rand.nextDouble() * w);
			y = (int)(rand.nextDouble() * h);
			g_b.fillOval(x, y, 10, 10); // 円をバッファに描画
			repaint();
		}
	}

	public void paint (Graphics g)
	{
		g.drawImage(Buf, 0, 0, this);      //バッファを画面に描画
	}

	public void update(Graphics g)    //オーバーライドして最低限のことだけをする
	{
		paint(g);
	}
}
		
20.4 ネットワーク

  ここでは,Web ページにおいてネットワークを利用する場合について説明します.なお,ソケットを使用する方法に関しては,19 章を参照してください.

(プログラム例 20.20 ) URL へリンク

  他のホームページへ移動するのは簡単です.この例では,プログラム例 20.3 と同様,PARAM 要素を通してリンク先に関する情報を Java プログラムに渡し,Java では,URL クラスApplet クラスの getAppletContext().showDocument() というメソッドを利用して,目的のページへ移動しています.ここをクリックして表示してみてください.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.net.*;

public class Net_1 extends Applet implements ActionListener
{
	Button bt[] = new Button [2];
	URL url[] = new URL [2];

	public void init() {
		Font f = new Font("TimesRoman", Font.BOLD, 20);
		setFont(f);
					// ボタンの設定
		bt[0] = new Button(getParameter("URL1_name"));
		bt[0].addActionListener(this);
		add(bt[0]);
		url[0] = URL_List(getParameter("URL1"));

		bt[1] = new Button(getParameter("URL2_name"));
		bt[1].addActionListener(this);
		add(bt[1]);
		url[1] = URL_List(getParameter("URL2"));
	}
					// URL の設定
	public URL URL_List(String name)
	{
		URL ul = null;
		try {
			ul = new URL(name);
		}
		catch (MalformedURLException e)
		{
			System.out.println("Bad URL: " + name);
		}
		return ul;
	}
					// ボタンがクリックされたときの処理
	public void actionPerformed(ActionEvent e)
	{
		URL link = null;
		for (int i1 = 0; i1 < bt.length && link == null; i1++) {
			if (e.getSource() == bt[i1])
				link = url[i1];
		}
		if (link != null) {
			System.out.println(link + " に接続中\n");
			getAppletContext().showDocument(link);   // リンク
		}
	}
}
		

(プログラム例 20.21 ) URL データの読み込み

  この例では,PARAM 要素を通して指定された URL にあるテキストデータ(菅沼のホームページの index.html )を,URL,または,URLConnection クラスのメソッドを使用して,アプレットのテキストエリアに読み込みます.この例のように,単にテキストデータを読むだけなら,Java だけで記述可能です.ここをクリックして表示してみてください.

  なお,この例においてはスレッドを利用していますが,必ずしもその必要はありません.しかし,一般に,ネットワークへ接続しデータを読み込むには,待ち時間も含め時間が掛かります.そのためだけに CPU を占有してしまうことは好ましくありません.そこで,Thred クラスの sleep メソッドを利用して CPU を解放する方法の方が良いと思います.
import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;

public class Net_2 extends Applet implements Runnable
{
	URL url;
	Thread th;
	TextArea ta;
	boolean state = true;

	public void init() {
					// 読み込むファイル名
		String name = getParameter("FILE");
					// 読み込むファイルの設定
		try {
			url = new URL(getCodeBase(), name);
		}
		catch (MalformedURLException e)
		{
			System.out.println("Bad URL: " + name);
		}
					// テキストエリアの追加
		Font f = new Font("TimesRoman", Font.BOLD, 20);
		setFont(f);
		ta = new TextArea(15, 50);
		add(ta);
	}
					// 周囲の余白
	public Insets getInsets()
	{
		return new Insets(10, 10, 10, 10);
	}
					// スレッドのスタート
	public void start()
	{
		if (th == null) {
			th = new Thread(this);
			th.start();
		}
	}
					// スレッドの停止
	public void stop()
	{
		state = false;
	}

	public void run()
	{
		InputStream stm = null;
//          		URLConnectionクラスを使用する場合
//		URLConnection c_inURL = null;
		BufferedReader in = null;
		String line;
		StringBuffer buf = new StringBuffer();

		while (state) {

			try {
				Thread.currentThread().sleep(100);	// 100msスリープ
			}
			catch (InterruptedException e) {};

			try {
							// URL への接続
				stm = this.url.openStream();
//          			URLConnectionクラスを使用する場合(上の1行は削除)
//				c_inURL = url.openConnection();
//				c_inURL.setDoInput(true);
//				c_inURL.setDoOutput(false);
//				c_inURL.setUseCaches(false);
//				c_inURL.connect();
//				stm = c_inURL.getInputStream();
							// ファイルから 1 行ずつ読み込み
				in = new BufferedReader(new InputStreamReader(stm));
				while ((line = in.readLine()) != null)
					buf.append(line + "\n");
							// テキストエリアに出力
				ta.setText(buf.toString());
				state = false;
				in.close();
			}
			catch (IOException e) {
				System.out.println("IO Error:" + e.getMessage());
			}
		}
	}
}
		

(プログラム例 20.22 ) URL(サーバ)との会話

  URL(サーバ)にデータを送り,そのデータをサーバ側にファイルとして保存するなど,サーバ側で何らかの処理を行いたいような場合は,すべてのプログラムをクライアント側の Java だけで記述することは不可能です.CGI( Common Gateway Interface ) が必要になります.CGI は,外部のプログラム(フォームを含んだホームページやサーバにデータを送信するアプレットなど)が,Web サーバとの間で情報を授受するための標準的な方法を規定するものです.この方法を実現するプログラムのことを,CGI プログラムと呼び,Web サーバに準備しておく必要があります.

  ここでは,テキストエリアに入力されたデータをサーバーに転送し,ファイルとして保存したい場合について説明します.なお,サーバ側の処理が終了すると,サーバはメッセージをクライアント側に送信しますが,そのメッセージも同じテキストエリアに表示します.ここをクリックして表示してみてください.ただし,ここには CGI プログラムが準備されていないため,送信するとエラーになってしまいます.
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.net.*;
import java.io.*;

public class Net_3 extends Applet implements Runnable, ActionListener
{
	URL url;
	Thread th;
	TextArea ta = new TextArea(15, 50);
	boolean state = true;
	boolean submit_sw = false;
	Button soshin;

	public void init() {
					// CGIの設定
		String name = "test.cgi";   // PHPの場合: String name = "test.php";
		try {
			url = new URL(getCodeBase(), name);
		}
		catch (MalformedURLException e)
		{
			System.out.println("Bad URL: " + name);
		}
					// テキストエリアの追加
		Font f = new Font("TimesRoman", Font.BOLD, 20);
		setFont(f);
		add(ta);
					// ボタンの追加
		soshin = new Button("送信");
		add(soshin);
		soshin.addActionListener(this);
	}
					// 周囲の余白
	public Insets getInsets()
	{
		return new Insets(10, 10, 10, 10);
	}
					// スレッドのスタート
	public void start()
	{
		if (th == null) {
			th = new Thread(this);
			th.start();
		}
	}
					// スレッドの停止
	public void stop()
	{
		state = false;
	}
					// ボタンが押されたときの処理
	public void actionPerformed(ActionEvent e)
	{
		if (e.getSource() == soshin)
			submit_sw = true;
	}

	public void run()
	{
		URLConnection c_URL;
		OutputStream o_stm = null;
		PrintStream out = null;
		InputStream i_stm = null;
		BufferedReader in = null;
		String line;
		StringBuffer buf = new StringBuffer();

		while (state) {

			try {
				Thread.currentThread().sleep(100);	// 100msスリープ
			}
			catch (InterruptedException e) {};

			if (submit_sw) {
				try {
					line = ta.getText();
								// データが入力されたことの確認
					if (line.equals("")) {
						ta.setText("データを入れて下さい\n");
						try {
							Thread.currentThread().sleep(1000);
						}
						catch (InterruptedException e) {};
						ta.setText("");
					}
					else {
								// CGIへ接続
						c_URL = url.openConnection();
						c_URL.setDoInput(true);
						c_URL.setDoOutput(true);
						c_URL.setUseCaches(false);
						c_URL.connect();
								// データをCGIへ送信
						o_stm = c_URL.getOutputStream();
						out   = new PrintStream(o_stm);
//						line  = "trans=" + line;   PHP の場合はこの行が必要
						out.print(line);
						out.close();
								// CGIからのメッセージを受け取る
						i_stm = c_URL.getInputStream();
						in    = new BufferedReader(new InputStreamReader(i_stm));
						while ((line = in.readLine()) != null)
							buf.append(line + "\n");
								// メッセージをテキストエリアに出力
						ta.setText(buf.toString());
						in.close();

						state = false;
					}
					submit_sw = false;
				}

				catch (UnknownServiceException e) {
					ta.setText("Unknown Service: " + e.getMessage());
				}
				catch (IOException e) {
					ta.setText("IO Error: " + e.getMessage());
				}
			}
		}
	}
}
		
  上の例で見るように,クライアント側は,CGI プログラムを一種のファイルとみなして入出力を行っています.また,CGI プログラムは,クライアントから送られてきたデータを標準入力として読み込み,また,クライアント側へのメッセージは標準出力として出力します.CGI プログラムは,どのようなプログラミング言語でも書くことが出来ます.次に示すのは,シェルスクリプトで書いた例です.見ても明らかなように,非常に簡単なプログラムです.なお,いずれの CGI プログラムでも同様ですが,HTTP ヘッダとして CGI プログラムが出力する情報の種類を最初に送ってやる必要があります.シェルスクリプトの場合は,2 行目及び 3 行目がこの情報に相当します.

  このシェルスクリプトを CGI として動作させるためには,上に示した Java プログラムをコンパイルしたクラスファイルと同じディレクトリに test.cgi という名前で保存し,test.cgi のファイルモードを実行可能に設定します.さらに,データを保存するため,以下のような手続きが必要です.この例においては,クライアントから送られてきたデータを,CGI と同じディレクトリにある data というファイルに保存しています.そのため,すべての人が読み書き可能なファイルモードを持つ data を前もって準備しておくか,又は,CGI が存在するディレクトリ自体をすべての人が読み書き可能なように設定しておかなければなりません.このファイル data に関する処置は,どの言語で CGI を記述しても同じです.
#!/bin/sh
echo Content-type: text/plain  # HTTP header
echo                           # HTTP header
umask 000
cat > data
umask 022
echo End of Submit
		
  次は,Perl で CGI を記述した例です.この場合も,ファイル名を test.cgi とし,かつ,ファイルモードを実行可能に変更する必要があります.
#!/usr/local/bin/perl
#

#
# 出力データファイルを指定
#

$datafile = "data";

#
# 標準入力データ読込
#
read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});

#
# ファイルのオープン
#
if (!open(OUT, ">$datafile")) {
    print "Content-type: text/plain\n\n";	# オープンに失敗した場合
    print "Writing Error\n";				# 書込失敗メッセージ送出
    exit(0);
}

#
# データファイル書込
#
flock(OUT, 2);					# ファイルをロック
print OUT "$buffer\n";				# データを書き出す
print OUT "\n";					# データを書き出す
flock(OUT, 8);
close(OUT);					# ファイルをクローズ

print "Content-type: text/plain\n\n";		# HTTPヘッダの送信
                                     		# (これから送る情報の種類)
print "データの送信を終了しました\n";       # 書込完了メッセージ送出
exit(0);
		
  次は,C/C++ で書いた例です.上で述べた 2 つの例との違いは,コンパイルしなければならない点です.つまり,以下のプログラムをサーバ側でコンパイルし,実行可能プログラムの名前を test.cgi にする必要があります.
#include <stdio.h>
#include <stdlib.h>

int main()
{
	char c;
	FILE *out;

	out = fopen("data", "w");

	while ((c = getchar()) != EOF)
		fputc(c, out);

	fclose(out);

	printf("Content-type: text/plain\n\n");   // HTTPヘッダの送信
	printf("データの送信を終了しました\n");

	return 0;
}
		
  最後は,PHP で書いた例です.この場合も,Perl などと同様,コンパイルする必要はありませんが,Java のプログラムにおいて 2 行ほど修正してあります.PHP の場合は,「変数名=内容」の形で送信された「内容」が,配列 $_POST['変数名'] の中身になりますので,Java プログラムにおいて,テキストエリアに入力されたデータの前に「 trans= 」を付け加えています.PHP のプログラムでは,配列 $_POST['trans'] の中身を改行で区切り,それぞれを配列 $x に保存しています.
<?php
					// 送信されてきたデータを受け取る(「\n」で分離)
	$k = 0;
	$x = strtok($_POST['trans'], "\n");
	while ($x) {
		$p[$k] = $x;
		$x     = strtok("\n");
		$k++;
	}
					// ファイルへ出力
	$out = fopen("data", "wb");
	for ($i1 = 0; $i1 < $k; $i1++)
		fwrite($out, $p[$i1]);
	fclose($out);
					// メッセージの送信
	printf("データの送信を終了しました\n");
?>
		
  なお,CGI の名称は test.cgi のままで構いませんが,その場合は,PHP のプログラムの 1 行目に次の行を入れ,かつ,実行可能なモードに変更する必要があります.
	#!/usr/local/bin/php
		
20.5 Java を使用した様々な例題

(プログラム例 20.6 ) 8 / 15 パズル

  表示される Window に適当な値を設定してやることによって,8 パズル,または.15 パズルを実行できます.プログラムを開始すると下の左図のような Window が表示されます.入力方法は,以下に示すとおりです. → プログラム

   

  一番上(ゲーム)で,まず,8 パズルか 15 パズルかを選びます.

  2 番目(初期設定)は,初期状態を指定するものです.「ランダム」を選べば,コンピュータが自動的に問題を作成してくれます.また,「入力」を選択した場合は,上の右図のような Window ( 8 パズルの場合)が表示され,自分で初期状態を入力することになります.8 パズルであれば 1 から 8 まで,15 パズルであれば 1 から 15 までの数字を入力します.いずれの場合も,一カ所だけ空白ができますが,そのままにしておいても,または,0 を入力してもいずれでも構いません.

  3 番目(ゴール)は,目標状態を入力します.その後,「 OK 」 ボタンをクリックすると(初期状態を入力する場合は,初期状態を入力した後,その Window の 「 OK 」 ボタンをクリックすると),目標状態が表示されます.8 パズルの場合,「回転」を選択すれば下に示す上の段の 1 番目の図,そうでなければ,2 番目の図のように目標状態が設定されます.また,15 パズルの場合は,下の段の 1,2 番目の図のようになります.


  目標状態の画面で,「ゲームの実行」ボタンをクリックすると,ゲームが開始されます.移動したいコマをクリックすれば,コマが移動します.コマの移動を繰り返した後,目標状態に達すれば,その時点までにコマを移動した回数が表示されます.目標状態に達しても,ゲームが終了するわけではありません.終了したい場合は,Window の終了ボタン(「×」)をクリックしてください.

  「8/16パズル」は,同じプログラム例をアプレット用に書き換えた例です.

  例え 8 パズルであっても,問題によっては解くためにかなりの時間がかかります.そこで,コンピュータによって解くことを考えてみます.8 パズルの解法を開き,表示されているアプレットに適当な値を設定した後「OK」ボタンをクリックすると,初期状態を入力する画面が生成されますので,解きたい問題を入力した後,その画面の「OK」ボタンをクリックしてください.

  問題が解けた場合は,

  状態番号 45 深さ=5 評価=4
   1 2 3
   8 0 4
   7 6 5
  成功!!

のように表示されるはずです.「状態番号」の後ろにある数字がコマ(数字)を動かした回数(操作回数)に相当します.なお,場合によっては,

  解決できませんでした!

のような結果になるかもしれません.最大試行回数,探索方法,最大探索深さなどを変更して再度挑戦してみてください.なお,評価関数としては,以下のようなものを利用しています.

  1. 評価関数1 f(n)=g(n)+h(n)
    • g(n):節点nの深さ
    • h(n):節点nのパターンの中で,誤って置かれている駒の数

  2. 評価関数2 f(n)=g(n)+h(n)
    • g(n):節点nの深さ
    • h(n)=p(n)+3s(n)
      p(n):各駒の正しい位置からの距離の和
      s(n):中心以外の駒に対し,その次の駒が正しい順序になっていなければ2,そうでなければ0,中心の駒には1を与える事によって得られる得点

(プログラム例 20.7 ) 剛体振り子の運動

       

  表示される Window (上図の左)に適当な値を設定してやることによって,剛体の振り子(上図の中),または,倒立振り子(上図の右)のシミュレーションが可能です.振り子と倒立振り子の運動は,以下に示す微分方程式によって記述されます.この方程式や図からも明らかなように,これらは全く同じシステムです.ただ,角度の計り方が異なるだけです.

振り子
     (I+mr2d2θ/dt2+kdθ/dt+mrgsinθ=−k1θ

倒立振り子
     (I+mr2d2θ/dt2+kdθ/dt−mrgsinθ=−k1θ

ただし,

   m:棒の質量
   l:棒の長さ
   r:棒の重心の位置(=l/2)
   i:棒の慣性能率(l2/12)
   k:摩擦係数(≧0)
   g:重力の加速度(=9.8)
   k1:フィードバックゲイン

とします. → プログラム

  剛体振り子は,同じプログラム例をアプレット用に書き換えた例です.

(プログラム例 20.8 ) TSP を自分で解く

  10 及び 20 都市の巡回セールスマン問題( TSP )に対して,自分で都市間を繋ぎながら解くためのプログラムです.プログラムをスタートさせると,右図に示すような Window が表示されますので,どちらかの問題を選択して下さい. → プログラム

  右図に示すのは,10 都市を選択した場合に表示される Window です.この Window において,マウスによって適当な都市間を接続してください.接続の方法は以下の通りです.

  1. 適当な都市を選びマウスでクリックします.画面の左上に「 Next Position ? 」というメッセージが現れます(現れなかったら,もう一度マウスでクリックしてください).

  2. 接続したい相手の都市を選びマウスでクリックしてください.2 つの都市が直線で結ばれ,画面の左上にすでに結ばれた都市間の距離の合計が表示されます(表示されない場合は,もう一度マウスでクリックしてください).

  3. 以上の処理を,すべての都市が結ばれるまで繰り返してください.なお,すでに直線で結ばれている 2 つの都市を選択すると,接続が解除されます.

  TSPは,同じプログラム例をアプレット用に書き換えた例です.

(プログラム例 20.9 ) グラフの表示

  このプログラムでは,棒グラフ(縦,横),片方の軸が項目の場合に対する折れ線グラフ(縦,横),両方の軸がデータの場合に対する折れ線グラフ(縦,横),積み上げ式棒グラフ(縦,横),円グラフ,散布図,レーダーチャート,ボード線図を描くことができます.また,表示画面の大きさを変えることも可能です.具体的な内容については,アプレット版の「グラフの描画」(プログラム)を見てください.

  ホームページを作成する場合,データベースなどから引き出したデータを単に表示するだけではなく,グラフとして表示したい場合があります.そのためには,上で示した各グラフを描画するプログラムにグラフを表示するためのデータを渡してやる必要があります.様々な方法が考えられますが,一つの方法は,
	<APPLET CODE="Graph.class" WIDTH="0" HEIGHT="0">
		<PARAM NAME="data" VALUE="1,2,・・・">
	</APPLET>';
		
のように,APPLET 要素の PARAM 要素を利用し,描画したいグラフの種類とそのグラフを描画するために必要なデータをカンマで区切って渡してやる方法です.プログラム Graph.java では,渡されたデータをカンマを利用して分割し,グラフを描画します.このようにすれば,サーバ上のデータベースから得られた結果を,ネットワークを介して即座にグラフとして表示することも可能になります.

(プログラム例 20.10 ) 2次方程式の根

  「2次方程式の根」は,2次方程式,
   ax2+bx+c=0
の根を求めるためのものです.適当な数値を入力して,結果を確認してみてください.

  2次方程式の根を求める程度のことであっても,コンピュータが勝手にやってくれるわけではありません.その計算手順を記述したものをプログラムとしてコンピュータに教えてやる必要があります.参考のため,上記の計算を行うプログラムを下に示します(Javaで書いてあります).
/************************/
/*     2次方程式を解く */
/************************/
public void solve()
{
	double a = dt.a, b = dt.b, c = dt.c, D, x, x1, x2;
	String str;
/*
          一次方程式 or 解なし
*/
	if (Math.abs(a) <= 1.0e-10) {
		if (Math.abs(b) <= 1.0e-10)
			text.insert("解を求めることができません!\n", 0);
		else {
			x = -c / b;
			str = "x = " + Double.toString(x) + "\n";
			text.insert(str, 0);
		}
	}
/*
         二次方程式
*/
	else {

		D = b * b - 4.0 * a * c;
					// 実根
		if (D >= 0.0) {
			D  = Math.sqrt(D);
			x1 = 0.5 * (-b - D) / a;
			x2 = 0.5 * (-b + D) / a;
			str = "x = " + Double.toString(x1) + ", " + Double.toString(x2) + "\n";
			text.insert(str, 0);
		}
					// 虚根
		else {
			D  = Math.sqrt(-D);
			x1 = -0.5 * b / a;
			x2 = 0.5 * D / a;
			str = "x = " + Double.toString(x1) + " ± i " + Double.toString(x2) + "\n";
			text.insert(str, 0);
		}
	}
}
		

(プログラム例 20.11 ) GAのステップ実行

  「GAのステップ実行」は,GA の基本的な流れを理解してもらうために,GA をステップごとに実行するシステムです.このシステムでは,ビット列にある 1 の数を適応度として実行しています.従って,世代が進むほど,ビット列の中の 1 の数が増加していくはずです.2 組の親をランダムに選択して 1 点交叉を行い,淘汰方法としては,エリート選択を使用しています.

  なお,親や交叉位置の選択は,人間が行うことになりますので,できるだけ無作為に選択してみてください.

(プログラム例 20.12 ) 関数の最大値

  「関数の最大値」は,関数,
y=−x2+2x+1=−(x−1)2+2

y=sin(3x)+0.5sin(9x)+sin(15x+50)

の最大値をGAを用いて求めるためのものです.関数を選択して,「Start」ボタンを押すと,1世代目の結果が表示されます.次に,「Next」ボタンを押す毎に,2世代目以降の結果が順に表示されます.

ホームページ 目次 演習解答例目次 付録目次 索引