Swingのコンポーネント配置ライブラリのテスト

NetBeans使ってるならそっちでやればいいだけだけど、コード生成やGUIエディタのクセとかformファイルの存在とかちゃんとやろうと思うとそれなりに勉強は必要なもの。Swing自体の知識が必須なのは当たり前としてだけど。

NetBeans以外だとGUIまともにやっていたのはJBuilderくらいか。ただし、こちらはカスタマイズ製が低いことや、独自のライブラリを勝手に組み込まれたりする危険性が多く、安心して使えないものだった。


10年以上前からXMLコンポーネントを配置するライブラリってのはあるけど、そのほとんどがメンテされておらず、しかも、独自のAPIでラップされていたりしていて非常に使い勝手が悪い。Webアプリと違って通常のWindowアプリは細かい制御をしてナンボだと思う。だから隠してしまってはいけないと思うんだよね。ライブラリが今でも継続してメンテされているならなんとかなるかもしれないけど、そうなってない現在では怖くて使えない。また、XMLでイベントも全て各タイプも珍しくなかった。いまだとMXMLとか普通にXMLの中にコードを書いているけど、当時はSwing用でいろいろとあったのだ。しかし、IDEが普及した現在、ロジックをIDEサポートが無いXMLでアプリケーションのロジックを書くことは怖くて誰も出来ないだろう。


というわけで軽く作ってみた。

まずはXML見てもらうのが早いか。

<?xml version="1.0" encoding="UTF-8"?>
<Swing>
    <JPanel>
        <JPanel>
            <Layout layout="grid" rows="4" cols="1"/>

            <JButton text="パネルの中のボタン"/>
            <JTextField  text="パネルの中テキスト"/>

            <JPanel>
                <JButton text="パネルの中のパネルの中のボタン"/>
            </JPanel>

            <JLabel text="パネルの中のラベル"/>
        </JPanel>

        <JButton  text="ボタン"/>
    </JPanel>
</Swing>

Swingわかる人ならたぶん迷わないと思う。呼び出しコードはこれ。

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

public class Main {

    public static void main(String[] args) throws Exception{

        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                try {
                    JFrame frame = new JFrame("サンプル");
                    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

                    JPanel jpanel = ComponentBuilder.create("layout.xml");
                    frame.add(jpanel);

                    frame.pack();
                    frame.setVisible(true);
                } catch (Exception ex) {
                    Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
    }

}

みてわかるのがJPanelを生成するというところ。アプレットでもアプリケーションでも使える。


実行結果。
http://shin.cside.com/diary/2010/0607-01.png


でもこれだとロジックがかけない。ではどうするのか。Swingには膨大なリスナーがあり、また拡張も人それぞれ。統一的な手段を用意するのは得策ではないと思う。そこであくまでも注入だけに特化することにした。つまり、GWTのUiBinderを参考に作られた、という感じで。

bindにはidを指定する。

<?xml version="1.0" encoding="UTF-8"?>
<Swing>
    <JPanel>
        <JPanel>
            <Layout layout="grid" rows="4" cols="1"/>

            <JButton text="パネルの中のボタン"/>
            <JTextField id="text" text="パネルの中テキスト"/>

            <JPanel>
                <JButton text="パネルの中のパネルの中のボタン"/>
            </JPanel>

            <JLabel id="outputLabel"  text="パネルの中のラベル"/>
        </JPanel>

        <JButton id="button" text="ボタン"/>
    </JPanel>
</Swing>


バインドするJPanel。

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import org.jdesktop.beansbinding.BindingGroup;

public class HogePanel extends JPanel {

    @Bind("outputLabel")
    private JLabel label;
    
    @Bind("text")
    private JTextField textField;

    @Bind("button")
    private JButton jButton;


    public void init(){
        jButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                label.setText( textField.getText() );
            }
        });
    }
}

mainクラスは以下の生成場所をコメント化する。そして2行追加。

//JPanel jpanel = ComponentBuilder.create("layout.xml");
HogePanel jpanel = ComponentBuilder.inject("layout.xml" , new HogePanel());
jpanel.init();

あらかじめインスタンスを生成しておき、それに対して注入をするというわけだ。


実行結果。abcdefgと入力してボタンを押してみた。どこがバインドされているかに注目してXMLとコードを見てみるとよい。
http://shin.cside.com/diary/2010/0607-02.png


これだけだと面白くないのでBeansBindingいれてみる。

<?xml version="1.0" encoding="UTF-8"?>
<Swing>
    <BindingGroup id="bg"/>
    <JPanel>
        <JPanel>
            <Layout layout="grid" rows="4" cols="1"/>

            <JButton text="パネルの中のボタン"/>
            <JTextField id="text" text="${inputText}"/>

            <JPanel>
                <JButton text="パネルの中のパネルの中のボタン"/>
            </JPanel>

            <JLabel id="outputLabel"  text="${inputText}"/>
        </JPanel>

        <JButton id="button" text="ボタン"/>
    </JPanel>
</Swing>
    @Bind("bg")
    private BindingGroup bindingGroup;

    private String inputText;

    public String getInputText() {
        return inputText;
    }

    public void setInputText(String inputText) {
        Object oldValue = this.inputText;
        this.inputText = inputText;
        firePropertyChange("inputText", inputText, oldValue);
    }

バインドを管理するBindingGroupを注入するようにしている。あらゆるイベントが取れるように。といっても、今のところバインディング名無名のみだけど。

そして、Labelとテキストフィールドに同じELを記述。それようのプロパティを追加。これでテキスト入力すると自動でセッターが呼ばれ、ラベルが更新される。もはやボタンを押す必要は無い。