第3回 HelloWorld を改造しよう!

今回の講座は?

第2回で作成したサンプル HelloWorld を改造し、遊んでみます。 アクションボタンを追加したり、アラートを表示してみたり。 グラフィックも使ってみましょう。 より多くの機能が理解できると思います。

まずはアクションボタンを追加してみる

サンプルを実行したら、ちゃんと終了させたいですよね。 なので最初に、"Exit" というアクションボタンを追加してみましょう。

HelloWorld.java (その3)
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;

public class HelloWorld extends MIDlet {
  private Command exitCommand = new Command("Exit", Command.ITEM, 1);
  public void startApp() {
    Form form = new Form("Hello World!");
    form.append(new StringItem("Message", "Hello World!"));
    form.addCommand(exitCommand);
    Display.getDisplay(this).setCurrent(form);
  }
  public void pauseApp() {}
  public void destroyApp(boolean unconditional) {}
}

まずはコマンドを表現する Command クラスのオブジェクト exitCommand を生成します。 new Command("Exit", Command.ITEM, 1); の引数はコマンドの名前、タイプ、表示の優先順位です。 この優先順位が高いものから画面に表示され、表示される数は機器によって違います。 携帯電話だと2個、Palm機だと3〜4個ぐらいでしょうか。

画面に表示される form オブジェクトに対し addCommand() メソッドを起動することで 実際にコマンドが追加されます。

HelloWorld MIDlet part3

と、画面に "Exit" というボタンが追加されましたね。

処理も追加しないとね!

"Exit" というボタンは画面に追加されましたが、タップしても何も起きませんね。 タップした時に呼び出される処理も、追加してあげなければなりません。

HelloWorld.java (その4)
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;

public class HelloWorld extends MIDlet implements CommandListener {
  private Command exitCommand = new Command("Exit", Command.ITEM, 1);
  public void startApp() {
    Form form = new Form("Hello World!");
    form.append(new StringItem("Message", "Hello World!"));
    form.addCommand(exitCommand);
    form.setCommandListener(this);
    Display.getDisplay(this).setCurrent(form);
  }
  public void pauseApp() {}
  public void destroyApp(boolean unconditional) {}

// implements CommandListener

  public void commandAction(Command c, Displayable s) {
    if (c == exitCommand)
      notifyDestroyed();
  }
}

ボタンがタップされた (コマンドが起動された) ことを 知る ためには implements CommandListener とコマンド用の "リスナ・インターフェース(Listener Interface)" を実装します。 また対象とするコマンドが追加された対象 form オブジェクトに対し setCommandListener() メソッドで、「コマンドイベントを受け取るよ!」と登録します。

リスナを実装 (implements) し、 イベントを貰いたい相手に自分を登録する。 これらは Java 言語におけるイベント処理のお約束です。

イベントを受け取った際の処理は、 commandAction() メソッドの中に記述します。 受け取ったコマンドが登録した exitCommand であることを確認した後、 notifyDestroyed() メソッドでアプリケーションを終了させます。

実行して "Exit" ボタンをタップしてみてください。 今度はきちんと終了できたでしょうか?

ボタンを追加し、アラートを表示してみる

ボタンをタップしたら終了、それだけでは寂しすぎますね。 もうひとつボタンを追加し、それをタップしたらアラート (警告メッセージ) が表示されるように改造してみましょう。

HelloWorld.java (その5)

import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;

public class HelloWorld extends MIDlet implements CommandListener {
  private Command exitCommand = new Command("Exit", Command.ITEM, 1);
  private Command alertCommand = new Command("Alert", Command.ITEM, 2);
  public void startApp() {
    Form form = new Form("Hello World!");
    form.append(new StringItem("Message", "Hello World!"));
    form.addCommand(exitCommand);
    form.addCommand(alertCommand);
    form.setCommandListener(this);
    Display.getDisplay(this).setCurrent(form);
  }
  public void pauseApp() {}
  public void destroyApp(boolean unconditional) {}

// implements CommandListener

  public void commandAction(Command c, Displayable s) {
    if (c == exitCommand)
      notifyDestroyed();
    else if (c == alertCommand) {
      Alert alert = new Alert("Alert Test", "Hello World!", null, null);
      alert.setTimeout(5000);
      Display.getDisplay(this).setCurrent(alert);
    }
  }
}

alertCommand を生成し、 addCommand() で追加するのは "Exit" と同じです。 アプリケーション固有の機能なので、コマンドのタイプには Command.ITEM を選択しています。 なお追加先は同じ form オブジェクトなので、リスナの登録を二度行う必要はありません。

紫色 の部分が、アラートを表示させる処理になります。 今回はタイトル (Alert Test) と 表示内容 (Hello World!) だけを指定してオブジェクトを生成しています。 第3引数で画像、第4引数でタイプや警告音を指定できるので、適当に試してみてください。 また setTimeout(5000) で表示時間に 5,000ミリ秒、つまり5秒を設定しています。

HelloWorld MIDlet part5   HelloWorld MIDlet part5 Alert

実行結果です。 "Alert" ボタンをタップすると、5秒間の間メッセージが表示されます。

HelloWorld MIDlet part5   HelloWorld MIDlet part5 Alert

携帯電話ではどんな風に表示されるのでしょうか? デバイスに "DefaultColorPhone" を選択し、実行させてみました。 コマンドが画面上のボタンではなく、左右のキーに割り振られています。 "Alert" 側のキーを押すと、メッセージが全画面に5秒間表示されます。 Alert が表示されている間、それ以前の画面は隠されていることを覚えておきましょう。

画像を表示するサンプルを作ろう!

さてここまでは、Form というベース部品を使い、その上に他の部品を追加してきました。 J2ME には他に Canvas という、より低レベルの処理のベース部品があります。 これを利用したサンプルも作成してみましょう。

おまけコラム: Form と Canvas

ライブラリには抽象度が異なる部品が混在しているものです。 Canvas は Form より抽象度が低い、低レベルな部品です。

一般的に、高レベルの部品で済むならば、 わざわざ低レベルの部品を使う必要はありません。 移植性が下がってしまう可能性があるからです。 例えば Canvas では画面のサイズの違いに気を配る必要があります。

Form には Image も追加することができるので、 グラフィックを使いたいだけならば Canvas を使う必要はありません。 この連載で Canvas を使用しているのは、この先の講座で、 スタイラスでの入力を処理するサンプルを紹介したいからです。

また低レベルの部品を理解しておけば、 それを利用した高レベルの部品を理解し易くなりますしね。 (^-^;



KToolbar で "属性設定(E)" のボタンを押し、設定ウィンドウを開きます。 "MIDlets" のタブで "追加" ボタンを押し、 "FirstStep2" という名前で "HelloWorld2" という名前のクラスを追加します。

Add Application

このように複数のクラスを登録することで、 1つの prc ファイルの中に複数のアプリケーションを入れることができます。 この場合は最初に選択メニューが表示され、 "名前" 欄で指定したアプリケーション名が使用されます。 第1回で試した Demos.prc や Games.prc と同じですね。

さて HelloWorld.java と同様に、 HelloWorld2.java も src ディレクトリに作成しましょう。

HelloWorld2.java (その1)
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;

public class HelloWorld2 extends MIDlet {
  public void startApp() {
    Canvas canvas = new Canvas() {
      protected void paint(Graphics g) {
        g.drawString("Hello World!", 2, 2, g.TOP|g.LEFT);
      }
    };
    Display.getDisplay(this).setCurrent(canvas);
  }
  public void pauseApp() {}
  public void destroyApp(boolean unconditional) {}
}

赤色 の部分が、Canvas クラスを使用している部分です。 単に new すれば使えた Form クラスと異なり、Canvas クラスは継承しないと使えません。 実際にグラフィックの描画を行うのは paint() メソッドなのですが、 Canvas クラスでは何も描画してくれない (中身が空) ので、 実際の処理を記述してあげなければなりません。

実際の処理、とは "HelloWorld!" という文字列を表示することです。 これは drawString() という、文字列を描画するメソッドで実現しています。 最初の引数が表示する文字列、続く2つは表示する位置、 残る2つは表示の基準点 (重心) の指定になります。

さて、実行してみましょう。

FirstStep selection   HelloWorld2 MIDlet part1

ただこのプログラムは、Java に特有の 匿名クラス (anonymous class) という記述方法を使っています。 クラスの定義と使用 (インスタンス生成) を同時に行うもので、 1度しか使用しないクラスを定義するのに便利な記述方法です。 参考のために、匿名クラスを使用しない記述方法も紹介します。 比べてみてください。

HelloWorld2.java (参考)
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;

public class HelloWorld2 extends MIDlet {
  public class MyCanvas extends Canvas {
    protected void paint(Graphics g) {
      g.drawString("Hello World!", 2, 2, g.TOP|g.LEFT);
    }
  }

  public void startApp() {
    Canvas canvas = new MyCanvas();
    Display.getDisplay(this).setCurrent(canvas);
  }
  public void pauseApp() {}
  public void destroyApp(boolean unconditional) {}
}

これだと Java に慣れていない人でも理解し易いのではないでしょうか? 赤色 の部分で MyCanvas というクラスを定義し、 paint() メソッドを再定義しています。 そして 青色 の部分でインスタンスを生成しています。

これら2つのプログラムは同じ動作をしますが、匿名クラスを用いると

  • 1度きりの使い捨てクラスであることが明白
  • MyCanvas など使い捨ての名前を考える必要が無い
  • 使用される場所に近いところに記述があるので、可読性が上がる
等のメリットがあることを、理解していただければ幸いです。

他にもいろいろ表示してみよう!

文字を表示しただけでは、いまいち違いが実感できないと思います。

先ほどのサンプル (匿名クラスの方) に、 幾つか描画命令を追加してみましょう。 全て Graphics クラスのメソッドです。

HelloWorld2.java (その2)
import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;

public class HelloWorld2 extends MIDlet {
  public void startApp() {
    Canvas canvas = new Canvas() {
      protected void paint(Graphics g) {
        g.drawString("Hello World!", 2, 2, g.TOP|g.LEFT);
        g.drawLine( 20, 20, 100, 20);  // 線を描く
        g.setColor(0x00ff0000); // 赤色
        g.fillRect( 20, 30, 80, 70);  // 塗りつぶした四角形を描く
        g.setColor(0x0000ff00); // 青色
        g.fillArc( 25, 35, 70, 60, 0, 240);  // 塗りつぶした円を描く
      }
    };
    Display.getDisplay(this).setCurrent(canvas);
  }
  public void pauseApp() {}
  public void destroyApp(boolean unconditional) {}
}

以下が実行画面です。 KToolbar の環境設定から POSE の表示色数を変更できるので、 4階調グレーと、256色カラーで試してみました。

HelloWorld2 MIDlet part2 (4 gray color)   HelloWorld2 MIDlet part2 (256 color)

いろいろ改造して、楽しんでみてください。

プロジェクトの中身は?

さて、プロジェクトの内容はどうなったでしょうか? c:\j2mewtk\apps\FirstStep の中を調べてみましょう。

 FirstStep
│  ├ bin
│  │  ├ FirstStep.jad
│  │  └ MANIFEST.MF
│  ├ classes
│  │  ├ HelloWorld.class
│  │  ├ HelloWorld2.class
│  │  └ HelloWorld2$1.class
│  ├ lib
│  ├ res
│  ├ src
│  │  ├ HelloWorld.java
│  │  └ HelloWorld2.java
│  ├ tmpclasses
│  │  ├ HelloWorld.class
│  │  ├ HelloWorld2.class
│  │  └ HelloWorld2$1.class
│  └ tmplib

HelloWorld2 を追加したので、 HelloWorld2.javaHelloWorld2.class が増えています。 また匿名クラスを使用したので、 HelloWorld2$1.class というクラスファイルが生成されています。

なお Palm 用のインストールファイル FirstStep.prc は、 c:\j2mewtk\wtklib\devices\PalmOS_Device ディレクトリの中にあります。

おまけコラム: 匿名クラスとファイル名

匿名クラス (anonymous class) には名前が無いため、クラスファイルの名前は Javaコンパイラが自動的に生成します。 具体的には定義されているクラス名、$ という文字、 そして定義された順番を繋げて名前にしているようです。

$ という文字はクラス名に使用できないので、 開発者が定義したクラスと名前が被ることはありません。

でも、匿名クラスを多く使用すると、 不思議な名前のファイルが増えて面倒ですよね。 匿名クラスは他のクラスから使用されないので、 定義したクラスのクラスファイルに格納してくれると便利なのですが。

実は初期の Java には、匿名クラスという概念はありませんでした。 過去のクラスファイルとの互換性を保つため、 苦肉の策で実装された仕組みだと、僕は理解しています。 もしかしたら、実行時の効率 (別ファイルなので、使用するまで読み込む必要が無い?) を考慮したのかもしれません。

まあ実際に配布する時は jar ファイル (Palm だと prc ファイル) にまとめるのが普通です。 なのであまり意識する必要は無いでしょう。



jar ファイルを生成してみる

J2ME 用のプログラムを配布する際、一般的には jar ファイルと jad ファイルの2つを用意します。 prc ファイルならば1つで済むのですが、 Palm 専用になってしまいますから。

KToolbar の "プロジェクト(P)" メニューから、 "パッケージ" を実行してみてください。

Package

FirstStep.jad と同じ bin ディレクトリに FirstStep.jar ファイルが生成されます。

 FirstStep
│  ├ bin
│  │  ├ FirstStep.jad
│  │  ├ FirstStep.jar
│  │  └ MANIFEST.MF
│  ├ classes
│  │  ├ HelloWorld.class
│  │  ├ HelloWorld2.class
│  │  └ HelloWorld2$1.class
│  ├ lib
│  ├ res
│  ├ src
│  │  ├ HelloWorld.java
│  │  └ HelloWorld2.java
│  ├ tmpclasses
│  │  ├ HelloWorld.class
│  │  ├ HelloWorld2.class
│  │  └ HelloWorld2$1.class
│  └ tmplib

jad, jar ファイルは、Palm 用の prc ファイルに変換することができます。 興味のある方は 補足3 J2ME ソフトで遊んでみよう! を参考にしてみてください。

ファイルのダウンロード

今回の講座で作成した prc ファイルです。 まだ完成版では無いので FirstStep_1.prc というファイル名にしてあります。 また zip 圧縮してあります。

リンクを右クリックして "対象をファイルに保存" や "Save Link As..." 等でダウンロードし、解凍して実行してみてください。

ファイル 種類 説明
FirstStep_1.zip Palm用prc 2つのサンプルを含んだPalm用インストールファイル

次回の講座は?

HelloWorld! の改造、楽しんでいただけましたでしょうか? 次回はいままでの知識を応用し、迷路ゲームにチャレンジする予定です。

楽しみましょう! (^-^)/



Copyright (c) 2002 Toshio Yamashita
first version 2002/08/05