「ソフトウェア演習(B)Java言語」
ベーシックコース

第6回 Javaマルチスレッドプログラミングとインタフェース
(JavaGUIプログラミングを通して) (Collectionsクラス+マルチスレッドプログラミング)

これまで、通常のクラスとその継承の仕組み、抽象クラスに基づくクラスの継承を中心にクラスの継承を説明してきました。 その際、インタフェースと呼ばれる抽象クラスと似た、基底クラスに相当するオブジェクトを用意して、 派生クラスを準備してプログラミングを組み立てていく方法があることを紹介しました。 今回は、インタフェースに焦点を当てて、それを継承(実装)するクラスを構築していく例をJavaのGUIのひとつであるSwingを通して紹介します。 Swingのチュートリアルは、英語での説明がメインですが、OracleのWebサイトにもありますので、個々のSwing内のクラスの詳細はそちらを参照してください。 (Androidには、Swingと同様な機能を提供する別のGUIとして、 たとえばdroiddrawがあります。)

Java Collectionsクラスの復習

これまで、 線形リストやクイックソートのようなクラスを自作する方法と、 Javaがシステムとして提供するCollectionsに代表されるクラスを用いて、 各種の集合型データ構造を借りてきて利用する例をいくつか紹介してきました。 Javaが提供するJava Collections FrameWork (JCF)では、 代表的なデータ構造を多く提供しています。 いずれもjava.utilパッケージに含まれます。 中でも以下の3つはいろいろな場面で利用されるグループです。 ここでListMapSetインタフェースとして定義されています。 従って、これら単独ではオブジェクトを生成できません。 つまり、グループ内のクラスで、 これらのインタフェースを継承する(実装する)クラスから間接的に利用されます。 Listグループには、ArrayListクラス、Vectorクラス、Stackクラス、LinkedListクラスなどが含まれます。特に、ArrayListクラスは非常に重宝しますので、繰り返し使いながら覚えておいてください。 Mapグループには、HashMapクラス、TreeMapクラス(赤黒木というバランスされた2分探索木でできています)、ConcurrentHashMapクラス、EnumMapクラスなどがあります。 この中では、HashMapクラスTreeMapクラス連想配列の実現に重宝しますので、これらも要チェックしておいいてください。 Setグループは、いわゆるデータを集合として扱うクラスを含み、たとえば、 HashSetクラス、LinkedHashSetクラス、EnumSetクラス、TreeSetクラス(赤黒木で実装されている)などがあります。上記のうち、ArrayListやVectorクラスのオブジェクトは配列のインデックスと類似する方法でランダムにアクセスすることができます。 しかし、多くの場合は、順序が保証されておらず、Iteratorクラスを使ってループさせながら、保持するデータにアクセスする場合が多いです。

クラスの継承的な観点から見ると、以下のような表現が頻繁に利用されます。 いずれも、代入される側(左辺)が基底クラスあるいはインタフェースで、 右辺がそれを継承するクラスになっています。

List <String>wordList = new ArrayList<String>();

Map <String,Integer>map = new HashMap<String,Integer>();
Set <String> key = map.keySet();

Set <Integer> mySet = new TreeSet<Integer>();
なお、左辺側では<.. >の部分(総称型)はなくても問題ありません。 右辺は必須と覚えておいてください。 復習を兼ねて、上述のCollection型を用いた簡単なプログラムを紹介しておきます。
CollectionTest.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*
	プログラム6-1: CollectionTest.java
*/
import java.util.*;

public class CollectionTest {
        
    public static void main(String[] args){
        List <String>wordList = new ArrayList<String>();/* ArrayList作成 */
        wordList.add("Java");/* ArrayList要素追加 */
        wordList.add("ArrayList");/* ArrayList要素追加 */
        wordList.add("HashMap");/* ArrayList要素追加 */
        for (int i = 0 ; i < wordList.size(); i++)
            System.out.println(wordList.get(i));

        Map <String,Integer>map = new HashMap<String,Integer>();/* HashMap作成 */
        map.put("Java", 1);/* HashMap要素追加 */
        map.put("ArrayList", 2);/* HashMap要素追加 */
        map.put("HashMap", 3);/* HashMap要素追加 */
        Set <String> key = map.keySet();
        Iterator<Map.Entry<String,Integer>> iter = map.entrySet().iterator();
        while (iter.hasNext()){/* IteratorでHashMapをループ */
            Map.Entry<String,Integer> e = iter.next();
            System.out.println("Key: "+e.getKey()+" Value:"+e.getValue());
        }
        Set <Integer> mySet = new TreeSet<Integer> ();/* TreeSet作成 */
        mySet.add(2);/* HashSet要素追加 */
        mySet.add(13);/* HashSet要素追加 */
        mySet.add(29);/* HashSet要素追加 */
        mySet.add(7);/* HashSet要素追加 */
        mySet.add(5);/* HashSet要素追加 */
        mySet.add(3);/* HashSet要素追加 */
        mySet.add(11);/* HashSet要素追加 */
        Iterator<Integer> itr = mySet.iterator();
        while (itr.hasNext()){/* IteratorでTreeSetをループ */
            System.out.print(itr.next()+" ");
        }
        System.out.print("\n");
    }
}
実行結果は以下のようです。
$ javacu CollectionTest.java

$ javau CollectionTest
Java
ArrayList
HashMap
Key: Java Value:1
Key: HashMap Value:3
Key: ArrayList Value:2
2 3 5 7 11 13 29
Collection型のサンプルプログラムの実行結果

Java言語のGUI

GUI(Graphical User Interface)は、計算機とユーザのインタフェースとして ターミナル窓、ファイル、キーボードといった入出力形式の代わりに、 入力ではマウスを加え、出力ではテキストだけでなく 各種図形や画像などの描画、ウィンドウやメニューを含むフレームでの表現など、 よりわかりやすい直観的なインタフェースを与える仕組みです。 Java言語では、初期のバージョンから AWT(Abstract Window Toolkit)として、 いわゆる重量型(heavy weighted)のGUIのパッケージが提供されました。 重量型とは、プラットホームに依存した各種グラフィック機能を使っていること から、「見た目」(look-and-feel)がプラットホームごとに異なることを意味します。 実際、AWTを使ったGUIのメニュは、Windows, Linux, Mac OS Xなどで 見た目が全く異なるものでした。 一方、Java2以降のバージョンで、 Swing と呼ばれるGUIが導入されると、実際の ハードウェアとのインタフェースの重量型の部分だけでなく、 AWTの Container クラスを継承した JComponent などのクラスに代表される 軽量型(light weighted)のインタフェースが充実されました。 さらに、AWT内で定義される描画の対象もぐっと拡大し、 幾何学的な各種図形(三次ベジエ曲線)などを含む java.awt.geom パッケージや、 BufferedImage クラスに代表される画像用のパッケージ、 幾何図形や画像の アフィン変換テキストの編集処理機能フォントの定義機能など、 格段にグラフィックス機能が 充実しました。 画像処理に関しては、画像ファイルのI/O処理は Java4から更に充実し、 javax.imageioパッケージが導入されて、 ようやく入力から出力まで 画像処理を完結できる基本機能がそろいました。 また、AWTとSwingでできるGUIでは、キーボードと マウスのイベント処理が可能ですが、通常のPCではなく、 アンドロイドのタッチパネルのイベント処理GUIを含むJava言語は Googleから 提供されています。

Javaグラフィックスの基本とアプレット・ビューア

Java言語により単純な計算プログラムをローカルなマシンで走らすような場合を除けば、 通常下図に示すようなウィンドウと呼ばれる領域にGUIプログラムの実行結果を表示します。 Javaでのウィンドウは,左上に原点があり,横軸は右側が正, 縦軸は下向きが正となることが前提となっています。 ウィンドウを定義するためには、横幅、及び縦幅を整数値(たとえば横幅400、縦幅300)で与えるだけです。 原点は常に(0,0)の位置にあり、原点自体を指定する必要はありません。 素朴な疑問ですが、なぜJavaではこのような座標軸やウィンドウの設定法を採用したのでしょうか? まず、横軸は右が正、縦軸は下が正というのは、多くの画像データフォーマットにおいてデータを、 左上から横方向にデータを格納し、その方向が終わるとひとつ下の行に移動し、 またその行で横方向にデータを格納し、以下画像の最後の行まで繰り返すという格納方式に 準じているという点に着目すると、画像データを扱いやすいからと考えられます 。 ウィンドウのサイズを整数で与えるのは、ウィンドウ内のデータは画素(ピクセル)の集合であり、 「横軸の画素数」=「横軸のスクリーンの解像度」、「縦軸の画素数」=「縦軸のスクリーンの解像度」 という考えを自然に取り入れたものでしょう 。 ここで、「画素」とは、スクリーン上のドットに対応しているものと考えてください。 たとえば、1024x800のスクリーンサイズでディスプレイ装置を定義した場合、 画素数も1024x800個あるという意味です。

JavaのGUIで用いるウィンドウ領域と座標系


アプレットビューアで描画してみよう

Java AWTでの描画方法の例を示します。 Java2以前の標準的なJava VMに基づくJava言語でのウィンドウの描画に関しては, 主としてAWTパッケージに含まれる Graphicsクラス の中のメソッドを用いて行います。 後述するJavaプログラム例の中のimport java.awt.*;という文が, AWTパッケージを読み込む(import)という意味を表します。 非常に簡単な描画の例を示します。
LineDrawApplet.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
/* プログラム6-2 lineDrawApplet.java
	線画の簡単な例 */
import java.awt.*; /*java.awtパッケージを利用*/
import javax.swing.JApplet;/*javax.swing.JAppletクラスを利用*/

public class lineDrawApplet extends JApplet {/*JAppletクラスを継承*/
	public void paint(Graphics g){/*描画用のpaint()メソッドの定義 */
		Dimension d = getSize();/*アプレットのサイズ取得*/
		g.drawLine(0,0,d.width,d.height);/*(0,0)から(d.width,d.height)まで描画*/
	}
}
さて、上述のlineDrawAppletクラス自体は、通常のJava言語の コンパイラでコンパイルし、中間コードであるクラスファイルを生成できます。 このプログラムでは、Graphicsクラスのオブジェクト例として 線画を描画するものですが,描画を実際に行っているのはpaint()関数の中 のdrawLine()メソッドです。 なお、8行目のgetSize()関数は、JAppletの継承関係をたどっていくと 見つかる Componentクラス に含まれる関数です。 getSize()関数が返す Dimensionクラス もAWTに含まれるクラスです。 さて、1~9回までの資料で説明してきたJava言語でのプログラムでは、 あるクラスを作成したら、コンパイルで生成されたクラスファイルをmain関数 から呼び出して実行していました。 しかし、この例では、残念ながらmain関数から呼び出すことはできません。 理由は JAppletクラス を継承しているためです。 では、JAppletクラスは、どのようにすれば実行できるのでしょうか? このために、Javaをインストールすると、 appletviewer(アプレットビューア)というアプリケーションが 付随しています。 アプレットビューアに与えるファイルとは、以下のように、アプレットを表示するための タグ(<applet> <embed> <object>タグなど)を挿入したHTMLにほかなりません。
LineDrawApplet.html
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
<!-- 線を描画するアプレット (lineDrawApplet.html)
-->
<html lang="ja">
<head>
<meta http-equiv=Content-Type content="text/html; charset=utf-8">
<title>線を描画するアプレット</title>
<body bgcolor="888888">
<center>
<applet code=lineDrawApplet.class width="300" height="200">
Javaアプレットが動きません。ごめんなさい。
</applet>
<br/>
線を描画するアプレット
</center>
</body>
</html>
実行結果は以下のようなウィンドウがポップアップします。

アプレットビューアを実行したところ


これは、アプレットでしたが、ほぼ同じ結果をmainを含む関数で実現することも できます。ただし、JAppletクラスを継承する場合は、アプレットビューアや Webブラウザからしか結果を見れませんので、JPanelクラスの継承に 変更し、mainからJFrameクラスのオブジェクトを作り、フレーム内に パネルを配置することで、ほぼ同等の結果を得られます。 その場合、プログラム6-2は、たとえば以下のように修正することで アプレットではなく、Swingのアプリケーションとして実行することができます。
LineDrawPanel.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* プログラム6-3 lineDrawPanel.java
	線画のJPanelでの描画例 */
import java.awt.*; /*java.awtパッケージを利用*/
import javax.swing.*;/*javax.swingパッケージを利用*/

public class lineDrawPanel extends JPanel {/*JPanelクラスを継承*/
	public void paint(Graphics g){/*描画用のpaint()メソッド(paintComponent()も同様) */
		Dimension d = getSize();/*アプレットのサイズ取得*/
		g.drawLine(0,0,d.width,d.height);/*(0,0)から(d.width,d.height)まで描画*/
	}
	public static void main(String[] args){
		lineDrawPanel panel = new lineDrawPanel();
		JFrame application = new JFrame();/* JFrameクラスでWindowを準備 */
		application.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE);/* Windowを閉じると終了 */
		application.add( panel );/* JFrameにJPanelを加える */
		application.setSize( 500, 500 );/* JFrameの(初期)サイズ */
		application.setVisible( true );/* Windowを表示 */
	}
}
上記のプログラム例では、メニュ類は一切ないため 実行結果は以下のようなシンプルなものとなります。

Swing/JFrameでアプレットビューアを同じ直線を描画した様子


GUIコンポーネント

Swingには、いろいろなGUIコンポーネントが用意されています。 以下は代表的なSwingのGUIを構成するコンポーネントです。 なお、画像処理、幾何図形描画、フォントの設定などは、 GUIに直接含まれないため、ここには含めていません。 具体的なプログラム例はイベント処理の項で述べます。

イベントとリスナー

前節で述べた各種のGUIコンポーネントに対して、 ボタンやリストなどが、適当な方法(たとえばマウスのクリック) で選択できるための設定(リスナー)や、 選択された時に行う処理(イベント処理)を 行うことで、ユーザインタフェースを構築するのが 代表的な使用方法になります。 イベントには、AWTのことから利用できたものとSwing の登場以降追加されたものがあります。 しかし、AWTイベントとリスナーの仕組みを知っておけば Swingで追加されたものも、ほぼ同様の手順で プログラムから呼び出せますので、ここでは AWTEventクラスとAWTイベントに特有のリスナーを 紹介します。 なお、AWTEventクラスの多くは、論理的なイベントですが 入力イベントに関しては、キーボードとマウスに 関するイベントとリスナーが用意されています。 以下の図は、java.awt.eventパッケージにある イベントクラスを示します。

AWTEventクラスの主たるイベント


また、以下は、 AWTEventクラスに関するリスナーとそのリスナーを設定するために実装する必要があるメソッド(一般に1個以上のメソッドが用意されています)の対応を示しています。 リスナー自体はインタフェースとして定義されていますので、 これらのメソッドは必ず実装する必要があります。

EventListenerクラスの主たるリスナー


イベントとリスナー事例

以下では、具体的な事例を幾つか紹介します。

JPEG画像を表示し、キーボードからESCAPEが押されたら、窓を閉じ終了するプログラム

この事例では、 JFrameクラス を継承し、KeyListenerインタフェースを実装する プログラム例です。 赤字の部分がAWTEventクラスの例として KeyEvent とそのリスナーである KeyListener で実装する必要のある3つの関数を設定している部分です。
myImageViewer.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/* プログラム6-4 JPEG画像表示 
*/
import java.io.File;/* ファイル */
import java.awt.Image;/* Imageクラス */
import java.awt.Graphics;/* Graphicsクラス */
import java.awt.image.BufferedImage;/* BufferedImageクラス */
import java.awt.event.KeyListener;/* キーボードからのイベントのリスナー */
import java.awt.event.KeyEvent;/* キーボードからのイベント */
import javax.swing.JFrame;/* JFrameクラス */
import javax.swing.JFileChooser;/* ファイル選択用 */
import javax.swing.JOptionPane;/* 情報表示 */
import javax.imageio.ImageIO;/* 画像のI/Oクラス */

public class myImageViewer extends JFrame implements KeyListener {

	protected Image img = null;/* 描画用Imageクラス:paint()から利用 */

	public myImageViewer(String title){/* コンストラクタ */
		setTitle(title);/* Swing窓に表示するタイトル */
		getImageFile(); /* 画像の選択と読込 */
		addKeyListener(this);/* キーボードのリスナーを設定 */
		setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE);/* Windowを閉じると終了 */
	}
	public void quit(){ System.exit(0); }
	/* 以下の3つの関数はキーボードのイベントを取得するために宣言が必要 */
	public void keyPressed(KeyEvent e){/* キーが押された時のイベント */
		if (e.getKeyCode()==KeyEvent.VK_ESCAPE) quit();/* VK_ESCAPEはキーボードのEscapeボタン */
	}
	public void keyReleased(KeyEvent e){}/* キーがリリースされたとき */
	public void keyTyped(KeyEvent e){}/* キーがタイプされたとき */

	private void getImageFile(){/* 画像ファイルの選択と読込 */
		JFileChooser fileChooser = new JFileChooser();/* ファイルの選択 */
		jpegFilter jf = new jpegFilter();/* フィルタ */
		fileChooser.setFileFilter(jf);/* フィルタの設定 */
		fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
		int result = fileChooser.showOpenDialog( this );/* ファイル選択窓の表示 */
		if ( result == JFileChooser.CANCEL_OPTION)/* キャンセルのときは、やめる */
			quit();
		File fileName = fileChooser.getSelectedFile(); /* ファイル名の取得 */
		if (( fileName == null) || (fileName.getName().equals("")) || (fileName == null) ){
			JOptionPane.showMessageDialog( this, "妥当なJPEGファイル名ではありません",
			"妥当なJPEG画像ファイル名ではありません", JOptionPane.ERROR_MESSAGE);
		}
		String pathFile = fileName.getAbsolutePath(); /* パス名の取得 */

		try {
			File bf = new File(pathFile);/* ファイル */
			img = ImageIO.read(bf);/* ImageIOクラスで読込む */
			BufferedImage bimg = (BufferedImage)img;/* BufferedImageクラスにキャスト */
			int width = bimg.getWidth();	/* 画像のサイズ(横幅)取得 */
			int height = bimg.getHeight();	/* 画像のサイズ(縦幅)取得 */
			setSize(width, height);/* 画像に応じてサイズを設定 */
		}
		catch (Exception e){ e.printStackTrace();}
	}
	public void paint(Graphics g){/* 描画ではpaintまたはpaintComponentが必ず呼び出される */
		g.drawImage(img,0,0,this);/* 画像の描画 */
	}
	static class jpegFilter extends javax.swing.filechooser.FileFilter{
		public boolean accept(File fileObj){
			String extension = "";
			if (fileObj.getPath().lastIndexOf('.') > 0)
				extension = fileObj.getPath().substring(
					fileObj.getPath().lastIndexOf('.')+1).toLowerCase();
			if (extension != "")
				return extension.equals("jpg");/* 拡張子がjpgのものだけ選択 */
			else 
				return fileObj.isDirectory();
		}
		public String getDescription(){
			return "JPEG Files (*.jpg)";
		}
	}

	public static void main(String[] args){
		myImageViewer t = new myImageViewer("JPEG Image Viewer");
		t.setVisible(true);/* 表示 */
	}
}
上記のプログラムでは、Swingから導入されたJFileChooserを利用しています。 これを実行すると、以下のようなファイル選択用のウィンドウがポップアップします。

Swing/JFileChooserで画像ファイルを選択している様子


この後、画像Gikadai.jpgを選択すると 最終的には、以下のようなウィンドウ(JFrame)に画像が表示されます。

選択された画像をSwing/JFrameで表示した様子

マウスのドラッグでお絵かきするプログラム

次に、マウスのイベントを処理する例として、 MouseMotionListenerという リスナーを用いたドラッグ・イベントと、マウスの位置に(黒色の)円を塗りつぶすことで 結果として、「お絵かき」するのと同じような効果を得ることができる例です。 この例は、もとは、DeitelのJava How To Program(第8版)にある例を ArrayListで拡張したものです。ArrayListが保持するのは、 MouseEventから 得られるマウスの位置を表す Pointクラスのオブジェクトです。
PaintPanel.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* プログラム6-5 PaintPanel.java  (Deitelの教科書を参考)*/
import java.awt.Point;/* 点:描画用 */
import java.awt.Graphics;/* Graphicsクラス */
import java.awt.event.MouseEvent;/* MouseEvent */
import java.awt.event.MouseMotionListener;/* MouseMotionListener */
import javax.swing.JPanel;/* JPanelクラス */
import java.util.ArrayList;/* ArrayListクラス */

public class PaintPanel extends JPanel implements MouseMotionListener {

	final int RADIUS = 10;/* 点を円で塗りつぶすときの半径 */
	private int pointCount = 0; /* 点の総数 */
	private ArrayList<Point> points = new ArrayList<Point>();

	public void mouseDragged(MouseEvent event){
		points.add(event.getPoint());/* ArrayListにPointを追加 */
		pointCount++; /* = points.size()に等しい */
		repaint(); /* 再描画 */
	}
	public void mouseMoved(MouseEvent event){}

	public PaintPanel() {/* コンストラクタ */
		addMouseMotionListener(this);/* マウスの動きをとるリスナー */
	}
	/* RADIUS x RADIUSの円を指定された位置に描画 */
	public void paintComponent( Graphics g ){
		/* ArrayList中の全部の点を描画 */
		for ( int i = 0; i < pointCount; i++ ){
			Point p = points.get(i);/* ArrayListのi番目の点 */
			int x = p.x; /* X座標 */
			int y = p.y; /* Y座標 */
			g.fillOval( x, y, RADIUS, RADIUS );/* 楕円(実際は円)の描画 */
   		}
  }
}
mainを含むクラスは以下の通りです。
Painter.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
/* プログラム6-6 Painter.java */
import java.awt.BorderLayout;/* レイアウト */
import javax.swing.JFrame;/* JFrameクラス */
import javax.swing.JLabel;/* JLabelクラス */

public class Painter {
	public static void main( String[] args ){ 
		JFrame app = new JFrame( "簡易ペイント・プログラム" );	/* JFrameの生成 */
		PaintPanel paintPanel = new PaintPanel(); /* PaintPanelオブジェクト生成 */
		app.add( paintPanel, BorderLayout.CENTER ); /* 中央に配置 */

		/* JLabelでラベルを生成し、レイアウトの南部に配置 */
		app.add( new JLabel( "マウスのドラッグで描画" ), BorderLayout.SOUTH );
		app.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );/* Window closeで終了 */
		app.setSize( 600, 600 ); /* フレームのサイズ */
		app.setVisible( true ); /* JFrameを表示 */
	}
}
実行例は以下のようです。

Swing/JPanelにマウスドラッグとfillOvalで描画している様子


MouseListenerMouseMotionListener両方発生させるプログラム

ここでは、2つのマウスイベントを同時に発生させる例を述べます。 これは、2つのインタフェースを同時に実装するクラスの例となっており、 一種の多重継承クラスを定義する事例となっています。 まず、JPanel上でこれら2つのイベントを取得し、同時に、 マウスの現在位置をJLabelに書き出すためのMouseHandler.javaを示します。
MouseHandler.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* プログラム6-7 MouseHandler.java(マウスのイベント処理) */
/* 一種の多重継承を複数のインタフェースを実装することで実現する*/
import java.awt.Color;/* 色(Color)クラス */
import java.awt.event.MouseListener;/* マウスのボタン関連リスナー */
import java.awt.event.MouseMotionListener;/* マウスの動きリスナー */
import java.awt.event.MouseEvent;/* マウスイベント */
import javax.swing.JLabel;/* JLabeleクラス */
import javax.swing.JPanel;/* JPanelクラス */

public class MouseHandler implements MouseListener, MouseMotionListener {
	/* MouseListenerとMouseMotionListener両方のイベント取得*/
	JPanel mousePanel = null;/* JPanelクラスの変数定義 */
	JLabel statusBar = null;/* JLabelクラスの変数定義 */
	public MouseHandler(JPanel mousePanel, JLabel statusBar){
		this.mousePanel = mousePanel;/* JPanalクラスの変数の初期化 */
		this.statusBar = statusBar;/* JLabelクラスの変数の初期化 */
	}
	/* 以下5つのメソッドはMouseListenerイベントに対応:MouseListenerのすべての抽象関数を定義する */
	public void mouseClicked( MouseEvent event ){/* クリック(押した後すぐにリリース) */
		statusBar.setText(String.format( "クリック [%d, %d]", event.getX(), event.getY() ) );
	}
	public void mousePressed( MouseEvent event ){/* 押された */
		statusBar.setText(String.format( "プレス [%d, %d]", event.getX(), event.getY() ) );
	}
	public void mouseReleased( MouseEvent event ){/* リリースされた */
		statusBar.setText(String.format( "リリース  [%d, %d]", event.getX(), event.getY() ) );
	}
	public void mouseEntered( MouseEvent event ){/* マウスが領域に入った */
		statusBar.setText(String.format( "領域入り  [%d, %d]", event.getX(), event.getY() ) );
		 mousePanel.setBackground( Color.CYAN );/* 色をシアン(水色)にする */
	}
	public void mouseExited( MouseEvent event ){/* マウスが領域を出た */
		statusBar.setText( "領域アウト" );
		mousePanel.setBackground( Color.WHITE );/* 白色に戻す */
	}

	/* 以下2つのメソッドはMouseMotionListenerイベントに対応:MouseMoutionListenerのすべての抽象関数を定義する */
	public void mouseDragged( MouseEvent event ){/* マウスのドラッグ */
		statusBar.setText(String.format( "ドラッグ [%d, %d]", event.getX(), event.getY() ) );
	}
	public void mouseMoved( MouseEvent event ){/* マウスの移動 */
		statusBar.setText(String.format( "移動 [%d, %d]", event.getX(), event.getY() ) );
	}
}
これを、呼び出すJFrameやmainを含むプログラム例を以下に示します。
MouseTrackerFrame.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* プログラム6-8 MouseTrackerFrame.java (マウスのイベント処理)*/
import java.awt.Color;/* 色(Color)クラス */
import java.awt.BorderLayout;/* レイアウト */
import java.awt.event.MouseListener;/* マウスのボタン関連リスナー */
import java.awt.event.MouseMotionListener;/* マウスの動きリスナー */
import java.awt.event.MouseEvent;/* マウスイベント */
import javax.swing.JFrame;/* JFrameクラス */
import javax.swing.JLabel;/* JLabeleクラス */
import javax.swing.JPanel;/* JPanel クラス */

public class MouseTrackerFrame extends JFrame {

	private JPanel mousePanel; /* マウスイベントを起こすパネル */
	private JLabel statusBar; /* イベント情報表示ラベル */

	public MouseTrackerFrame() { /* コンストラクタ:GUIの設定とマウスイベント登録 */
		super( "マウスイベントのデモ" );
		mousePanel = new JPanel(); /* パネルの生成 */
		mousePanel.setBackground( Color.WHITE ); /* 背景は白色 */
		add( mousePanel, BorderLayout.CENTER ); /* パネルをJFrameに追加 */
		statusBar = new JLabel( "マウスがJPanelの外にあります" );/* ステータスバー */ 
		add( statusBar, BorderLayout.SOUTH ); /* ステータスバーはウィンドウの最下位置に*/

		/* MouseHandlerを呼び出す */
		MouseHandler handler = new MouseHandler(mousePanel, statusBar); 
		mousePanel.addMouseListener( handler ); /* MouseListenerを登録*/
		mousePanel.addMouseMotionListener( handler ); /* MouseMOtionListenerを登録 */
	}
	public static void main( String[] args ){ 
		MouseTrackerFrame mouseTrackerFrame = new MouseTrackerFrame(); 
		mouseTrackerFrame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
		mouseTrackerFrame.setSize( 400, 200 ); /* JFrameのサイズ */
		mouseTrackerFrame.setVisible( true ); /* JFrameの表示 */
	}
}
以下の実行例では、最初がマウスがJPanelの外にあるとき、次がJPanel内を動いているときです。
マウスがJPanelの外にあったときJLabelで通知する様子
マウスJPanel内にあったとき、ステータスバーに位置を表示する様子

Java2D

Java 2D APIは、Java2以降利用できるようになりました。 パッケージとして、java.awt, java.awt.image, java.awt.color, java.awt.font, java.awt.geom, java.awt.print, java.awt.image.renderableなどが 関連します。 Java2DのAPIで描画を担当するのは、 Graphics2Dクラスです。 ここでは、クラスの継承で紹介した2次元図形の例を示します。

Java2Dを使った2次元図形のクラス継承例

2次元図形の抽象クラスShape2Dを用いたクラスの継承事例を以前紹介しました。 以前は出力がPostScriptでしたが、ここでは、これをSwing上のJava GUIに出力します。 以下で示すプログラムは、Java2Dを用いています。全体の関係は以下の図のようになります。

Shep2D抽象クラスとその派生クラスを用いたクラスの継承をSwingで表現したクラス間の相関関係のイラスト


以降、この図に現れるクラスを定義していきます。
Shape2D.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
	プログラム6-9 Shape2Dクラス:抽象クラス
*/
import java.awt.Graphics;/* Graphicsクラス */
import java.io.PrintStream;/* PrintStreamクラス */

public abstract class Shape2D {/* 2次元形状を現す抽象クラス */
    private Color c;/* 0.0 <= r,g,b <= 1.0 */
    public Color getColor() { return c;}
    public void setColor(Color c){/* 3原色をセットするメンバ関数 */
        this.c = c; 
    }
    private String name; /* 形状の名前  */
    private String getName(){ return name; }/* 形状の名前を返す:抽象関数ではない */
    protected void setName(String name) { this.name = name; } /* 形状の名前を設定する */
    void printHead(PrintStream cout){
        cout.printf( "%% %s 面積 = %5.3f\n", getName(), area());
        cout.printf( "%% %s 周囲長 = %5.3f\n", getName(), perimeter());
        cout.printf( "%1.1f %1.1f %1.1f setrgbcolor\n", c.getR(),c.getG(),c.getB());
    }
    /* 以下、抽象関数(privateにはできないので注意!) */
    public abstract double area(); /* 面積を求める抽象メソッド */
    public abstract double perimeter(); /* 周囲長を求める抽象メソッド */
    protected abstract void psPrint(PrintStream cout);/* PostScript形式でプリントする抽象関数 */
    public abstract void swingPaint(Graphics g);/* Swingで描画 */
}
色を表すColorクラスは以下の通りです。注意すべきは、java.awt.Colorにも同じ名前の クラスがあるため、後者はフルネームで使うこととします。
Color.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
	プログラム6-10 Colorクラス
*/
public class Color { /* RGB三原色での色クラス */
    private double r;/* 赤色成分:0-255 */
    private double g;/* 緑色成分:0-255 */
    private double b;/* 青色成分:0-255 */
    public void setColor(double r,double g,double b){/* 色をセット */
        this.r=r; this.g=g; this.b=b;
    }
    public void setColor(Color c){/* 色をセット:オーバーロード */
        this.r=c.r; this.g=c.g; this.b=c.b;
    }
    public double getR(){/* 赤色成分を返す関数*/    
        return this.r;    
    }
    public double getG(){/* 緑色成分を返す関数*/
        return this.g;
    }
    public double getB(){/* 青色成分を返す関数*/
        return this.b;
    }
}
次に、楕円クラスを示します。
Circle.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
	プログラム6-11 Circleクラス:派生クラス
*/
import java.io.PrintStream;
import java.awt.Graphics;/* Graphicsクラス */
import java.awt.Graphics2D;/* Graphics2Dクラス */
import java.awt.BasicStroke;/* BasicStrokeクラス */
import java.awt.geom.Ellipse2D;/* Ellipse2Dクラス */

public class Circle extends Shape2D {
    private double radius; /* 半径 */
    private Coord2 v; /* center of ellipse 楕円の中心座標 */

    public Circle(){ setName("円"); }
    public Circle(Coord2 v, double radius, Color c){
        this();// 無引数のコンストラクタを呼び出す
        this.v = v;// 中心座標
        this.radius = radius;// 半径
        super.setColor(c);
    }
    public double area(){ /* 面積を求める抽象メソッド */
        return Math.PI*radius*radius;// 円の面積
    }
    public double perimeter(){ /* 周囲長を求める抽象メソッド */
				return 2*Math.PI *radius;
    }
    public void psPrint(){/* PostScriptでの楕円の記述 */
        printHead(cout);
	cout.println( "newpath" );
	cout.printf("%5.3f %5.3f %5.3f 0 360 arc\n", v.getX(), v.getY(), radius );
	cout.println( "stroke" );
    }
    public void swingPaint(Graphics g){/* Swingで楕円を描画 */
        Graphics2D g2d = (Graphics2D) g;/* Graphics2Dを得る */
        Color c = getColor();/* 親クラスからColorデータをゲット */
        java.awt.Color colorW = 
            new java.awt.Color((float)c.getR(), (float)c.getG(), (float)c.getB());
        g2d.setColor(colorW);/* 色の設定 */
        g2d.setStroke( new BasicStroke( 2.0F ) ); /* 線幅 */
        g2d.draw( new Ellipse2D.Double( v.getX(), v.getY(), radius, radius));/* 描画 */
    }
}
三角形クラスはたとえば以下のように定義できます。
Triangle.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/* プログラム6-12: 三角形クラス*/
import java.awt.Graphics;/* Graphicsクラス */
import java.awt.Graphics2D;/* Graphics2Dクラス */
import java.awt.geom.GeneralPath;/* GeneralPathクラス */
import java.io.PrintStream;
import java.awt.BasicStroke;/* BasicStrokeクラス */

public class Triangle extends Shape2D {

  private Coord2 v1, v2, v3;

  public Triangle(){ setName("三角形"); }/* コンストラクタ(無引数)*/
    public Triangle(Coord2 v1, Coord2 v2, Coord2 v3, Color c){/* コンストラクタ */
      this();/* 無引数のコンストラクタ呼び出し */
      this.v1 = v1;/* 頂点1 */
      this.v2 = v2;/* 頂点2 */
      this.v3 = v3;/* 頂点3 */
      super.setColor(c);
    }
    public Coord2 getV1(){ return v1; }/* 第一座標を返すメンバ関数 */
    public Coord2 getV2(){ return v2; }/* 第二座標を返すメンバ関数 */
    public Coord2 getV3(){ return v3; }/* 第三座標を返すメンバ関数 */
    public double perimeter(){/* 三角形の周囲長を返すメンバ関数:オーバーライド */
      double t1, t2, t3;
      t1 = Coord2.distance(v1, v2);/* v1-v2の距離 */
      t2 = Coord2.distance(v2, v3);/* v2-v3の距離 */
      t3 = Coord2.distance(v3, v1);/* v3-v1の距離 */
      return (t1+t2+t3); /* 3辺の距離の和 */
    }
    public double area(){/* 三角形の面積を返すメンバ関数:オーバーライド */ 
      double x1 = v1.getX();/* X1座標 */
      double x2 = v2.getX();/* X2座標 */
      double x3 = v3.getX();/* X3座標 */
      double y1 = v1.getY();/* Y1座標 */
      double y2 = v2.getY();/* Y2座標 */
      double y3 = v3.getY();/* Y3座標 */
      double t = (x2-x1)*(y3-y1) - (x3-x1)*(y2-y1);/* 一種の外積 */
      t = 0.5 * Math.abs(t);/* 外積の絶対値の1/2が三角形の面積 */
      return t; 
    }
    public void psPrint(PrintStream cout){
      /* 三角形のデータをPostScriptで書き出すメンバ関数:オーバーライド */
      printHead(cout);
      cout.println( "newpath" );/* newpathコマンド:詳細はhttp://tutorial.jp/graph/ps/psman.pdf等を参照 */
      cout.printf( "%5.3f %5.3f moveto\n", v1.getX(), v1.getY() );/* ペンを移動 */
      cout.printf( "%5.3f %5.3f lineto\n", v2.getX(), v2.getY() );/* ペンで次の座標まで線を描く */
      cout.printf( "%5.3f %5.3f lineto\n", v3.getX(), v3.getY() );/* ペンで次の座標まで線を描く */
      cout.println( "closepath" );/* closepathコマンド(一筆書きを閉じる) */
      cout.println( "stroke" );/* strokeコマンド(描画する) */
    }
    public void swingPaint(Graphics g){/* Swingで三角形を描画 */
      Graphics2D g2d = (Graphics2D) g;/* Graphics2Dを得る */
      Color c = getColor();/* 親クラスからColorデータをゲット */
      java.awt.Color colorW = 
          new java.awt.Color((float)c.getR(), (float)c.getG(), (float)c.getB());
      g2d.setColor(colorW);/* 色の設定 */
      g2d.setStroke( new BasicStroke( 2.0F ) ); /* 線幅 */
      GeneralPath gp = new GeneralPath();/* GeneralPathオブジェクト生成 */
      gp.moveTo(v1.getX(), v1.getY());/* 第一の点に移動 */
      gp.lineTo(v2.getX(), v2.getY());/* 第二の点まで線を引く */
      gp.lineTo(v3.getX(), v3.getY());/* 第三の点まで線を引く */
      gp.closePath();/* Pathを閉じる */
      g2d.draw( gp );/* GeneralPathを実際に描画(結果として三角形を描画) */
    }
}
長方形クラスはたとえば以下のように定義できます。
Rectangle.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
	プログラム6-13 長方形クラス Rectangle.java
 */
import java.io.PrintStream;
import java.awt.Graphics;/* Graphicsクラス */
import java.awt.Graphics2D;/* Graphics2Dクラス */
import java.awt.BasicStroke;/* BasicStrokeクラス */
import java.awt.geom.GeneralPath;/* GeneralPathクラス */
import java.awt.geom.Ellipse2D;/* Ellipse2Dクラス */

public class Rectangle extends Shape2D{

    private Coord2 v1, v2; /* 長方形の左下と右上座標 */
    private double len1, len2;

    public Rectangle(){ setName("長方形"); }/* コンストラクタ(無引数)*/
    public Rectangle(Coord2 v1, Coord2 v2, Color c){
        this();/* 無引数のコンストラクタ呼び出し */
        this.v1 = v1;/* 頂点(left-bottom) */
        this.v2 = v2;/* 頂点(right-top) */
        super.setColor(c);
        len1 = Math.abs(v1.getX()-v2.getX());/*1辺の長さ */
        len2 = Math.abs(v1.getY()-v2.getY());/*もう1辺の長さ */
    }
    public double area() {//面積を返す
        return len1*len2;
    }

    public double perimeter() {//周囲長を返す
        return (2*(len1+len2));
    }

    public void psPrint(PrintStream cout) {
        printHead(cout);
        cout.println( "newpath" );
        cout.printf("%5.3f %5.3f moveto\n", v1.getX(), v1.getY());/* ペンを移動 */
        cout.printf("%5.3f %5.3f lineto\n", v2.getX(), v1.getY());/* ペンで次の座標まで線を描く */
        cout.printf("%5.3f %5.3f lineto\n", v2.getX(), v2.getY());/* ペンで次の座標まで線を描く */
        cout.printf("%5.3f %5.3f lineto\n", v1.getX(), v2.getY());/* ペンで次の座標まで線を描く */
        cout.println("closepath");/* closepathコマンド(一筆書きを閉じる) */
        cout.println( "stroke" );/* strokeコマンド(描画する) */
    }
    public void swingPaint(Graphics g){/* Swingで三角形を描画 */
        Graphics2D g2d = (Graphics2D) g;/* Graphics2Dを得る */
        Color c = getColor();/* 親クラスからColorデータをゲット */
        java.awt.Color colorW = 
            new java.awt.Color((float)c.getR(), (float)c.getG(), (float)c.getB());
        g2d.setColor(colorW);/* 色の設定 */
        g2d.setStroke( new BasicStroke( 2.0F ) ); /* 線幅 */
        GeneralPath gp = new GeneralPath();/* GeneralPathオブジェクト生成 */
        gp.moveTo(v1.getX(), v1.getY());/* 第一の点に移動 */
        gp.lineTo(v2.getX(), v1.getY());/* 第二の点まで線を引く */
        gp.lineTo(v2.getX(), v2.getY());/* 第三の点まで線を引く */
        gp.lineTo(v1.getX(), v2.getY());/* 第四の点まで線を引く */
        gp.closePath();/* Pathを閉じる */
        g2d.draw( gp );/* GeneralPathを実際に描画(結果として三角形を描画) */
    }
}
2次元図形を乱数で生成するクラスを以下のように作成するとします。
ShapeGeneration.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/*
	プログラム6-14 ShapeGeneration.java(図形を発生)
*/
import java.util.Random;/* 乱数 */
import java.util.Date;/* 日付 */
import java.io.PrintStream;/* 出力ストリーム */
import java.util.ArrayList;/* 動的配列リスト(システムクラス)*/

public class ShapeGeneration {

    final static double XRANGE = 600.0;/* X方向のキャンバスサイズ */
    final static double YRANGE = 800.0;/* Y方向のキャンバスサイズ */
    final static double RADIUS = 300.0;/* 半径の最大値 */
    final static double LENGTH = 300.0;/* 半径の最大値 */

    Color c; /* 色クラスの変数 */
    Coord2 v1, v2, v3;/* 2次元座標 */
    double radiusH, radiusV;/* 半径 */
    double length;/* 一辺の長さ */
    /* Shape2Dクラスを要素として持つ、動的な配列リストクラスを利用 */
    ArrayList<Shape2D> list = new ArrayList<Shape2D>();
    Shape2D shape = null;/* 抽象基底クラスの変数:宣言はOK */
    Date date = null;/* 日付変数 */
    double r,g,b;

    public ShapeGeneration(int n){
        date = new Date();/* 現在の日付(乱数の初期値用)*/
        Random rand = new Random(date.getTime());/* 乱数クラス */
        for (int j = 0 ; j < n ; j++){ 
            v1 = new Coord2(
                XRANGE * rand.nextDouble(), /* nextDouble()は0.0-1.0の乱数を発生 */
                YRANGE * rand.nextDouble()); 
            c = new Color();
            r = rand.nextDouble();/* 赤色 [0.0,1.0] */
            g = rand.nextDouble();/* 緑色 [0.0,1.0] */
            b = rand.nextDouble();/* 青色 [0.0,1.0] */
            c.setColor(r,g,b);
            switch ( j % 3 ){
                case 0:/* 三の倍数なら三角形を生成:triangle */
                    v2 = new Coord2(
                        XRANGE * rand.nextDouble(), 
                        YRANGE * rand.nextDouble()); 
                    v3 = new Coord2(
                        XRANGE * rand.nextDouble(), 
                        YRANGE * rand.nextDouble()); 
                    shape = new Triangle(v1, v2, v3, c);/* 派生クラス(三角形)のインスタンス生成*/ 
                    break;
                case 1:/* 3で割ったとき1余れば円を生成:circle */
                    radius = RADIUS * rand.nextDouble();/* [0.0-RADIUS]の乱数を発生 */
                    shape = new Circle(v1, radius, c); /* 派生クラス(円)のインスタンス生成*/ 
                    break;
                case 2:/* 3で割ったとき2余れば長方形生成:square */
                    v2 = new Coord2(
                        XRANGE * rand.nextDouble(), 
                        YRANGE * rand.nextDouble()); 
                    length = LENGTH * rand.nextDouble();
                    shape = new Rectangle(v1, v2, c); /* 派生クラス(長方形)のインスタンス生成*/ 
                    break;
            }
            list.add(shape);/* ArrayListにShape2Dクラスのデータを追加 */
        }
    }
    public ArrayList<Shape2D> getList(){ return list; }
    public void psOut(){
        /* 出力:System.outを利用 */
        PrintStream cout = System.out;
        cout.println( "%%!PS-Adobe-3.0" ); 
        cout.println( "%% 作者: 青野雅樹:01162069" );
        cout.println( "%% 日付: "+date.toString() );
        int count = 1;
        double area=0.0;/* 面積の総和用 */
        double per=0.0; /* 周囲長の総和用 */
        for (int i = 0 ; i < list.size(); i++ ){/* ArrayListのサイズ回ループ */
            shape = list.get(i);/* i-番目の要素を取り出す */
            cout.println( "%%" + count + "番目の図形" );
            shape.psPrint(cout);/* 派生クラスに応じたpsPrintメソッドが呼ばれる */
            area += shape.area();/* 面積を加算 */
            per += shape.perimeter();/* 周囲長を加算 */
            count++;
        }
        cout.println( "% 総面積 = "+area);
        cout.println( "% 総長 = "+per);
        cout.println( "showpage" );
    }
}
SwingでのJPanelを使った描画プログラムは、たとえば以下のように実装できます。
ShapeJPanel.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* プログラム6-15 ShapesJPanel.java, Java2Dの図形描画例 */
import java.awt.Graphics;/* Graphicsクラス */
import javax.swing.JFrame;/* JFrameクラス */
import javax.swing.JPanel;/* JPanelクラス */
import java.util.ArrayList;/* 動的配列リスト(システムクラス)*/
import java.awt.event.KeyListener;/* キーボードからのイベントのリスナー */
import java.awt.event.KeyEvent;/* キーボードからのイベント */

public class ShapeJPanel extends JPanel implements KeyListener  {/* Java 2D API */

    ArrayList<Shape2D> list = null;
    int numShapes = 0;
    JFrame frame = null;
    Shape2D shape = null;/* 抽象基底クラスの変数:宣言はOK */

    /* コンストラクタ */
    public ShapeJPanel(JFrame frame, ArrayList<Shape2D> list, int numShapes){
        this.list = list;
        this.numShapes = numShapes;
        frame.addKeyListener(this);/* キーボードのリスナーを設定 */
    }
    public void keyPressed(KeyEvent e){/* キーが押された時のイベント */
        if (e.getKeyCode()==KeyEvent.VK_PAGE_DOWN) {
            list.clear();
            ShapeGeneration sg = new ShapeGeneration(numShapes);
            list = sg.getList();
            repaint();
        }
        if (e.getKeyCode()==KeyEvent.VK_ESCAPE) quit();
    }
    public void keyReleased(KeyEvent e){}/* キーがリリースされたとき */
    public void keyTyped(KeyEvent e){}/* キーがタイプされたとき */
    public void quit(){ System.exit(0); }
    public void paintComponent( Graphics g ){/* 描画 */
        super.paintComponent( g ); /* スーパークラスのpaintComponent */
        for (int i = 0 ; i < list.size(); i++ ){/* ArrayListのサイズ回ループ */
            shape = list.get(i);/* i-番目の要素を取り出す */
            shape.swingPaint( g );/* 派生クラスに応じたpsPrintメソッドが呼ばれる */
        }
   }
}
main関数とSwingでのJFrameを使った全体のウィンドウ制御プログラムは、以下のように実装できます。
ShapeMain.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
	プログラム 6-16 ShapeMain.java(メインプログラム)
*/
import javax.swing.JFrame; /* JFrameクラス */

public class ShapeMain {
    public static void main(String[] args){

        if (args.length < 1){/* 引数が与えられることを確認する*/
            System.err.println("java ShapeMain 数(3の倍数)");
            System.exit(0);
        }
        int n = Integer.valueOf(args[0]).intValue();/* 発生する図形総数:変動可 */
        /* 引数のチェック */
        if (n < 21 || n > 90 || (n%3 != 0)){
            System.err.println("図形総数は[21-90]の間の3の倍数にしてください");
            System.exit(0);
        }
        ShapeGeneration sg = new ShapeGeneration(n);
        sg.psOut();/* PostScriptへの出力 */
        JFrame frame = new JFrame("Swingで二次元図形を描画: 青野雅樹[01162069]");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        ShapeJPanel sjp = new ShapeJPanel(frame, sg.getList(), n);/* JPanelへの出力 */
        frame.add( sjp );
        frame.setSize((int)ShapeGeneration.XRANGE, (int)ShapeGeneration.YRANGE);
        frame.setVisible(true);
    }
}
実行例は以下のとおりです。

Swingを使ったShape2Dの実行例


アニメーション

Swing(+AWT)でアニメーションを行う例を示します。 正確に言うと、ここでのアニメーションとは、 スプライトアニメーションと呼ばれる種類のものです。 スプライトアニメーションでは、アニメーションのシーンの中に、 図形要素(以下の例では円)を用意し、 フレームごとに各図形要素の位置を計算しオフスクリーンバッファと、 フロントスクリーンバッファを利用して、 片方(通常オフスクリーン)に描画しながら、 もう片方と描画時に表示するスクリーン(画像空間)を高速に切り替えることで、 あたかも図形要素(スプライト)が動いてみえるように表示する技術をいいます。 なお、2つのスクリーンバッファを利用して切り替えることから、 ダブルバッファリングとも呼ばれることがあります。 また、多くの場合、マルチスレッド(あるいはシングルスレッド)プログラミングと併用します。 実際、スレッドのスタートのタイミングで図形の描画とオフスクリーンバッファの切り替えを開始することが多いです。 この際、途中でアニメーションを中断するには、たとえば、キーボード などのイベントを設定しておくことで実行します。 以下のプログラムでは、実行中にキーボードのSキーでStop(中断)し、 Rキーで、Resume(再開)するように設定しています。 また、Escapeキーでプログラム全体を終了するようにしています。 基本的な流れは以下のようです。

Java/AWT(Swing)でアニメーションの流れ
SmoothBouncingCircleFrame.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
	プログラム6-17 (cf. Java Example in a Nutshell):ボールのアニメーション
 */
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import java.awt.event.KeyListener;/* キーボードからのイベントのリスナー */
import java.awt.event.KeyEvent;/* キーボードからのイベント */

public class SmoothBouncingCircleFrame extends JFrame implements Runnable, KeyListener {
    int x = 150, y = 50, r = 50;  // 初期位置と円の半径
    int saveX, saveY;             // 現在位置のセーブ用
    int dx = 11, dy = 7;          // 円の速度ベクトル
    int saveDx, saveDy;           // 現在の進行方向ベクトルのセーブ用
    int sleepTime = 10;           // 休止時間(ミリ秒)
   // オフスクリーン画像用のデータ
    Thread thread = null;/* スレッド用データ */
    Thread saveThread = null;/* セーブスレッド用データ */
    Image offScreenImage = null;/* 前景オフスクリーン用データ */
    Image backGroundImage = null;/* 背景オフスクリーン用データ */
    Graphics backG = null;/* 背景グラフィクス */
    Graphics offG = null;/* 前景グラフィクス */
    Graphics saveOffG = null;/* 前景グラフィクスのセーブ(再開で使用) */
    Graphics2D offG2 = null;/* 前景グラフィクスGraphics2D */
    private int w; // 横幅
    private int h; // 縦幅
    java.awt.Color backColor = new java.awt.Color(1.0f,1.0f,1.0f);/*背景色 */
    float R = 1.0f, G = 0.0f, B = 0.0f;
    java.awt.Color ballColor = new java.awt.Color(R,G,B);/*ボールの色;赤色 */
    java.awt.Color ballColor2 = new java.awt.Color(Math.max((int)(1.25*R),255),
        Math.max((int)(1.25*G),255),
        Math.max((int)(1.25*B),255));/*ボールの色2 */

    public SmoothBouncingCircleFrame(String name){ super(name);}
    public void quit(){ System.exit(0); }
    /* 以下の3つの関数はキーボードのイベントを取得するために宣言が必要 */
    public void keyPressed(KeyEvent e){/* キーが押された時のイベント */
      if (e.getKeyCode()==KeyEvent.VK_ESCAPE) quit();/* VK_ESCAPEはキーボードのEscapeボタン */
      else if (e.getKeyCode()==KeyEvent.VK_S) {/* ストップ */
        stop();
      }
      else if (e.getKeyCode()==KeyEvent.VK_R) {/* Rキー:リズーム(再開) */
        resume();
      }
    }
    public void keyReleased(KeyEvent e){}/* キーがリリースされたとき */
    public void keyTyped(KeyEvent e){}/* キーがタイプされたとき */

    public void init(){
      Dimension d = getSize();// ウィンドウサイズ獲得
      w = d.width;//横幅セット
      h = d.height;//縦幅セット
      // オフスクリーン(前面)画像用のデータ領域
      offScreenImage = createImage(w,h);//前面オフスクリーン領域生成
      offG = saveOffG = offScreenImage.getGraphics();//前面オフスクリーン描画用オブジェクト獲得
      offG2 = (Graphics2D) offG;
      // オフスクリーン(背景)画像用のデータ領域
      backGroundImage = createImage(w,h);//後面オフスクリーン領域生成
      backG = backGroundImage.getGraphics();//後面オフスクリーン描画用オブジェクト獲得
      backG.setColor(backColor);//背景は白色
      backG.fillRect(0,0,w,h);//背景を白で塗りつぶし
      addKeyListener(this);/* キーボードのリスナーを設定 */
    }
    public void paint(Graphics g) {
        /* キャンバスが準備できていない場合は何もしない */
        if (offG == null || backGroundImage == null) return;
        // まず背景オフスクリーン画像を描画
        offG.drawImage(backGroundImage,0,0,this);
        java.awt.Color centerColor = ballColor;
        java.awt.Color color2 = ballColor2;
        Point2D center = new Point2D.Double(x, y);/* ボールの中央位置 */
        float radius = r;/* 半径 */
        float[] dist = {0.15f, 0.8f};/* 0.15(color2), 0.8(centerColor) [0.15-0.8]を補間*/
        Color[] colors = {color2, centerColor};/* 補間のための2色 */
        RadialGradientPaint rgp = new RadialGradientPaint(center, radius, dist, colors);
        offG2.setPaint(rgp);/* ボールの色塗り設定 (Graphics2Dクラス(offG2)が必要)*/
        offG.fillOval(x-r, y-r, r*2, r*2);/* ボールの描画(前景)*/
        g.drawImage(offScreenImage,0,0,this);
    }
    public void animate() {/* ボールの位置変更 */
      if (thread != null){
        // ウィンドウの端で速度ベクトルを反転 
        java.awt.Rectangle bounds = getBounds();
        if ((x - r + dx < 0) || (x + r + dx > bounds.width)) dx = -dx;
        if ((y - r + dy < 0) || (y + r + dy > bounds.height)) dy = -dy;

        // 円の移動 (一般にはx = lastx(直前位置) + speedX * dx)
        x += dx;  y += dy;

        // paint()メソッドを呼び出す(repaint()で間接的に)
        repaint();
      }
    }
    /**
     * This method is from the Runnable interface.  It is the body of the 
     * thread that performs the animation.  The thread itself is created 
     * and started in the start() method.
     **/
    public void run() {
        while (true){
            animate();//描画位置更新
            try {
                Thread.sleep(sleepTime);// 待つ
            }
            catch (InterruptedException e){}//割り込みなし
        }
    }
    /** Start animating when the browser starts the applet */
    public void start() {
        if (thread == null){
            thread = saveThread = new Thread(this);//スレッド生成
            thread.start();//スレッド開始
        }
    }
    /** Stop animating when the browser stops the applet */
    public void stop() {
        if (thread != null){
	    saveX = x; saveY = y;/* 現在位置をセーブ */
	    saveDx = dx; saveDy = dy;/* 現在の方向をセーブ */
            offG = null;/* thread.stop()は非推奨なのでpaintを空回りさせ動きをストップさせる */
            thread = null;
        }
    }
    public void resume(){/* スレッド再開 */
	if (offG == null && thread == null){
		offG = saveOffG;/* セーブしていたオフスクリーングラフィックスを戻す*/
		thread = saveThread;/* セーブしていたスレッドを戻す */
		x = saveX; y = saveY;/* セーブしていた位置を戻す */
		dx = saveDx; dy = saveDy;/* セーブしていたベクトルを戻す */
	}
    }

  public static void main(String[] args) {
    SmoothBouncingCircleFrame bcf = 
        new SmoothBouncingCircleFrame("ボールぴょんぴょん 青野雅樹:01162069");
    bcf.setSize(600, 600);
    bcf.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    bcf.setVisible(true);
    bcf.init();
    bcf.start();
  }
}
実行例(Sキーでストップさせた状態)は以下のとおりです。

マルチスレッドを使ったSwingアニメーション例