Bullet 第5回

GUI コンポーネントの作成 (2)
  1. 今回の例題
  2. バーはバーメータのバー
  3. バーを動かす
  4. 他のクラスから指示値を設定
  5. インタフェースを使ってみよう
  6. おまけ
  7. ソースコードのダウンロード

 
  今回の例題  
 

前回はバーメータを作成するための基礎編としてJavaでのグラフィックスの取り扱いや、アニメーションについて説明しました。今回は前回の説明を基に実際にバーメータを作っていきましょう。

今回の最終的にできあがるアプレットは BarMeterApplet.html です。

作成する Java のソースコードは BarMeterApplet.java DataCreator.java SinDataCreator.java IncrementalDataCreator.java AbsCosDataCreator.java の 5 つです。急にファイルが増えてしまいましたが、バーメータを構成するのは BarMeterApplet.java の一つだけですから、安心してください。

それでは、はじめましょう。

 
  ▲このページのトップへ戻る  
  バーはバーメータのバー  
 

バーメータの基本はあたりまえのようですがバーです。ですから、どうやってバーを表示させるかをまず問題にしましょう。

バーを表示するには何が必要でしょうか。何はなくとも指示値は必要ですね。その他は...、バーを構成するためにはグラフの最大値、最小値が必要になります。また、グラフ自体の大きさも必要ですね。それでは、

指示値 : value
グラフの最大値 : 100
グラフの最小値 : 0
グラフの幅 : width
グラフの高さ : height

としてバーを描いてみましょう。

まず、value の値を座標に変換しなくてはなりません。グラフは図 1 の赤い枠に示した (0, 0) から (width, height) までの四角形になります。このグラフ上で 0 は y 座標の height に相当し、100 が 0 に相当します。このとき、value の座標は簡単な比率計算で求めることができます。

value に対する y 座標 = height (1-value/100)   ・・・(1)

図1 バーに関する座標

図1 バーの座標

したがって、バーを描画するには (0, height(1 - value/100)) を始点として、幅 width、高さ height × value/100 の四角形を描画すればいいことになります。これを行ったのが BarMeterApplet1.java です。

まずは実行させてみましょう BarMeterApplet1.html

BarMeterApplet1 は HTML 文章の <param> タグで記述された value を読み込んでバーを描画しています。バーを描画しているのが drawBar メソッドです。

    protected void drawBar(Graphics g, double value){
	
        // 指示値の相対値を算出
        double val = value / 100.0;

        // グラフ上の高さに変換
        val *= height;

        int y = height - (int)val;
        int h = (int)val;

        // バーの色を設定
        g.setColor(Color.yellow);

        // バーを描画
        g.fillRect(0, y, width, h);
    }

注意しなければならないことに変数の型があります。value が double 型であるのに対して、座標は物理的な画面のピクセルに相当しますから int であるという点です。このため、double から int へのキャストを行わなければなりませんが、桁落ちのための誤差がなるべく小さくなるようにしなければなりません。

BarMeterApplet1 では最大値を 100、最小値を 0 としていましたが、これを任意の値にしてみましょう。とくに難しいことはありません。最大値を max、最小値を min としたら式 (1) は次のようになります。

式 2   ・・・(2)

となります。そこで drawBar メソッドを式 (2) に変更すればいいだけです。これを実現したのが BarMeterApplet2.java です。

アプレットはこちらで実行できます BarMeterApplet2.html

BarMeterApplet2.java では最大値、最小値も HTML 文章の中から読み出すようにしました。drawBar メソッドの部分は次の用に変更しています。

    protected void drawBar(Graphics g, double value){
	
        // 指示値の相対値を算出
        double val = (value - minimum)/ (maximum - minimum);

        // グラフ上の高さに変換
        val *= height;

        int y = height - (int)val;
        int h = (int)val;

        // バーの色を設定
        g.setColor(Color.yellow);

        // バーを描画
        g.fillRect(0, y, width, h);
    }

ここまでできればバーメータはほとんど終わったも同然です。それでは、次の章ではバーを動かしてみることにしましょう。


 
  ▲このページのトップへ戻る  
  バーを動かす  
 

前章までは静的なバーグラフでしたから監視システムでは使い物になりません。やはり、リアルタイムにバーが指示値を表さなければ意味がないですね。というわけで、バーを動かしてみましょう。ここまできたら、どのように動かすかはお分かりだと思います。そう、定期的に指示値を更新して再描画を行えばよいのです。ただし、前回説明したようにちらつきを防ぐためにダブルバッファリングはお忘れなく。

まずは、アプレット自身が指示値を作り出すようにしてみましょう。

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

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

BarMeterApplet3 では指示値を作り出すためにスレッドを作成しています。

    public void start(){
        // 指示値を生成するためのスレッドを作成
        Thread thread = new Thread(this);
        thread.start();
    }

スレッドでは sin メソッドを使用して指示値を生成しています。java.lang.Math クラスは sin やルートなどの算術演算を定義したクラスです。sin メソッドの引数はラジアンなので度からラジアンの変換が必要になります。ラジアンの変換で使用されている Math.PI は Math クラスに定義してある定数でπを表しています。value を更新すると、再描画を行い、その後 100ms スリープします。

    public void run(){
        while(true){
            for(int i = 0 ; i < 360 ; i++){

                value = Math.sin(i*Math.PI/180.0);
                repaint();

                try{
                    Thread.sleep(100);
                }catch(InterruptedException ex){
                    return;
                }
            }
        }
    }

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

他のクラスから指示値を設定

 
 


前章までで、バーメータの基本的な動作は説明したのですが、このバーメータの使い勝手をもう少し向上させて見ましょう。今までは、動作例を示すためにバーメータ自身が指示値を生成してそれを表示していました。しかし、実際には監視対象からあがってきたデータを表示することが重要でしょう。そこで他のオブジェクトからの指示値を表示させて見ましょう。

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

ソースはここで見ることができます BarMeterApplet4.java, DataCreator1.java

今までの例では自分のクラス内で value を変更させていたため、直接 value に代入を行っていました。しかし、他のオブジェクトから直接 value を操作させることはオブジェクト指向の情報隠蔽が守られなくなります。そこで、value の更新のために setValue メソッドを用意しました。

    public void setValue(double value){
        this.value = value;
        repaint();
    }

他のオブジェクトはこのメソッドを呼び出すことによって、指示値を描画することが可能になります。

指示値を生成するのは DataCreator1 です。DataCreator1 はスレッドを持ち、定期的に BarMeterApplet4#setValue メソッドをコールします。

    public void run(){
        double value;
        while(true){
            for(int i = 0 ; i < 360 ; i++){
                // 指示値を生成
                value = Math.sin(i*Math.PI/180.0);

                // 指示値の設定
                applet.setValue(value);

                try{
                    Thread.sleep(100);
                }catch(InterruptedException ex){
                    return;
                }
            }
        }
    }

変数 applet が BarMeterApplet4 のオブジェクトを参照しています。

この方法で他のオブジェクトで指示値を生成することができるのですが、汎用性がかけています。もし、ことなる指示値を表示したい場合は DataCreator1.java を書き換えなければなりません。1つや2つであればいいのですが、多種の扱わなければならない指示値があった場合対処できなくなってしまいます。そこで、次の章ではインタフェースを用いて複数のクラスを一律に扱ってみましょう。

 
  ▲このページのトップへ戻る  
  インタフェースを使ってみよう  
 

インタフェースにはいろいろな使い道がありますが、ここでは複数の同一機能をもつクラス群をまとめて扱うことに利用しました。指示値を生成するには上述した例であれば sin メソッドを使用していますが、その他にもいろいろと方法はあります。三角関数や対数などを使用して数学的に生成することもあれば、もちろん実際に監視機器からデータを吸い上げてくることもあります。しかし、機能的には指示値をバーメータに渡すということにあります。そこで、この部分をインタフェースにを用いてまとめることができるわけです。

指示値の生成のためのインタフェースは次のようにしました。

public interface DataCreator extends Runnable {
    public void setBarMeterApplet(BarMeterApplet applet);
    public void start();
}

BarMeterApplet の setValue メソッドを呼ぶためには、BarMeterApplet の参照を持たなくてはいけませんから、そのためのメソッドを用意しました。もう一方は指示値の生成の開始を支持するためのメソッドです。このインタフェースをインプリメントしたクラスを 3 種類作成してみました。SinDataCreator, IncrementalDataCreator, AbsCosDataCreator の 3 つです。それぞれ、sin(x)、x++、|cos(x)|という関数を利用して指示値を生成しています。実際の処理は DataCreator1 とほとんど同じです。

次に BarMeterApplet における DataCreator の扱いについて説明しましょう。BarMeterApplet では DataCreator オブジェクトをメンバ変数として保持しますが、変数の定義はインタフェースの DataCreator で行っています。

    protected DataCreator dataCreator;

DataCreator インタフェースはそれだけではオブジェクトを生成できないので、SinDataCreator など実際にインタフェースをインプリメントしたクラス (具象クラスもしくはコンクリートクラスと呼びます) のオブジェクトを生成しなくてはなりません。今回は生成するクラスを HTML 文章の <param> タグで記述するようにしました。こうすると、ソースを一行も変更しなくても異なるオブジェクトを扱うことが可能になります。

        String dataCreatorName = getParameter("data");

        Class dataCreatorClass = Class.forName(dataCreatorName);
        dataCreator = (DataCreator)dataCreatorClass.newInstance();

このソースは例外処理を省略しています。

まず、クラス名が記述してある <param> タグを読み込んで dataCreatorName に代入します。java.lang.Class クラスはクラスの情報を表すクラスですから、dataCreatorClass が HTML 文章に記述された具象クラスを表しています。この Class オブジェクトからオブジェクトを生成させるには newInstance メソッドを使用します。

具象クラスのオブジェクトが生成すれば、あとは具象クラスがなんであろうとも行うことは同一です。

    public void start(){
        dataCreator.setBarMeterApplet(this);
        dataCreator.start();
    }

dataCreator に自分自身の参照を渡し、指示値の生成をスタートさせるだけです。後は、dataCreator がスレッドを生成して、定期的に BarMeterApplet の setValue メソッドを呼び出すようになります。

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

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

このようにインタフェースを使用することで具象クラスを意識することなしに、プログラムが記述することができました。しかし、DataCreator の方に BarMeterApplet の参照を渡さなければならないという部分が気になります。DataCreator は指示値を生成するだけであって、それを渡す相手は BarMeterApplet でなくてもいいはずです。ここもインタフェースにしてしまえば、もっと汎用に DataCreator を使うことができるはずです。これを突きつめたのがイベントです。

オブジェクト指向ではなるべくオブジェクト間の依存性を低くするように設計します。他のオブジェクトとの依存度が低ければ低いほど汎用的に使用でき、再利用性も高まります。オブジェクト間の依存度をほとんどなくしてしまったのがコンポーネント (ソフトウェア部品) です。Java にはコンポーネントの仕様として JavaBeans があります。JavaBeans ではコンポーネント間のつながりはイベントを用いて行います。

それでは、次回は今回作成したアプレットをコンポーネント化して行きましょう。

 

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

BarMeterApplet では、指示値から height までの高さのバーを描画しています。でも、ちょっと考えてみてください。height のバーをはじめに描いてしまってから、指示値の部分まで消してしまってもいいはずです。

さらに進んで考えれば、はじめに描くバーは単色の四角形でなくてもいいはずです。そこで、イメージを読み込んでバーの代わりに使えないでしょうか。これを行ってみたのが ImageBarMeterApplet.java です。

drawBar メソッドの中で drawImage メソッドを使用してイメージを描画し、その後 fillRect メソッドで消去を行っています。

    protected void drawBar(Graphics g, double value){
        // 指示値の相対値を算出
        double val = (value - minimum)/ (maximum - minimum);

        // グラフ上の高さに変換
        val *= height;
        int h = (int)val;

        // イメージを描画
        g.drawImage(foregroundImage, 0, 0, this);

        // 背景色でバーを塗りつぶす
        g.setColor(this.getBackground());
        g.fillRect(0, 0, width, h);
    }

さらにさらに考えてみれば消去を行う時に、別のイメージを使用してもいいはずです。

これを行ってみたのが ImageBarMeterApplet2.java です。

イメージを使って消去するにはイメージを指示値にあわせてカットする必要があります。java.awt.Graphics クラスには drawImage(int x, int y, int width, int height, ImageObserver observer) という幅と高さを指定してイメージの描画を行うメソッドがあるのですが、このメソッドは指定した幅と高さにイメージをリサイズしてしまうためここでは使用できません。

そのために、もう一つ別のイメージ bimage を使用しました。bimage は指示値にあわせてその都度生成しています。そこに背景用のイメージを貼りこみます。bimage からはみ出た部分は捨ててしまうことで、指示値にあわせた高さを持つイメージができあがります。

ただし、このように動的にイメージを生成すると、パフォーマンスが落ちる欠点があります。

    protected void drawBar(Graphics g, double value){
	
        // 指示値の相対値を算出
        double val = (value - minimum)/ (maximum - minimum);

        // グラフ上の高さに変換
        val *= height;
        int h = (int)val;

        // イメージのクリア
        g.clearRect(0, 0, width, height);

        // イメージを描画
        g.drawImage(foregroundImage, 0, 0, this);

        if(h != 0){
            // 背景イメージの描画
            Image bimage = this.createImage(width, h);
            Graphics bg = bimage.getGraphics();
            bg.drawImage(backgroundImage, 0, 0, this);

            g.drawImage(bimage, 0, 0, this);
        }
    }

完成したアプレットはこちらから実行できます ImageBarMeterApplet.html

ImageBarMeterApplet.html では次に示すようなイメージをバーの描画に使用しています。

グラデーション Cars People 温度計 前景用 温度計 背景用
      前景用 背景用
図2 アプレットに使用したイメージ

イメージが入るだけでメータがより魅力的になりませんか。このようなちょっとした工夫で監視システムなどの画面を表現豊かにすることが可能になると思います。

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

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



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

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