Deep Side of Java〜Java 言語再入門 第4回 〜 アプレット、スレッド、AWT

GridBagLayout サンプル Applet のソース

GridBagLayout をいろいろ実験するためのアプレットのソースである。

このプログラムは次の構成になっている。

GBCApplet.java GBCSample.java
形式的なアプレット、スタンドアロンGUIのメインプログラムである。


GBCTotalTest.java
これが全体のパネルになる。


ChoiceManagedObject.java
抽象的に任意のクラスについて、そのフィールドを外部からいじるクラスである。Choice または Label ウィジットを作って返す Builder でもある。


つまり、このプログラムのミソは、「任意のクラス」について、それが持つフィールドをいじることができるような Choice ウィジットを Build するところにある。そのために、java.lang.reflect パッケージを活用している。なかなか凝ったプログラムなので、楽しみにしてほしい。


GBCApplet.java GBCSample.java

まあ、これはつまらないクラスである。単なるメインクラスに過ぎない。Applet 用の GBCApplet.java は次の通り。

import java.awt.*;
import java.awt.event.*;

public class GBCSample extends Frame {
    public static void main( String [] argv ) {
        new GBCSample();
    }

    private GBCSample( ) {
        super( "GridBagConstraints Total Test" );
        addWindowListener( new WindowAdapter() {
                public void windowClosing( WindowEvent we ) {
                    System.exit( 0 );
                }
            } );
        GBCTotalTest gbctt = new GBCTotalTest();
        setLayout( new BorderLayout() );
        add( "Center", gbctt );
        pack();
        show();
    }
}

スタンドアロンGUI用の GBCSample.java は次の通り。

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

public class GBCApplet extends Applet {
    public void init( ) {
        GBCTotalTest gbctt = new GBCTotalTest();
        setLayout( new BorderLayout() );
        add( "Center", gbctt );
    }
}

特にコメントも不要であろう。


GBCTotalTest.java

さて、具体的なアプリケーションとして、全体のGUIを実現するのがこのクラスである。あまり大した処理はしていない。まじめにウィジットを初期化して配置しているだけである。

ここで GBCTotalTest クラスと、ChoiceManagedObject クラスとの関係は、デザインパターンでいう「Builder」である。ChoiceManagedObject クラスのメソッド nextComponet() は、選択項目が1つしかない場合には java.awt.Label クラスを、いくつもある場合には java.awt.Choice クラスを返す。つまり、実際に生成するインスタンスのクラスが条件によって異なるのである。だから、それを受ける変数のクラスは両者の基底クラスである java.awt.Componet で定義している。

import java.awt.*;
import java.awt.event.*;

public class GBCTotalTest extends Panel implements ActionListener {
    private GridBagLayout gbl;  /* サンプル領域用の GridBagLayout を保存する */
    private Panel base;         /* サンプル領域のパネルである */
    private Button [] but = new Button [9];  /* GridBagLayout でレイアウトされるサンプル */

    /* さて、これが面白い仕事をするクラスである */
    private ChoiceManagedObject [] cmo = new ChoiceManagedObject [9];

    /* 初期化用に文字列配列を定義しておく */
    static private String [][] lines = { 
        /* フィールド名, 初期値, 取りうる可能な値をコンマで区切って繋げたもの */
        { "gridx", "0", "0,1,2,RELATIVE" },
        { "gridy", "0", "0,1,2,RELATIVE" },
        { "gridwidth", "1", "1,2,3,RELATIVE,REMAINDER" },
        { "gridheight", "1", "1,2,3,RELATIVE,REMAINDER" },
        { "weightx", "0.0", "0.0,1.0,2.0" },
        { "weighty", "0.0", "0.0,1.0,2.0" },
        { "anchor", "CENTER","CENTER,NORTH,NORTHEAST,EAST,SOUTHEAST,SOUTH," +
                             "SOUTHWEST,WEST,NORTHWEST" },
        { "fill", "NONE", "NONE,HORIZONTAL,VERTICAL,BOTH" },
        { "ipadx", "0", "0,5,10,20" },
        { "ipady", "0", "0,5,10,20" }
    };


    /* Panel としてのコンストラクタ */
    public GBCTotalTest( ) {
        setLayout( new BorderLayout() );
        Panel table = new Panel();
        table.setLayout( new GridLayout( 2, 2 ) );
        setFont( new Font( "Dialog", Font.PLAIN, 10 ) );

        /* さて、9つのサンプルがあるので、それらの個々に GridBagConstraints がある。*/
        for( int j = 0; j < 9; j++ ) {
            /* ChoiceManagedObject は、Choice ウィジットで任意のクラスのフィールドをいじることが
               できるようにするものである。だから、動的ローディングで与え、一種の Factory 風の
               使い方をする。 正確には、1つのクラス ChoiceManagedObject から、そのコンストラクタ
               に与えた引数のインスタンスを取得するメソッド getObject() と、それを設定するための 
               Choice ウィジットを取得するメソッド nextComponent() があるのである。*/

            cmo[j] = new ChoiceManagedObject( "java.awt.GridBagConstraints" );
            for( int i = 0; i < lines.length; i++ ) {
                /* defineLine で注目するフィールドを伝え、その初期値と取りうる可能な値を伝える */
                cmo[j].defineLine( lines[i][0], lines[i][1], lines[i][2] );
            }
        }

        base = new Panel();  /* フライングして先にサンプル用パネルだけ作っておく */
        table.add( base );

        /* 各操作用テーブルを作る */
        for( int j = 0; j < 3; j++ ) {
            Panel p = createPanel( j * 3 );
            table.add( p );
        }
        add( "Center", table );

        setWidget( base );  /* サンプルたちをそのパネルの中に作る */

        Button b = new Button( "do Layout!" );
        b.addActionListener( this );
        add( "South", b );
    }

    /* 各操作用テーブルを作る */
    private Panel createPanel( int num ) {
        Panel p = new Panel();
        p.setLayout( new GridLayout( lines.length + 1, 4 ) );

        /* まず一番上のサンプル名の見出し */
        p.add( new Label( " " ) );
        for( int j = 0; j < 3; j++ ) {
            p.add( new Label( "項目" + (num + j) ) ); 
        }

        /* 個々のテーブルを作る。Iterator 風のインターフェイス */
        for( int i = 0; cmo[num].hasMoreComponents(); i++ ) {
            p.add( new Label( lines[i][0], Label.CENTER ) );  /* フィールド名の見出し */
            for( int j = 0; j < 3; j++ ) {
                /* nextComponent メソッドは、取りうる可能性が複数ならば Choice ウィジットを、
                   1つならば Label ウィジットを返す。だから戻り値型は Component クラスである。*/
                Component c = cmo[num + j].nextComponent();
                p.add( c );
            }
        }
        return p;
    }

    /* サンプルを作る */
    private void setWidget( Panel p ) {
        gbl = new GridBagLayout();
        p.setLayout( gbl );
        try {
            for( int i = 0; i < 9; i++ ) {
                but[i] = new Button( "項目" + i );
                but[i].setFont( new Font( "Dialog", Font.BOLD, 14 ) );
                /* getObject() メソッドは、コンストラクタで指定されたクラスのインスタンスを、
                   現在の Choice 内容で値を与えて返す。*/
                GridBagConstraints o = (GridBagConstraints)cmo[i].getObject();
                gbl.setConstraints( but[i], o );
                p.add( but[i] );
            }
        } catch( Exception e ) {  /* 結構多くの例外を getObject は投げる */
            e.printStackTrace();
        }
    }


    /* do Layout! ボタンが押されたら */
    public void actionPerformed( ActionEvent ae ) {
        try {
            /* すべてのサンプルに対して、新規にその Choice 内容による GridBagConstraints 
               を取得し、サンプルに与える。*/
            for( int i = 0; i < 9; i++ ) {
                GridBagConstraints o = (GridBagConstraints)cmo[i].getObject();
                gbl.setConstraints( but[i], o );
            }
            /* レイアウトし直して、再表示 */
            gbl.layoutContainer( base );
            repaint();
        } catch ( Exception e ) {  /* 結構多くの例外を getObject は投げる */
            e.printStackTrace();
        }
    }
}

ChoiceManagedObject.java

さて、一番面白いクラスである。任意のクラスについてコンストラクタで受け取ったクラス名から、その public なフィールドを操作する Choice ウィジットを制作し、現在の Choice ウィジットの選択値からそのクラスのインスタンスを返すものである。

このように抽象的に書く場合には、Java 1.1 で加わった java.lang.reflection パッケージを使う。このパッケージはうまく使えば、インタプリタなどで組み込み関数を実装するのに便利であるし、あるいは ClassLoader と連係させて任意のパスにある「プラグイン」から動的に何かの機能を呼び出すなんていう処理ができてしまう。java.lang.reflection クラスを知らないあなた、反省である。

import java.awt.*;
import java.lang.reflect.*;
import java.util.*;

public class ChoiceManagedObject {
    Class objClass;  /* 作成するクラスを保持 */
    Object obj;      /* 作成されたインスタンス */
    Vector symbol;   /* いじるフィールド名 */
    Vector canTake;  /* そのフィールドの初期値 */
    Vector nowValue; /* そのフィールドに設定可能な値のコンマで区切られたリスト */
    Vector compo;    /* Choice か Label ウィジット */
    int now;         /* Iterator 用の現在注目しているオブジェクト */

    /* 動的ローディング Factory 風のコンストラクタ */
    public ChoiceManagedObject( String cls ) {
        try {
            objClass = Class.forName( cls );
            obj = objClass.newInstance();
        } catch( Exception e ) {  /* あまりコンストラクタで例外を投げたくない */
            obj = null;
        }
        symbol = new Vector();
        canTake = new Vector();
        nowValue = new Vector();
        compo = new Vector();
        now = 0;
    }

    /* 内部で保持するデータベースにデータを追加 */
    /* JDK 1.1 なので、Vector.add() が使えないので、addElemet を使う */
    public void defineLine( String sym, String init, String can ) {
        symbol.addElement( sym );
        nowValue.addElement( init );
        canTake.addElement( can );
    }

    /* Iterator 風にウィジット取得をする */
    public boolean hasMoreComponents( ) {
        if( now < symbol.size() ) {
            return true;
        } else {
            return false;
        }
    }

    /* Iterator 風にウィジット取得をする */
    public Component nextComponent( ) {
        Component ret;
        String  nowv = (String)nowValue.elementAt(now);  /* 初期値 */
        /* 設定可能な値をバラす */
        StringTokenizer st = new StringTokenizer( 
                                     (String)canTake.elementAt(now), "," );

        if( st.countTokens() <= 1 ) {  /* もし設定可能な値が1かセットされていない */
            ret = new Label( nowv );   /* その時は初期値の Label ウィジットを返す */
        } else {                       /* 設定可能な値が複数 */
            Choice c = new Choice();   /* Choice オブジェクトに */
            while( st.hasMoreTokens() ) {
                String s = st.nextToken();
                s = s.trim();
                c.addItem( s );       /* 設定可能な値をセットして選べるようにする */
                /* 初期値にそれが相当していれば、それを Choice の初期値にする */
                if( s.equals( nowv ) ) {
                    c.select( s );    
                }
            }
            ret = c;
        }
        compo.addElement( ret );  /* 作成したウィジットを Vector に取っておく */
        now++;
        return ret;
    }

    /* 使用している java.lang.reflection パッケージの解説 

       このプログラムではフィールドしか見ないので、Field クラスしか使っていない。

       まず、Class クラスのインスタンスから、getField( String フィールド名) メソッド
       によって、指定した名前の Field クラスのインスタンスを取得する。
       このメソッドは親クラスなどの継承関係とは無関係に、存在するフィールドは全部検索
       してくれるのでありがたい。

       Field クラスのインスタンスでは、次のメソッドが使える。

       Class getType()。そのフィールドの型を取得する。Class クラスなので面倒だから、
       プログラムでは toString で文字列にして判定している。

       int getInt( Object o ) など。具体的なインスタンスからそのフィールドの値を取得。

       void setInt( Object o, int val ) など。具体的なインスタンスに値を設定。

       get〜, set〜 は各プリミティブ型用にすべて用意されている。まだ参照型用には get, 
       set がある。
     */


    /* 現在 Choice で選択されている値で、インスタンスをセットして返す */
    public Object getObject( )  throws NoSuchFieldException, NumberFormatException, 
                                                             IllegalAccessException {
        String sel;
        String sym;
        /* 取っておいたウィジットから */
        for( int i = 0; i < compo.size(); i++ ) {
            Component at = (Component)compo.elementAt( i );
            if( at instanceof Label ) {
                sel = ((Label)at).getText();
            } else if( at instanceof Choice ) {
                sel = ((Choice)at).getSelectedItem();
            } else {
                return null;
            }
            /* 現在の設定値が String sel に入る */

            sym = (String)symbol.elementAt(i);    /* フィールド名 */

            /* リフレクションによって、そのフィールドを取得。
               ここで例外を投げる可能性があるが、それは呼び出し側に任せる */
            Field tgt = objClass.getField( sym ); 

            /* これは static final シンボル対策が含まれる */
            try {
                Field sval = objClass.getField( sel );
                /* もし、設定値がシンボルならば、その値を取得してセットする */
                setObjectItemSymbol( tgt, sval );
            } catch( NoSuchFieldException  e ) {
                /* もし、設定値がシンボルでないならば、多分即値である */
                setObjectItemValue( tgt, sel );
            }
        }
        return obj;
    }

    /* 設定値が static final されたシンボルの場合には */
    private void setObjectItemSymbol( Field nam, Field val ) throws NoSuchFieldException, 
                                                                  IllegalAccessException {
        String type = nam.getType().toString();

        /* 受け側の型に応じた処理をする。ややダサいが仕方ない */
        if( type.equals( "int" ) ) {
            nam.setInt( obj, val.getInt( obj ) );
        } else if( type.equals( "byte" ) ) {
            nam.setByte( obj, val.getByte( obj ) );
        } else if( type.equals( "char" ) ) {
            nam.setChar( obj, val.getChar( obj ) );
        } else if( type.equals( "double" ) ) {
            nam.setDouble( obj, val.getDouble( obj ) );
        } else if( type.equals( "float" ) ) {
            nam.setFloat( obj, val.getFloat( obj ) );
        } else if( type.equals( "long" ) ) {
            nam.setLong( obj, val.getLong( obj ) );
        } else if( type.equals( "short" ) ) {
            nam.setShort( obj, val.getShort( obj ) );
        } else if( type.equals( "boolean" ) ) {
            nam.setBoolean( obj, val.getBoolean( obj ) );
        } else {  /* オブジェクトの場合 */
            nam.set( obj, val.get( obj ) );
        }
    }

    /* 設定値が即値の場合には */
    private void setObjectItemValue( Field nam, String sel ) throws NoSuchFieldException, 
                                            NumberFormatException, IllegalAccessException {
        String type = nam.getType().toString();

        /* 受け側の型に応じた処理をする。ややダサいが仕方ない */
        if( type.equals( "int" ) ) {
            nam.setInt( obj, Integer.parseInt( sel ) );
        } else if( type.equals( "byte" ) ) {
            nam.setByte( obj, Byte.parseByte( sel ) );
        } else if( type.equals( "char" ) ) {
            nam.setChar( obj, sel.charAt(0) );
        } else if( type.equals( "double" ) ) {
            /* nam.setDouble( obj, Double.parseDouble( sel ) ); 
               のつもりだが、これは JDK 1.1 ではこう。*/
            nam.setDouble( obj, Double.valueOf( sel ).doubleValue() );
        } else if( type.equals( "float" ) ) {
            /* nam.setFloat( obj, Float.parseFloat( sel ) ); 
               のつもりだが、これは JDK 1.1 ではこう。*/
            nam.setFloat( obj, Float.valueOf( sel ).floatValue() );
        } else if( type.equals( "long" ) ) {
            nam.setLong( obj, Long.parseLong( sel ) );
        } else if( type.equals( "short" ) ) {
            nam.setShort( obj, Short.parseShort( sel ) );
        } else if( type.equals( "boolean" ) ) {
            /* boolean 型の場合は手作業で判定せざるを得ない */
            if( sel.equals( "true" ) ) {
                nam.setBoolean( obj, true );
            } else if( sel.equals( "false" ) ) {
                nam.setBoolean( obj, false );
            } else {
                throw new NumberFormatException( "Illegal boolean string, " + 
                                                 "true or false permitted." );
            }
        } else if( type.equals( "String" ) ) {
            nam.set( obj, sel );
        } else {
            /* どうやったら文字列からオブジェクトを生成できるのだろう? 
               そりゃ、String 型を取るコンストラクタがあればいいんだが、この処理は
               面倒すぎる。*/
            throw new IllegalAccessException( "Illegal type field, can set only " +
                                              "Primitive Type or String." );
        }
    }
}



copyright by K.Sugiura, 1996-2006