■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2007年08月05日

    楽しいJava講座 - 初心者から達人へのパスポート
                  vol.065

                                セルゲイ・ランダウ
 バックナンバー: http://www.flsi.co.jp/Java_text/
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


[このメールマガジンは、画面を最大化して見てください。]


========================================================
◆ 01.グラフィックスのプログラミング
========================================================


今回は、ClockAppletに海外の時刻も同時に表示できるようにし
ましょう。
つまり、いわゆる世界時計の機能を追加することにします。


その前に前提知識を頭に入れておきましょう。

世の中には、「世界標準時」と「タイムゾーン(time zone:時刻帯)」
という言葉が存在しますね。

世界標準時は、元々はイギリスのグリニッジ(Greenwich)天文台
での天体観測によって計られていた時刻であり、グリニッジ標準時
(Greenwich Mean Time、略してGMT)と呼ばれていました(この言葉
自体は今でも使われています)。
しかし現在では、原子時計によって得られる正確な時刻に天文学的
に決める時刻を考慮して調整したUTC(英語=Coordinated Universal Time、
フランス語=Temps Universel Coordonne = 協定世界時)が世界の標準時
として使われるのが普通になりました。
現在では、GMTと表記しても実際はUTCの時刻を表しています。

ところで、地球の自転1回転が1日であることから、15度の回転ごとに
1時間経過することになり、経度が15度違うと時刻が1時間違うことに
なりますね。
しかし実際には、あるまとまった地域では統一された同一の時刻が
使われるようになっており、たとえば中国のような広大な土地を持つ
国でも一つの統一された時刻が使われています。(中国の東端と西端
では、経度の違いだけで計算すると本来は4時間ほども時刻が違うべ
きものなのです。一方、アメリカなどのいくつかの国では、同じ国内
でも地方によって異なる時刻が使われています。)
このように、統一した時刻が使われる、まとまった地域のことをタイム
ゾーン(time zone:時刻帯)といいます。また、その地域で統一された
時刻のことを「(地方の)標準時((Local) Standard Time)」と呼び
ます。
地方の標準時はGMTから何時間進んでいるか、あるいは遅れているかで
表すこともでき、例えば3時間進んでいるのであればGMT+3、5時間遅れ
ているのであればGMT-5というふうに表記することがあります。

詳しく知りたい人は、ウィキペディア(http://ja.wikipedia.org/
などを使って自分で調べてください。


では、プログラミングに入ることにしましょう。
最初に海外の時計を表示するためのJPanelを用意し、そこにClockPicture
を組み込み、海外のタイムゾーンを選択するためのJComboBoxを貼り付け
ることにします。
そして、選択されたタイムゾーンの時刻でClockPictureに時計を描かせる
ことにします。


Eclipseを起動して、作業を始めましょう。

jp.co.flsi.lecture.clockパッケージの中に海外の時計を表示するための
WorldClockJPanelというクラスをJPanelのサブクラスとして、ビジュアル・
クラス(Visual Class)として作成しましょう。
つまり、パッケージ・エクスプローラーの中のJStudy1の中のjp.co.flsi.lecture.clock
を右クリックし、「新規」→「その他」→(「Java」の中の)「Visual Class」
を選択し「次へ」ボタンをクリックし、「名前」欄にWorldClockJPanelと入力し、
「Style」欄で(Swingの中の)Panelを選択しましょう。また、「インターフェース」欄
にRunnable(java.lang.Runnable)を追加しておきます。(これはclass文に
「implements Runnable」を指定することを意味します)
そして、「Inherited abstract methods」にチェック・マークを入れておいてから、
「終了」ボタンをクリックします。

WorldClockPanelのVEエディターが開きましたら、最初にソース・コードの
initialize()メソッドの中の
--------------------------------------------------------
this.setSize(300, 200);
--------------------------------------------------------
の行を
--------------------------------------------------------
this.setSize(130, 140);
--------------------------------------------------------
に変更しておきましょう。
(もしくは、「プロパティー」ビューでsizeプロパティーの値を130,140に
変更するというやり方もあります。)

続いて、このパネルのLayoutManagerをnullにしておきましょう。すなわち
JPanelの画像を右クリックして「Set Layout」→「null」を選択します。

では、このパネルにClockPictureを組み込みましょう。
パレットから「Choose Bean」を選択し、「Choose a Bean」ウインドウにお
いてClockPictureを入力して「OK」ボタンをクリックし、パネルの画像の外側
の白いところ(空白のエリア)をクリックすることによって貼りつけます。
Bean NameはclockPictureのままにしておきましょう。

そうするとgetClockPicture()というメソッドが自動的に生成されますね。
このメソッドを使えば、ClockPictureのインスタンスを取り出すことがで
きます。

では、次にこのパネルにJComboBoxを貼り付けましょう。
パネル上の上端あたりに貼り付けてください。
Bean NameはjComboBoxTimeZoneにしておきましょう。
できるだけ(パネルの幅いっぱいまで)jComboBoxTimeZoneの幅を広げて
おいてください。

次のようなgetJComboBoxTimeZone()メソッドが生成されていますね。
--------------------------------------------------------
private JComboBox getJComboBoxTimeZone() {
   if (jComboBoxTimeZone == null) {
      jComboBoxTimeZone = new JComboBox();
      jComboBoxTimeZone.setBounds(new Rectangle(4, 4, 123, 25));
   }
   return jComboBoxTimeZone;
}
--------------------------------------------------------
ただし、数字はjComboBoxTimeZoneの幅の広げ方によって異なります。

これを次のように編集しましょう。
--------------------------------------------------------
private JComboBox getJComboBoxTimeZone() {
   if (jComboBoxTimeZone == null) {
      jComboBoxTimeZone = new JComboBox();
      jComboBoxTimeZone.setBounds(new Rectangle(4, 4, 123, 25));
      String [] timeZones = TimeZone.getAvailableIDs();
      for (String timeZone : timeZones) {
         jComboBoxTimeZone.addItem(timeZone);
      }
   }
   return jComboBoxTimeZone;
}
--------------------------------------------------------

ここでTimeZoneというのはタイムゾーンを表現するクラスで、java.utilパッ
ケージにはいっています。
また、TimeZoneのgetAvailableIDs()というstaticメソッドはタイムゾーンの
識別名(time zone ID = タイムゾーンID)の文字列を配列にして返すメソッ
ドで、これを使えばTimeZoneクラスで使用可能なタイムゾーンの名前のリス
トが得られます。

ところで、JComboBoxに表示されるフォントのデフォルトはボールド(bold
= 太字)でサイズが12ポイントになっていますが、これをplain(普通の文字)
で10ポイントに変更することによって、なるべくたくさんの文字が表示でき
るようにしておきましょう。
「Java Beans」ビューの中でjComboBoxTimeZoneをクリックし、
「プロパティー」ビューの中でfontプロパティーを選択してください。
その右側の値欄にDialog,bold,12という値が表示されているはずですが、
さらにその右端にあるボタンをクリックしてください。
「Java Property Editor」が開きますね。そこで、Styleはplainを選択し、
Sizeは10を選択して「OK」ボタンをクリックしてください。
終わったら保管(Ctrl + S)しておいてください。

パッケージ・エクスプローラーからWorldClockJPanelを右クリックし、
「実行」→「Java Bean」を選択して、どのように見えるか確認してみて
ください。
フォントのサイズを6ポイントくらいに小さくすれば、すべてのタイムゾーン
の名前が収まって表示できるようになると思いますが、小さすぎて読みにくく
なります(というより、読めなくなります)。読みやすく、かつ、すべての
タイムゾーンの名前が収まるようにするためには、パネル自体をもう少し広げ
てやったほうがいいですが、面倒くさいので省略します。気になる人は自分で
調整してください。

では、このjComboBoxTimeZoneで選択したタイムゾーンの値を世界時計を表示
するときに渡せるようにフィールドに保管するようにしましょう。
そのために以下のフィールドの定義をWorldClockJPanelのソース・コードに
追加してください。
--------------------------------------------------------
private String timeZoneId = null;
--------------------------------------------------------

続いて、jComboBoxTimeZoneで選択が行われたら、その選択されたタイムゾーン
がtimeZoneIdフィールドに代入されるようにプログラミングしましょう。
「Java Beans」ビューのjComboBoxTimeZoneを右クリックし、
「Events」→「actionPerformed」を選択します。

getJComboBoxTimeZone()メソッドの中に次のようなコードが自動生成されましたね。
--------------------------------------------------------
jComboBoxTimeZone.addActionListener(new java.awt.event.ActionListener() {
   public void actionPerformed(java.awt.event.ActionEvent e) {
      System.out.println("actionPerformed()"); // TODO Auto-generated Event stub actionPerformed()
   }
});
--------------------------------------------------------
これを次のように編集しましょう。
--------------------------------------------------------
jComboBoxTimeZone.addActionListener(new java.awt.event.ActionListener() {
   public void actionPerformed(java.awt.event.ActionEvent e) {
      timeZoneId = (String) jComboBoxTimeZone.getSelectedItem();
      repaint();
   }
});
--------------------------------------------------------


次に、時計の画像を描画するためにpaintComponent()メソッドを実装しましょう。
あとでこのパネルにJComboBoxを貼り付けますので、paint()メソッドではなく
paintComponent()メソッドをオーバーライドしなければならないのです。
意味わかりますね。忘れた人は、vol.060を復習してください。
下記のようなpaintComponent()メソッドをWorldClockJPanelのソース・コードに
追加してください。
--------------------------------------------------------
public void paintComponent(Graphics g) {
   Image image = createImage(130, 140);
   Graphics2D g2dImage = (Graphics2D) image.getGraphics();
   GregorianCalendar newTime = new GregorianCalendar(TimeZone.getTimeZone(timeZoneId));
   getClockPicture().setCurrentTime(newTime);
   getClockPicture().setCenterX(65);
   getClockPicture().setCenterY(85);
   getClockPicture().setRadius(35);
   getClockPicture().drawAll(g2dImage);
   g.drawImage(image, 0, 0, 130, 140, null);
}
--------------------------------------------------------
ここで、new GregorianCalendar(TimeZone.getTimeZone(timeZoneId))という
ふうにGregorianCalendarのコンストラクターを呼び出してやると、
timeZoneIdに指定した識別名のタイムゾーンにおける現在時刻を持っ
たGregorianCalendarインスタンスが生成されます。

あとは、ClockJPanelをプログラミングしたときとほとんど同じプログラ
ミングで済むのですが、ただ、海外の時刻を示す時計は日本の時刻を示す時計
よりも小さくすることによって区別したいので、そのために半径と中心の座標
を調整しておきます。それが、ClockPictureのsetCenterX()メソッド、
setCenterY()メソッド、setRadius()メソッドを呼び出している理由です。


では、ClockJPanelをプログラミングしたときと同様に、
フィールド
--------------------------------------------------------
private boolean runningFlag = false;
--------------------------------------------------------
をWorldClockJPanelのソース・コードに追加しましょう。

そして、run()メソッド(メソッド・スタブが自動生成されている)
を下記のように編集しましょう。
--------------------------------------------------------
public void run() {
   while(runningFlag) {
      repaint();
      try {
         Thread.sleep(100);
      }
      catch(InterruptedException e) {
        
      }
   }
}
--------------------------------------------------------

またClockJPanelのプログラミングのときと同様に、
下記の2つのメソッド
--------------------------------------------------------
public void clockStart() {
   runningFlag = true;
   Thread aThread = new Thread(this);
   aThread.start();
}
--------------------------------------------------------

--------------------------------------------------------
public void clockStop() {
   runningFlag = false;
}
--------------------------------------------------------

WorldClockJPanelのソース・コードに追加しましょう。


では、WorldClockJPanelを保管しておいてから、ClockAppletの
VEエディターを開いてください。

パレットから「Choose Bean」を選択し、「Choose a Bean」ウイン
ドウにおいてWorldClockJPanelを入力して「OK」ボタンをクリックし、
アプレットの画像の中の空いているところ(前回AlarmJPanelを貼り付け
たところの下)に貼り付け(クリック)してください。
Bean NameはworldClockJPanelのままにしておきましょう。

そうするとgetWorldClockJPanel()というメソッドが自動的に生成
されますね。
このメソッドの中の
--------------------------------------------------------
worldClockJPanel.setBounds(new Rectangle(187, 83, 10, 10));
--------------------------------------------------------
という行(数字は貼り付け位置によって異なります)を
--------------------------------------------------------
worldClockJPanel.setBounds(new Rectangle(170, 60, 130, 140));
--------------------------------------------------------
に書き換えましょう。


次に、ClockAppletのinit()メソッドとdestroy()メソッドを編集して
WorldClockJPanelの時計のスレッドを起動するコードと停止するコード
を追加しましょう。

すなわち、init()メソッドを
--------------------------------------------------------
public void init() {
   this.setSize(300, 200);
   this.setContentPane(getJContentPane());
   getClockJPanel().clockStart();
   AudioClip audioClip = getAudioClip(getDocumentBase(), "alarm01.au");
   getAlarmJPanel().setAudioClip(audioClip);
   getWorldClockJPanel().clockStart();
}
--------------------------------------------------------
のように編集し、

destroy()メソッドを
--------------------------------------------------------
public void destroy() {
   getClockJPanel().clockStop();
   getWorldClockJPanel().clockStop();
}
--------------------------------------------------------
のように編集しましょう。

(何を編集したのかわかりますね。それぞれgetWorldClockJPanel().clock...
の行が追加されただけですよ。)


では、ClockAppletを保管して起動し、テストしてみてください。
タイムゾーンをいろいろ選択してみてください。世界時計の時刻
が変わりますね。

ちなみに同じ標準時のタイムゾーンがいくつか並んでいたりするので、
タイムゾーンの選択を変えてみても時刻が変わらない場合がたくさん
あります。
たとえば、Asia/TokyoとJST(=Japan Standard Time)とJapanは同じもの
です。このうちのどれを選択しても日本の時刻になりますね。
また、中国の国内はどこでも同じ標準時を使っているのにそのタイムゾーン
ときたら、CTT(China Taiwan Time)、PRC(People's Republic of China)か
ら始まって、Asia/Shanghai(上海)、Asia/Harbin(ハルピン)、・・・と、
全部で11個くらいのタイムゾーン識別名があります。・・・・・
というふうに、このタイムゾーン識別名のリストは無駄が多いし、わかりにくい
と感じるでしょうから、もっとわかりやすく整理されたリストを作ってみた
い人は、自分でプログラムを改良してみてください。
なお、どのタイムゾーン識別名(Time zone ID)がどの地域のものかわからない
という人は、下のリンクをクリックしてみてください。(下記リンクのWebページは
当メールマガジンとは何の関係もありません。)

Time zone IDs


ところで、この世界時計、午前と午後の区別がつきませんね。それに日付
もわからない。
WorldClockJPanelにもClockJPanelと同じように日付と現在時刻を文字列で
表示させるようにすればこの問題は解決しますが、それでは芸がない(と
いうより、本来はClockJPanelを再利用したほうが速いのだが、ここでは練習
のために重複的でも手でプログラミングしておきたい)ので、日にちと
AM/PMだけの表示を追加してみましょう。
また、ついでにAMかPMかによって世界時計の背景の色を変えるようにして、
世界時計の領域を飾りつけしてみましょう。

では、WorldClockJPanelのpaintComponent()メソッドを
--------------------------------------------------------
public void paintComponent(Graphics g) {
   Image image = createImage(130, 140);
   Graphics2D g2dImage = (Graphics2D) image.getGraphics();
   GregorianCalendar newTime = new GregorianCalendar(TimeZone.getTimeZone(timeZoneId));
   String ampm;
   Color backColor;
   if (newTime.get(Calendar.AM_PM) == Calendar.AM) {
      ampm = "AM";
      backColor = Color.ORANGE;
   }
   else {
      ampm = "PM";
      backColor = Color.CYAN;
   }
   g2dImage.setColor(backColor);
   g2dImage.fillRect(0, 0, 130, 140);
   g2dImage.setColor(Color.BLACK);
   g2dImage.drawString(ampm , 100, 50);
   g2dImage.drawString(newTime.get(Calendar.DAY_OF_MONTH) + "日" , 10, 50);
   getClockPicture().setCurrentTime(newTime);
   getClockPicture().setCenterX(65);
   getClockPicture().setCenterY(85);
   getClockPicture().setRadius(35);
   getClockPicture().drawAll(g2dImage);
   g.drawImage(image, 0, 0, 130, 140, null);
}
--------------------------------------------------------
のように編集してみましょう。面倒くさいので、paintComponent()メソッド
のソース・コードをすべて提示しましたが、どこにどんなコードが追加され
たのかわかりますね。


では、ClockAppletを保管して再度、起動し、テストしてみてください。
タイムゾーンをいろいろ選択してみてください。今度は世界時計に日にちと
AM/PMの表示が出ますね。また、AMかPMかによって、世界時計の背景の色が
変わりますね。
(ちょっと色の趣味がよくなかったかも知れません。気になる人は自分で調
整してください。)


では、今回はここまでにします。なお、今回作成したアプレットもホーム
ページにも掲載しておきますので、完成したアプレットの動きを確認した
い人は下記ホームページのvol.065の項目から実行してみてください。

http://www.flsi.co.jp/Java_text/

何か、わからないところがありましたら、下記のWebページまで質問を
お寄せください。


(続く)



========================================================
◆ 02.Java(文法等)解説 [カスタム・タイムゾーンID]
========================================================

[カスタム・タイムゾーンID]

タイムゾーンを指定するときに、今回のプログラムのようにタイム
ゾーンの識別名を使わずに、GMTからの時間のずれで指定することも
できます。
そのためには、カスタム・タイムゾーンIDを使用します。

カスタム・タイムゾーンIDは、
--------------------------------------------------------
GMT 符号 時 : 分
または、
GMT 符号 時 分
または、
GMT 符号 時
--------------------------------------------------------
の構文で記述されます。
(ここで、符号は + - のどちらか、時は1桁または2桁の数字、
 分は2桁の数字です)

たとえば、GMTより8時間早いタイムゾーンであれば"GMT+8:00"
または"GMT+0800"または"GMT+8"と記述し、GMTより11時間遅れ
たタイムゾーンであれば"GMT-11:00"または"GMT-1100"または
"GMT-11"と記述します。

たとえば、日本の標準時はGMTより9時間進んでいますから、
TimeZone.getTimeZone("GMT+9")
としてTimeZoneオブジェクトを作ってやればJSTと同じタイム
ゾーンになります。
したがって、日本の標準時での現在時刻を表すGregorianCalendar
インスタンスを生成するためには、
--------------------------------------------------------
new GregorianCalendar(TimeZone.getTimeZone("GMT+9"))
--------------------------------------------------------
というコードでもかまわないのです。
ただし、日本のタイムゾーンは予めパソコンに設定されている
(パソコンの設定が正しければ)ので、いちいちタイムゾーン
を指定しなくてもデフォルトで日本のタイムゾーンが選ばれて
います。したがって、
--------------------------------------------------------
new GregorianCalendar()
--------------------------------------------------------
というコードでも日本のタイムゾーンが使用されるのです。


ちなみに英国(ロンドン)の標準時には夏時間(サマー・タイム、
イギリス英語ではsummer time、アメリカ英語ではdaylight saving time)
がありますので、現在は(夏時間なので)GMTより1時間進んでいます。
GMTと同じではありませんので、注意してください。
(GMTには夏時間はありません。)
上のClockAppletに組み込んだ世界時計でEurope/Londonを指定すると、
GMTより1時間進んだ時刻になるはずです。つまり、このプログラムで
使ったタイムゾーンの識別名は夏時間も反映したものなのです
(つまり、これを使えば夏時間も自動的に計算されるのです)。
その点、カスタム・タイムゾーンIDには夏時間の概念は含まれません
ので、注意してください。


(続く)



以上、今回は
┌───────────────────────────┐
・使用できるタイムゾーンの調べ方(TimeZoneのgetAvailableIDs()メソッドの使用方法)
・タイムゾーンを指定して現在時刻を取得する方法(new GregorianCalendar(TimeZone.getTimeZone(timeZoneId)))
・現在時刻のAM/PMの取得方法(Calendarのget(Calendar.AM_PM)メソッドの使用方法)
・描画の背景色の指定の仕方
・カスタム・タイムゾーンIDの記述方法
└───────────────────────────┘
を学習しました。
では、また来週。



================================================
◆ 03.演習問題
================================================

カスタム・タイムゾーンIDの記述方法を確認するために、下記のような
プログラムを作成して実行してみてください。

--------------------------------------------------------
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;

public class TimeZoneTest {

   public static void main(String[] args) {
      GregorianCalendar newTime;
      newTime = new GregorianCalendar();
      System.out.println("デフォルト");
      System.out.println(newTime.get(Calendar.DAY_OF_MONTH) + "日"
            + newTime.get(Calendar.HOUR_OF_DAY) + "時" + newTime.get(Calendar.MINUTE) + "分");
     
      newTime = new GregorianCalendar(TimeZone.getTimeZone("JST"));
      System.out.println("JST");
      System.out.println(newTime.get(Calendar.DAY_OF_MONTH) + "日"
            + newTime.get(Calendar.HOUR_OF_DAY) + "時" + newTime.get(Calendar.MINUTE) + "分");
     
      newTime = new GregorianCalendar(TimeZone.getTimeZone("GMT+9"));
      System.out.println("GMT+9");
      System.out.println(newTime.get(Calendar.DAY_OF_MONTH) + "日"
            + newTime.get(Calendar.HOUR_OF_DAY) + "時" + newTime.get(Calendar.MINUTE) + "分");
     
      newTime = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
      System.out.println("GMT");
      System.out.println(newTime.get(Calendar.DAY_OF_MONTH) + "日"
            + newTime.get(Calendar.HOUR_OF_DAY) + "時" + newTime.get(Calendar.MINUTE) + "分");
     
      newTime = new GregorianCalendar(TimeZone.getTimeZone("Europe/London"));
      System.out.println("Europe/London");
      System.out.println(newTime.get(Calendar.DAY_OF_MONTH) + "日"
            + newTime.get(Calendar.HOUR_OF_DAY) + "時" + newTime.get(Calendar.MINUTE) + "分");

      newTime = new GregorianCalendar(TimeZone.getTimeZone("GMT+1"));
      System.out.println("GMT+1");
      System.out.println(newTime.get(Calendar.DAY_OF_MONTH) + "日"
            + newTime.get(Calendar.HOUR_OF_DAY) + "時" + newTime.get(Calendar.MINUTE) + "分");
     
      newTime = new GregorianCalendar(TimeZone.getTimeZone("GMT+01:00"));
      System.out.println("GMT+01:00");
      System.out.println(newTime.get(Calendar.DAY_OF_MONTH) + "日"
            + newTime.get(Calendar.HOUR_OF_DAY) + "時" + newTime.get(Calendar.MINUTE) + "分");

      newTime = new GregorianCalendar(TimeZone.getTimeZone("GMT+0100"));
      System.out.println("GMT+0100");
      System.out.println(newTime.get(Calendar.DAY_OF_MONTH) + "日"
            + newTime.get(Calendar.HOUR_OF_DAY) + "時" + newTime.get(Calendar.MINUTE) + "分");
   }

}
--------------------------------------------------------



┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
★ホームページ:
      http://www.flsi.co.jp/Java_text/
★このメールマガジンは
     「まぐまぐ(http://www.mag2.com)」
 を利用して発行しています。
★バックナンバーは
      http://www.flsi.co.jp/Java_text/
 にあります。
★このメールマガジンの登録/解除は下記Webページでできます。
      http://www.mag2.com/m/0000193915.html
★このメールマガジンへの質問は下記Webページにて受け付けて
 います。わからない所がありましたら、どしどしと質問をお寄
 せください。
      http://www.flsi.co.jp/Java_text/
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Copyright (C) 2007 Future Lifestyle Inc. 不許無断複製