JavaアプレットをTcl Blendから呼び出すには

 ここでは普通にWebブラウザにも載せられるJavaアプレットをJacl/Tcl Blend でロードして実行させてみます。 下の例は単に黄色い円が左から右に流れて行く何の変哲もないアプレットで、 最近ではもう面白くもなんともないでしょう。

import java.awt.*;
import javax.swing.*;

public class BallApplet extends JApplet implements Runnable {
    public static final int WIDTH  = 300;
    public static final int HEIGHT = 200;

    Thread thread = null;
    boolean activated = false;
    int x = 0, y = 80, oldx, oldy;

    public void init(){
        getContentPane().setSize(WIDTH, HEIGHT);
        getContentPane().setBackground(Color.black);
        x = 0; y = 80;
    }

    public void start(){
        if(thread == null){
            activated = true;
            thread = new Thread(this); thread.start();
        }
    }

    public void stop(){
        if(thread != null){ activated = false; }
    }

    public void run(){
        while(activated){
            try {
                Thread.currentThread().sleep(100);
            } catch(InterruptedException ex){ break; }
            oldx = x; oldy = y;
            x += 10; if(x >= WIDTH+20) x = -20;
            repaint();
        }
    }

    public void paint(Graphics g){
        g.setColor(getBackground()); g.fillOval(oldx-20, oldy-20, 40, 40);
        g.setColor(Color.yellow);    g.fillOval(x-20, y-20, 40, 40);
    }

    public static void main(String[] args){
        JFrame f = new JFrame("Example");
        BallApplet my = new BallApplet();
        my.init();
        f.setSize(my.WIDTH+5, my.HEIGHT+5);
        f.getContentPane().add(my);
        f.show();
        my.start();
    }
}

 「BallApplet.java」という名前でセーブしてjavacでコンパイルすれば、 JDKのjavaコマンドで動くことが確認できると思います。 「こんなのTcl/Tkで10行で書けるじゃねえか!」って?そ、そうかな?

pack [canvas .can -bg black -width 300 -height 200]
set circ [.can create oval 0 80 40 120 -fill yellow]
proc draw_c {circ x y} {
    if {[incr x 5] > 300} {set x 0}
    .can coords $circ $x $y [expr $x+40] [expr $y+40]
    after 50 draw_c $circ $x $y
}
draw_c $circ 0 80

待て待て!なんだこれわ!たった8行や〜ん。 でもこのままでは話が続かないので、強引に先のJavaコードを使うことにします。 BallApplet.class を環境変数CLASSPATHが通った場所にコピーしておき、 こんなTclスクリプトを作ります。ここでは ss.tcl とします。

package require java

set f [java::new javax.swing.JFrame "サンプル"]
set my [java::new BallApplet]
$my init
$f setSize [expr [$my getWidth]+10] [expr [$my getHeight]+10]
[$f getContentPane] add $my
$f show

java::bind $f windowClosing { puts Bye.; exit }
$my start
vwait zzz

 ん?なんか、言語は違えどやっていることは BallApplet.javaの public static void main() と全く同じですね。同じなんです。 というわけで、実行してみましょう。

shell> jtclsh
% source ss.tcl
%
 出て来ましたよね?よね? このアニメーションを止めるにはJava言語では
  my.stop();
と書きますが、Tcl言語でもやり方は全く同じです。
  $my stop
というわけで、ポイントは Java言語の main() と同じ処理を Tcl言語で書き下せばOKなんです。書き換えは先程から書いている通り、
  instance = new ClassName(args...);
が
  set instance [java::new ClassName $args...]
に、
  r = instance.method(args...)
が
  set r [$instance method $args...]
に変わるだけです。クラスメソッドや変数のアクセスについては先述した通りです。


JavaのAWT GUIを呼び出してみよう

 JavaのAWT GUIのボタンなども、もちろん出せます。 ということで今度はそういうサンプルです。 当サイトの住人もカップめんは嫌いじゃないですよ。

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class RamenListModel extends AbstractListModel {
    String[] a = {"日清食品", "エースコック", "マルちゃん",
                  "明星食品", "寿がきや"};
    public Object getElementAt(int index){ return a[index]; }
    public int getSize(){ return a.length; }
}

public class Ramen extends JApplet {
  public  static int WIDTH  = 200;
  public  static int HEIGHT = 240;
  public  JList   lsta;
  public  JButton cmda;
  public  int    result = -1;
  public  String itemname = "none";

  public Ramen(){
      setSize(WIDTH, HEIGHT);
  }

  public void init(){
      Container c = getContentPane();
      c.setBackground(new Color(192, 128, 192));
      c.setLayout(null);
      JLabel laba = new JLabel("好きなラーメンメーカー");
      c.add(laba); laba.setBounds(20, 10, 150, 30);
      lsta = new JList(new RamenListModel());
      JScrollPane pa = new JScrollPane(lsta);
      c.add(pa); pa.setBounds(10, 50, 180, 120);
      lsta.setSelectedIndex(0);
      cmda = new JButton("食べる");
      c.add(cmda); cmda.setBounds(100, 190, 80, 24);

      /* ここの処理をTcl Blendで書くのが本題 */
      cmda.addActionListener(
        new ActionListener() {
        public void actionPerformed(ActionEvent ev){
          int index = lsta.getSelectedIndex();
          result = index;
          itemname = (String) lsta.getModel().getElementAt(index);
          System.err.println("[" + (index+1) + "]:" +
                             itemname + " is selected.");
        }
      }
    );
  }

  public static void main(String args[]){
    JFrame f = new JFrame("Ramen");
    Ramen my = new Ramen();
    f.setSize(my.WIDTH, my.HEIGHT);
    f.getContentPane().add(my);
    my.init(); my.start(); f.show();
  }
}

これをRamen.javaとして、UNIXなら日本語EUC、WindowsならシフトJISでセーブします。でコンパイル。 もう、あとは同じです。 「食べる」ボタンを押すと現在選択されている項目が標準出力に表示されます。 されます…が、ここで疑問がわいてきます。 上のプログラムの場合、ボタンが押された後の処理もJava世界で行うので、 上のようにクラスの中でイベントリスナーを定義すればよいわけですが、 それでは、そのようなイベント処理をTcl世界で行いたい場合にはどうすれば? 当然、「ボタンが押された」「リストが選択された」 というタイミング(イベント)を、Tcl側で感知できなくてはいけません。 その仕組みを調べるために、下のようなTclスクリプトを実行してみましょう。

package require java
set f [java::new javax.swing.JFrame "Are you Ramenized?"]
set my [java::new Ramen]
[$f getContentPane] add $my
$f setSize [java::field $my WIDTH] [java::field $my HEIGHT]
$my init
java::bind $f windowClosing {
    puts "Good Bye."; exit
}
java::bind $my mouseMoved {
    puts "([java::event x],[java::event y])"
}
set lsta [java::field $my lsta]
java::bind $lsta mouseClicked {
    # イベントを発生させたJavaコンポーネントを取得する。
    set comp [java::event component]
    # そのコンポーネントをjavax.swing.JListにキャスト(強制型変換)する。
    set lsta [java::cast javax.swing.JList $comp]
    # マウスが押された座標を取得する。
    set point [java::event point]
    # その座標に該当する、リストボックスの項目番号(0から)を取得する。
    set index [$lsta locationToIndex $point]
    # リストモデルのメソッドを使って、その項目番号の項目の文字列を取得する。
    set name [[[$lsta getModel] getElementAt $index] toString]
    # どうです。(いばるなよ)
    puts "****** \[$index\] : $name ******"
}
$my start
$f show
vwait zzz
# end.

スクリーンショット

 これを実行すると、ウィンドウのコンテナ部分(紫色の部分) をマウスが動くたびにその座標が表示され、 また、リストのどれかの項目をつつくとその項目が表示されます。 この処理がTclスクリプトの中で行われていることに注意してください。 java::bindというTclコマンドを使うことにより、 JavaのAWTイベントのコールバック処理としてTcl世界のスクリプトを指定することができます。 mouseMovedとかmouseClickedというのは、 AWTイベントのひとつ java.awt.event.MouseEvent が定義しているイベントハンドラメソッドの名前です。
 コールバック内では、java::event というコマンドで、 イベントに関する情報をJavaのイベントオブジェクトから取得できます。 java::eventの最初の引数は、イベントオブジェクトのプロパティの名前です。 は?簡単に言うと、 Java世界の

  event.getXxxxxx()
というメソッドで取得できる情報は、Tcl世界では
  java::event xxxxxx
というコマンドで取得できるということです。 xxxxxxの部分がプロパティ名と呼ばれる部分で、 Javaのgetメソッド(プロパティのアクセサメソッド) の名前においてはその先頭文字が大文字になります。 また、java.awt.event.ComponentEvent.getComponent() は戻り値の型が java.lang.Objectなので、これをキャストするために、Java世界では
  JList lsta = (JList) event.getComponent();
とする必要がありますが、同じことをTcl世界では java::castというコマンドを使って、
  set lsta [java::cast javax.swing.JList [java::event component]]
と書くことができます。
 ということで、java::bindとjava::eventという2つのコマンドを使うことによって、 JavaのAWTイベントを受けたときの処理をTcl言語で書くことも可能です。

 ちなみに話は外れますが、 Tcl Blendが1.2台になってTcl 8.2ベースになったため、 いよいよUnicode対応になりましたが、 JavaのUnicode(UTF-8)とTclのUnicode(UTF-8)はまったく同じ体系になっています。 そのため、上の例の場合、Javaプログラム内のAWT GUIに表示されている 「マルちゃん」などの日本語の文字列がそのままTcl/TkのGUIにも表示できます。 これでますます使い勝手が向上したと言えます。

拡張レビュー分室 top
(first uploaded 1998/12/15 last updated 2002/08/19, MISUMI URANO - KOUKEN HEIJIMA)