bullet 第4回

GUI コンポーネントの作成 (1)
  1. 今回の例題
  2. とりあえず描いてみよう
  3. アニメーションに挑戦
  4. ちらつきを防ごう
  5. まだまだちらつくという方に
  6. おまけ
  7. ソースコードのダウンロード

 
  今回の例題  
 

今回から監視システムでなくてはならないもの、メータやグラフなどのグラフィカルな部分について説明したいと思います。

Javaではメータなどの描画は AWT (Abstract Window Toolkit)を用いて行います。Java 2 ではAWT以外にもJFC (Java Foundation Class、Swing も JFC に含まれる)という多機能のグラフィックライブラリもあります。しかし、標準的に Java 2 をサポートしているブラウザーがないため、この講座では AWT を使用することにします。

監視システムの GUI にはメータ、トレンドグラフなどあり、用途に応じて使い分けられていると思います。その中でもバーメータは様々な用途に使えるのですが、作成はそれほど難しくはありません。そこで、この講座ではバーメータを材料として、Java を用いた GUI 部品の基本を説明して行きましょう。

バーメータといっても図に示したようにいろいろと種類がありますが、まずは図の一番左に示したような最もシンプルなものを作ってみましょう。ただし、1 回の連載ですべて説明するのは量的に多いので、3 回に分けたいと思います。第 1 回目はJavaの描画の基本の部分について、2 回目がバーメータの作成、そして最後に部品化への対応について説明したいと思います。

<IMG SRC="images/barmeterexample.gif" WIDTH=450 HEIGHT=250 BORDER=0>

図1 いろいろなバーメータ

 
  ▲このページのトップへ戻る  
  とりあえず描いてみよう  
 

まず、なにか画面に描いてみましょう。下に示したアプレットはブラウザ上に赤い四角を描画するものです。Java ではファイル名は"クラス名".javaとなるのでこのアプレットは SquareApplet.java となります。

SquareApplet.java
import java.awt.*;
import java.applet.*;

public class SquareApplet extends Applet{

    public void paint(Graphics g){
        g.setColor(Color.red);           // (1)
        g.drawRect(10, 10, 100, 50);     // (2)
    }

}

これを実行するための HTML ファイルを SquareApplet.html とします。

SquareApplet.html
<html>
<head><title>Square Applet</title></head>
<body>
  <h1>Square Applet</h1>
  <applet code="SquareApplet.class" width="300", height="200">
  </applet>
</body>
</html>

それでは、このアプレットを実行してみましょう SqaureApplet.html

ソースコードはここで見ることができます SquareApplet.java

SquareApplet.java はソースを見ればお分かりのようにたった1つのメソッドしかなく、その他の余計な部分を一切取りのぞいています。

残された1つのメソッドが描画を行う paint メソッドです。描画を行う場合は、paint メソッドにその処理を記述するようにします。paint メソッドの引数になっている Graphics クラスはグラフィックス・コンテキストと呼ばれるもので、描画は Graphics クラスのオブジェクトに対して行います。

Graphics クラスの描画機能の例を示すと、

描画の種類 描画メソッド 塗りつぶしメソッド
drawLine fillLine
四角形 drawRect fillRect
楕円 drawOval fillOval
円弧 drawArc fillArc
多角形 drawPolygon fillPolygon
文字列 drawText なし
ビットマップイメージ drawImage なし

などがあります。そのほかに、色やフォントの設定などの機能もあります。

SquareApplet ではまず、(1) の部分の setColor メソッドで色の設定を行っています。Color.red というのは、色を管理する Color クラスに始めから用意されているスタティックなオブジェクトのひとつで、赤を示しています。赤以外にも青や黄色も用意されていますが、RGB の値を指定して任意の色を作ることもできます。

(2) では drawRect メソッドを使用して座標 (10, 10) から始まり、幅が 100、高さが 50 の四角形を描画しています。Java の座標系は左上が原点になり、下に向かって y 座標の値が大きくなるようになります。

図1 SquareApplet の座標

図2 SquareApplet での座標

Graphics クラスの機能を知るために、もう少し複雑な絵を描いてみましょう DrawingApplet.html

ソースコードはここで見ることができます DrawingApplet.java

それでは、Graphics クラスの機能もわかってきたので、アニメーションに挑戦してみましょう。


 
  ▲このページのトップへ戻る  
  アニメーションに挑戦  
 

なぜ、メータにアニメーションが必要なのでしょうか。それは、メータの指針が動くことやバーメータのバーが上下することを考えれば分かります。メータの指針が動くには定期的に指針を書き換える必要があります。これはアニメーションにほかならないのです。監視システムではいろいろなところでアニメーションが使われます。例にあげたメータやバーメータもそうですが、トレンドグラフやインディケータもアニメーションを使っています。連続的に変化する情報を監視するのに優れた方法の1つがアニメーションといえるのではないでしょうか。

それでは、実際にアニメーションのプログラミングを行う前に、画面の書き換えについて説明しましょう。今までの例では、ソースコードの中に画面の書き換えに関するものは含まれていません。しかし、あるウィンドウに隠されていたブラウザのウィンドウが表示されたときなどは、画面の書き換えが必要です。実際にこのような場合の書き換えは Java のバーチャルマシンが自動的に行っています。では、ユーザが自分で画面を描きかえる場合はどのようにすればいいのでしょうか。

画面の書き換えのために用意されているメソッドは repaint です。画面の書き換えを行いたいところで repaint メソッドを書くと、repaint メソッドの中から update メソッドが呼ばれます。さらに、update メソッドから先ほど説明した paint メソッドが呼ばれるようになるのです。

ということは定期的に reapint メソッドを呼び出すようにして、その場面に応じて paint メソッドで描画を行えばアニメーションができるわけです。定期的に repaint メソッドを呼び出すには、前回説明したスレッドを利用します

まずは簡単な例として円の色を変化させるアニメーションです。

ColorCircleApplet.java
import java.awt.*;
import java.applet.*;


public class ColorCircleApplet extends Applet implements Runnable {

    protected int color;

    public void init(){
        color = 0;                          // (1) color の初期設定
    }

    public void start(){
        Thread thread = new Thread(this);   // (2) スレッドを生成
        thread.start();                     //     スレッドの開始
    }

    public void run(){                      // (3) スレッドで呼ばれるメソッド
        while(true){
           repaint();                       // (4) 再描画

           color++;                         // (5) color を更新する
           if(color > 4){                   //     4 以上であれば 0 に戻す
               color = 0;
           }

           try{
               Thread.sleep(2000);          // (6) 2秒間スリープ
           }catch(InterruptedException ex){}
        }
    }

    public void paint(Graphics g){
        switch(color){                      // (7) color の値に応じて色を設定
        case 0:
            g.setColor(Color.red);
            break;
        case 1:
            g.setColor(Color.yellow);
            break;
        case 2:
            g.setColor(Color.green);
            break;
        case 3:
            g.setColor(Color.cyan);
            break;
        case 4:
            g.setColor(Color.blue);
            break;
        }

        g.fillOval(10, 10, 100, 100);       // (8) 円を描画
    }
}

それでは、このアプレットを実行してみましょう ColorCircleApplet.html

ソースコードはここで見ることができます ColorCircleApplet.java

2秒ごとに色の違う円を描画していることが確認できたでしょうか。このアプレットでは変数 color の値によって円の色を変化させています。

それでは、ColorCircleApplet の動きを追ってみましょう。アプレットは生成されると init メソッド, start メソッドの順にコールされます。そこで、(1) init メソッドで color の初期設定を行い、(2) start メソッドでスレッドを生成させ開始します。

スレッドの中心となる run メソッドは (4) 再描画のための repaint メソッドをコールします。これで定期的に画面を更新するわけです。次に、(5) color の値の更新を行います。color の値を変更することで再描画を行うときに異なる描画ができるようになります。その後、(6) 2 秒間スリープし、再び (4) の処理を繰り返し行います。

run メソッドの中で reapint メソッドが呼ばれると、最終的に paint メソッドが呼ばれます。paint メソッドではcolor が 0 であれば赤というように、(7) switch 文で color の値に応じて、描画する円の色を設定します。そして、最後に (8) 円を描画します。

なんか、とても簡単に書けるので、結構あっけないのではないでしょうか。逆にいえば、Java ではこのような用途のための機能がはじめから盛り込まれているので、ユーザが無理なくアプレットを書けるのではないでしょうか。


次に、もう少し動きのあるアプレットを作って見ましょう。動く円のアプレットです MovingCircleApplet1.html

ソースコードはここで見ることができます MovingCircleApplet.java

ColorCircleApplet ではクラスのメンバ変数として色を保持していましたが、MovingCircleApplet では円の x 座標を持たせています。init メソッドでは x を初期設定しています。

    protected int x;

    public void init(){
        x = 0;                              // x座標の初期設定
    }

また、ColorCircleApplet と同様に run メソッドの中で、x の更新を行います。

 	    x += 20;

	    if(x > 300){                       // 300 以上であれば 0 に戻す
		    x = 0;
	    }

これは円を 20dot 動かすことを意味します。円が右端にきたら左端に移動させるために if 文で x の値を調べ、x が 300 以上であれば 0 (左端) にします。

そして最後に paint メソッドで x の位置に円を描画します。

    public void paint(Graphics g){
	    g.setColor(Color.red);
	    g.fillOval(x, 10, 100, 100);       // xの位置に円を描画
    }

赤い円が 20ms ごとに移動することが確認できたでしょうか。でも、このアプレットをよーく見てみてください。なんか、おかしくないですか。そうです、このアプレットは画面がちらつくのです。そこで、次の章ではこのちらつきをおさえる方法を考えてみましょう。


 
  ▲このページのトップへ戻る  
  ちらつきを防ごう  
 


なぜ、アプレットがちらつくのでしょうか。 それはアプレットの画面の更新動作に理由があります。

アプレットでは描画を行うときに、まず画面をクリアにするために update メソッドで背景色ですべてを塗りつぶします。そして、その後に paint メソッドで描画を行います。

update() -> paint()

このため、画面は背景色で塗られた状態があり、これがちらつきの原因となります。これを取り除くには画面のクリアを最小限にすればいいわけです。たとえば MovingCircleApplet では、画面全体を書き換える必要はなく円がかかれた部分だけを消去すれば、クリアの作業が最小化できます。

それでは、このちらつきを防止した MovingCircleApplet を作ってみましょう。

完成版はこちらで見られます MovingCircleApplet2.html

ソースはここで見ることができます MovingCircleApplet2.java

クラスに機能を追加する場合には、クラスの派生を行います。MovingCircleApplet2 は MovingCircleApplet の画面消去の機能を追加したものになります。

まず、update メソッドではどのようなことを行っているのでしょうか。update メソッドは java.awt.Container で次のように定義されています。

    public void update(Graphics g) {
        if (isShowing()) {
            if (! (peer instanceof java.awt.peer.LightweightPeer)) {
                g.clearRect(0, 0, width, height);
            }
            paint(g);
        }
    }

ちなみに Applet の継承関係は

java.lang.Object
  |
  +--java.awt.Component
        |
        +--java.awt.Container
              |
              +--java.awt.Panel
                    |
                    +--java.applet.Applet

となっており、update メソッドは Applet の基底クラスの基底クラスである Container で定義されているわけです。

update メソッドに話を戻しましょう。

2行目の isShowing メソッドは表示されているかどうか調べるメソッドです。表示されていない場合は再描画を行う必要はないのは自明ですね。

次の行はとりあえず飛ばして、問題はその次の行です。

                g.clearRect(0, 0, width, height);

width は画面の横幅、heiht が高さを示しているので、すべてをクリアするということになります。これを MovingCircleApplet2 では paint メソッドを呼ぶだけにしています。

    public void update(Graphics g){        // update を再定義して
        paint(g);                          // 背景色での全面消去を
    }                                      // 行わないようにする

全面消去を行わなくなった代わりに MovingCircleApplet2 では円の部分だけ消去するようにします。このために、一つ前の円の位置を保持するメンバ変数 oldX を導入しました。

    protected int oldX;

そして run メソッドの中で x を更新する前に、その値を oldX にコピーしています。

            oldX = x;                       // 現在の円の位置を保存しておく

            x += 20;                        // xの更新 
            if(x > 300){
                x = 0;
            }

描画を行うときには、前回描画した円を消去しなくてはなりません。前回描画した円は oldX が示している位置に存在しているので、それを clearRect メソッドを用いて消去します。その後、円を描画します。

    public void paint(Graphics g){
        g.clearRect(oldX, 10, 100, 100);   // 前の位置の円を消去する

        g.setColor(Color.red);
        g.fillOval(x, 10, 100, 100);       // xの位置に円を描画
    }

どうですか、少しはちらつきが減ったのではないでしょうか。

 

 
  ▲このページのトップへ戻る  
  まだまだちらつくという方に  
 

MovingCircleApplet2 でちらつきを減らしましたが、まだ完全にちらつきがなくなったわけではありません。画面の消去を行う範囲を小さくしたとしても、どうしても背景色だけの瞬間が生じてしまうためわずかですがちらつきが起こってしまいます。どうすれば、ちらつきをなくすことができるでしょうか。それにはダブルバッファリングを使います。

ダブルバッファリングは画面とは別に表示されない画面 (イメージバッファといいます) を用いて行います。画面の表示を行うことと、描画を行うことを切り離して、描画はイメージバッファに行います。イメージバッファへの描画が終了した時点で、それを表示するようにします。このようにすることで描画の過程を表示しなくなりますので、ちらつきが抑えられます。

これを行ったのが、MovingCircleApplet3 です。完成版はこちらで見られます MovingCircleApplet3.html

ソースはこちらです MovingCircleApplet3.java

MovingCircleApplet3 は MovingCircleApplet2 と同様に MovingCircleApplet を派生させて作成しているので、ダブルバッファリングの部分だけ書き換えればすみます。では MovingCircleApplet3 を見ていきましょう。

イメージバッファを作るために java.awt.Image オブジェクトを利用しています。また、イメージバッファに書き込むためのグラフィックコンテンツもメンバ変数に加えておきましょう。

    protected Image image;                  // DoubleBuffering用のイメージ

    protected Graphics g2;                  // image に書き込むための
                                            // Graphics Context

次に paint メソッドの内部処理です。

    public void paint(Graphics g){
        if(image == null){                  // image が生成されていなければ生成する
            image = createImage(400, 120);
            g2 = image.getGraphics();       // image からGraphics Contextを得る
        }          

        g2.clearRect(0, 0, 400, 120);       // image を消去する。
        g2.setColor(Color.red);
        g2.fillOval(x, 10, 100, 100);       // xの位置に円を描画

        g.drawImage(image, 0, 0, this);     // Buffering用のイメージを描画する
    }

まず、image を生成していなければ、生成を行います。この image がイメージバッファになりますから、表示画面と同じ大きさにします。Image オブジェクトは抽象クラスのため、 new で生成させることはできません。そのために createImage メソッドを使用します。そして、image に描画するためのグラフィックコンテキストを得ます。その後、image をクリアして、円を描画します。

最後にイメージバッファを表示させます。

MovingCircleAplet.html では 3 種類のアプレットを並べてみました。3 種類を比較するとちらつきの頻度が変化していることがわかると思います。監視システムでは、ユーザが画面を注視することもあるので、ちらつきなどがあると目が疲れるだけでなく、集中力の低下にもつながりかねません。きれいな表示を行うためにも今回説明を加えたダブルバッファリングを活用してください。

さて、次回は実際にバーメータを作成していきたいと思います。なかなか、バーメータの作成に入らないのでいらいらしている人もいらっしゃると思いますが、次回までもう少しお待ちください。

 

 
  ▲このページのトップへ戻る  
  おまけ  
 

今回の例では、実際に paint メソッドの中で描画機能を用いて描いているものを示しましたが、JPEG や GIF などのイメージファイルを読み込んでアニメーションを行うこともできます。たとえば、次の例では下の 8 種類の絵 (実際には 6種類ですが) を用いてアニメーションを行っています。

Cup 0 Cup 1 Cup 2 Cup 3 Cup 4 Cup 5 Cup 6 Cup 7

アプレットはこちらで見られます ImageAnimator.html

ソースコードはこちらです ImageAnimator.java

HTML文章の中で読み込むファイルを指定すると、ファイルを読み込んでアニメーションを開始します。ソースコードの中で java.awt.MediaTrcker というクラスが使用されていますが、これはイメージファイルのロードを 管理するクラスです。

また、今まで使ってこなかった Applet の機能も使用しています。getParam メソッドは HTML 文章中に <applet> タグの内側で使用される <param> タグの内容を取ることができます。たとえば、ImageAnimator.html では次に示したように 3種類のパラメータをアプレットに渡すために <param> タグを使用しています。

<applet code="ImageAnimator.class" width="36", height="36">
  <param name="number" value="8">
  <param name="filename" value="images/javacup">
  <param name="suffix" value="gif">
</applet>

その他にも、URL で示されたイメージファイルを読み込む getImge メソッドなどを使用していますが、詳しくはJava の API ドキュメントを参照してください。 このシリーズの第 1 回目では日本語版のドキュメントのポインタが示されていましたので、今回は英語版を紹介しておきます。

JDK 1.1.8 API Document (英語版) http://java.sun.com/products/jdk/1.1/docs/api/packages.html

java.applet.Applet http://java.sun.com/products/jdk/1.1/docs/api/java.applet.Applet.html

java.awt.MediaTracker http://java.sun.com/products/jdk/1.1/docs/api/java.awt.MediaTracker.html

 

 
  ▲このページのトップへ戻る  
  ソースコードのダウンロード  
 

今回用いた全てのアプレットのソースファイル、クラスファイル、HTMLドキュメント、イメージファイルはここからダウンロードできます appletsrc.zip



bullet JavaおよびJavaに関する商標は、米国Sun Microsystems社の登録商標または商標です。

 
  ▲このページのトップへ戻る