Eclipseプラグインを作ってみよう!

第7回画面の作成(2)

前回でMasterDetailsBlockクラスを継承したFieldsBlockクラスを作成し、確認のためのラベルとテキストボックスの表示しました。今回からはMaster(一覧⁠⁠、Details(詳細)を作っていきます。

Master(一覧)の実装

最初にMaster(一覧)の実装から行います。一覧表示できるウィジェットにはツリーやリストなどさまざまなウィジェットがありますが、ここではテーブルを使用します。テーブルにはフィールド名と説明を表示することにします。またマニフェストエディターと同様に[追加⁠⁠、⁠削除⁠⁠、⁠上へ⁠⁠、⁠下へ]の各ボタンを実装することにします。

Master(一覧)のラフスケッチ
Master(一覧)のラフスケッチ

テーブルの表示

それでは、Master(一覧)にテーブルを表示してみましょう。まず、FieldsBlockクラスのcreateMasterPart()メソッドで、セクションのタイトルと説明を変更します。次に確認のために配置したラベルとテキストボックスを削除し、テーブルを生成します。

FieldBlockクラスのcreateMasterPart()メソッドでテーブルを生成する
protected void createMasterPart(
                IManagedForm managedForm,
                Composite parent) {
    FormToolkit toolkit = managedForm.getToolkit();

    Section section = toolkit.createSection(parent, 
            Section.TITLE_BAR | Section.DESCRIPTION);
    section.setText("フィールド一覧");
    section.setDescription("編集するフィールドを選択してください。");

    Composite composite = toolkit.createComposite(section);
    composite.setLayout(new GridLayout(2, false));
    
    Table table = toolkit.createTable(
                    composite, 
                    SWT.SINGLE | SWT.FULL_SELECTION);
    table.setHeaderVisible(true);
    table.setLinesVisible(true);
    
    TableColumn column1 = new TableColumn(table, SWT.NULL);
    column1.setText("フィールド名");
    column1.setWidth(100);

    TableColumn column2 = new TableColumn(table, SWT.NULL);
    column2.setText("説明");
    column2.setWidth(100);

    section.setClient(composite);
}

ラベルやテキストボックスと同様にFormToolkitクラスのファクトリーメソッドcreateTable()を使用してテーブルを生成します。テーブルは行全体を一度に1行だけ選択できるようにするため、SWT.SINGLE, SWT.FULL_SELECTIONを指定します。それでは実行してみましょう。

Master(一覧)にテーブルが表示されました
Master(一覧)にテーブルが表示されました

Master(一覧)にテーブルが表示されました。しかし、マニフェストエディターのようにエディターの枠全体に広がっていません。どのようにすればテーブルを枠全体に広げることができるのでしょうか。

レイアウト

テーブルの表示サイズについて考える前に、ウィジェットの配置方法について考えてみましょう。SWT(Standard Widget Toolkit)を基盤としたUIコンポーネントの配置は設定されたレイアウトによって決まります。SWTでは標準的なレイアウトクラスとして下記の3つを提供しています。

FillLayoutクラス

もっともシンプルなレイアウトです。ウィジェットは垂直・水平いずれかの指定された方向に対して同じサイズで配置されます。使いやすい反面、ウィジェットのサイズが等分されるので、あまり見栄えのしないレイアウトになります。

FillLayoutクラスを使った垂直・水平方向へのウィジェットの配置
FillLayoutクラスを使った垂直・水平方向へのウィジェットの配置

RowLayoutクラス

FillLayoutクラスよりも一般的に使用されているのがRowLayoutクラスです。FillLayoutクラスと同じく、垂直または水平方向にウィジェットを配置していきますが、ウィジェットのサイズは等分されません。

RowLayoutクラスを使ったウィジェットの配置
RowLayoutクラスを使ったウィジェットの配置

GridLayoutクラス

GridLayoutクラスは標準的なレイアウトクラスの中で最も一般的に使われています。ウィジェットをグリッド(格子)上に配置していくことができるため、柔軟な配置を行うことができます。

GridLayoutクラスを使ったウィジェットの配置(列数を2に指定)
GridLayoutクラスを使ったウィジェットの配置(列数を2に指定)

子を保持できるウィジェット(Compositeクラスを継承したウィジェット)に対してレイアウトを設定することで、ウィジェットの配置を制御することができます。なお、SWTのレイアウトについては、Eclipse Corner ArticleUnderstanding Layouts in SWTに詳しく説明されていますので、ご参照ください。

Master(一覧)のレイアウト

レイアウトについてひととおり学んだところで、Master(一覧)のレイアウトを考えてみましょう。まず採用するレイアウトですが、 GridLayoutクラスが一般的かつ柔軟だということなので、全面的にこれを採用することにします。テーブルとボタン群にそれぞれ1列ずつ割り振り、ボタン群に対しては別途Compositeクラスを用意してそこに各ボタンを配置することにします。

Master(一覧)のレイアウト
Master(一覧)のレイアウト

GridDataオブジェクトの設定

GridLayoutはレイアウト時に各ウィジェットからGridDataオブジェクトを取得し、その設定情報に基づいて配置方法を決定します。従って、適切に設定したGridDataオブジェクトをTableオブジェクトに設定すれば、テーブルを枠全体に広げて表示することができます。GridDataオブジェクトに対してどのような設定が可能か見てみましょう。

フィールド名説明
excludeウィジェットが保持するサイズと位置情報を無視するか
grabExcessHorizontalSpaceウィジェットが配置されるセルの幅を親コンポジットに合わせるか
grabExcessVerticalSpaceウィジェットが配置されるセルの高さを親コンポジットに合わせるか
minimumHeightウィジェットが配置されるセルの最小の高さを指定する
minimumWidthウィジェットが配置されるセルの最小の幅を指定する
horizontalAlignmentセル内での水平方向の配置方法を決定する。設定は左寄せ(SWT.BEGINNING(LEFT)⁠⁠、中央寄せ(SWT.CENTER⁠⁠、右寄せ(SWT.END(RIGHT)⁠⁠、あるいは全体に広げる(SWT.FILL)のいずれかから選択可能です
verticalAlignmentセル内での垂直方向の配置方法を決定する。設定は左寄せ(SWT.BEGINNING(TOP)⁠⁠、中央寄せ(SWT.CENTER⁠⁠、右寄せ(SWT.END(BOTTO )⁠⁠、あるいは全体に広げる(SWT.FILL)のいずれかから選択可能です
horizontalIndentセルの左端からの水平方向のインデント位置を設定する
verticalIndentセルの上端からの垂直方向のインデント位置を設定する
horizontalSpan水平方向に結合するセル数を指定する
verticalSpan垂直方向に結合するセル数を指定する
heightHintウィジェットの最適な高さを指定する
widthHintウィジェットの最適な幅を指定する

テーブルを枠全体に広げるには、まずウィジェットが配置されるセルのサイズを親コンポジットの枠全体に広げ、次にテーブル自体をセルの枠全体に広げます。これを水平・垂直方向に対して行います。

テーブルが枠全体に広がるようにGridDataオブジェクトを設定する
protected void createMasterPart(
                IManagedForm managedForm,
                Composite parent) {
    ...
   
    Table table = toolkit.createTable(
                    composite, 
                    SWT.SINGLE | SWT.FULL_SELECTION);
    table.setHeaderVisible(true);
    table.setLinesVisible(true);

    GridData layoutData = new GridData();
    layoutData.horizontalAlignment = GridData.FILL;
    layoutData.verticalAlignment = GridData.FILL;
    layoutData.grabExcessHorizontalSpace = true;
    layoutData.grabExcessVerticalSpace = true;
    table.setLayoutData(layoutData);
   
    TableColumn column1 = new TableColumn(table, SWT.NULL);
    ...
}

それでは実行してみましょう。テーブルを枠全体に広げることができました。

テーブルを枠全体に広げることができました
テーブルを枠全体に広げることができました

Fieldクラスの作成と一覧表示

テーブルを表示することができましたので、次は表示するデータを考えてみます。まずテーブルに1行ずつ表示するデータはフィールドの情報なので、それを保持するクラスを作成しましょう。

Fieldクラス
public final class Field {
    private String fName;
    private String fDescription;
    private boolean fRequired;
    private String fMessage;

    public Field(final String name) {
        if (name == null) {
            throw new NullPointerException();
        }
        fName = name;
        fDescription = "";
        fRequired = false;
        fMessage = "";
    }

    public String getName() {
        return fName;
    }

    public void setName(final String name) {
        if (name == null) {
            throw new NullPointerException();
        }
        fName = name;
    }

    public String getDescription() {
        return fDescription;
    }

    public void setDescription(final String description) {
        if (description == null) {
            throw new NullPointerException();
        }
        fDescription = description;
    }

    public boolean isRequired() {
        return fRequired;
    }

    public void setRequired(final boolean required) {
        fRequired = required;
    }

    public String getMessage() {
        return fMessage;
    }

    public void setMessage(final String message) {
        if (fMessage == null) {
            throw new NullPointerException();
        }
        fMessage = message;
    }
}

Fieldクラスはフィールド名、説明、必須かどうか、メッセージの各プロパティーを持ち、それらのgetter/setterメソッドを提供するクラスです。続いて、FieldsPageクラスでサンプルデータを作成し、表示してみます。

サンプルデータを作成し、テーブルに一覧を表示する
private class FieldsBlock extends MasterDetailsBlock {
    @Override
    protected void createMasterPart(
                    IManagedForm managedForm,
                    Composite parent) {
        ...
        section.setClient(composite);
        
        for (Field field : createSample()) {
            TableItem item = new TableItem(table, SWT.NONE);
            item.setText(0, field.getName());
            item.setText(1, field.getDescription());
        }
    }

    ...
    
    private List createSample() {
        List fields = new ArrayList();
        
        Field field1 = new Field("name");
        field1.setDescription("名前");
        field1.setRequired(true);
        field1.setMessage("名前は必須です。");
        
        fields.add(field1);

        Field field2 = new Field("phone");
        field2.setDescription("電話番号");
        field2.setRequired(true);
        field2.setMessage("電話番号は必須です。");
        
        fields.add(field2);
        
        Field field3 = new Field("address");
        field3.setDescription("住所");
        field3.setRequired(false);
        
        fields.add(field3);
        
        return fields;
    }
}

それでは実行してみましょう。作成したサンプルデータが一覧表示されます。

作成したサンプルデータが一覧表示されました
作成したサンプルデータが一覧表示されました

TableViewerクラスの導入

前項の方法は作成したサンプルデータを表示するために、TableItemクラスを使って1行ずつデータを文字列として設定するというものでした。しかし、この方法はすぐに破綻しそうです。というのも、Tableオブジェクトはフィールド名と説明を文字列として保持しているだけなので、詳細を表示するときにほかのデータを表示することができません。テーブルのデータと同じ内容のListオブジェクトを保持するなど、工夫次第では実装できないことはないですが、明らかに複雑になります。

この問題を解決するために、JFaceのTableViewerクラスを導入します。JFaceについては、ヘルプの「Platform Plug-in Developer Guide⁠⁠→⁠Programmer's Guide⁠⁠→⁠The JFace UI framework」に概要が記されています。それによるとJFaceはUIプログラミングにおいて共通する処理をまとめたヘルパークラスを提供しています。今回のように「ウィジェット(Table)とモデル(Field)の関係」をまとめる機構として、JFaceではビューアーを提供しています。ビューアーが間に入ることでFieldオブジェクトのデータを直接テーブルに追加するといった処理が不要になります。また、データをモデルクラスの形式でやりとりできるので、追加や削除も簡単に行えます。今回はTableクラスのビューアーであるTableViewerクラスを使用します。

従来はアプリケーション自身がウィジェットとモデルの仲介を行う必要があった
従来はアプリケーション自身がウィジェットとモデルの仲介を行う必要があった
ビューアークラスを導入することによりウィジェットとモデルの仲介を行ってくれる
ビューアークラスを導入することによりウィジェットとモデルの仲介を行ってくれる

コンテンツプロバイダーとラベルプロバイダー

ウィジェットとモデルの仲介を行うビューアーですが、実際の仲介処理はコンテンツプロバイダーとラベルプロバイダーに委譲しています。コンテンツプロバイダーはビューアーからの要求に対して適切な形式でモデルを返します。独自に実装することも可能ですが、一般的にJFaceが提供しているコンテンツプロバイダーを使用します。今回は指定されたエレメントを配列で返すArrayContentProviderクラスを使用します。

ラベルプロバイダーはモデルをどのように表示するかを決定します。例えば、今回であれば1列目にフィールド名、2列目に説明を表示しています。これを指定するのがラベルプロバイダーです。従って、ラベルプロバイダーは独自に実装する必要があります。今回はTableViewerクラスのためのITableLabelViewerインタフェースを実装します。

コンテンツプロバイダーとラベルプロバイダーをセットする
protected void createMasterPart(
                IManagedForm managedForm,
                Composite parent) {
    ...
    TableColumn column2 = new TableColumn(table, SWT.NULL);
    column2.setText("説明");
    column2.setWidth(100);

    TableViewer viewer = new TableViewer(table);
    viewer.setContentProvider(new ArrayContentProvider());
    viewer.setLabelProvider(new ITableLabelProvider() {
        public Image getColumnImage(Object element, int columnIndex) {
            return null;
        }

        public String getColumnText(Object element, int columnIndex) {
            if (element == null) {
                return "";
            }
            if (!(element instanceof Field)) {
                return "";
            }
            
            Field field = (Field) element;
            if (columnIndex == 0) {
                return field.getName();
            } else if (columnIndex == 1) {
                return field.getDescription();
            }
            return "";
        }

        public void addListener(ILabelProviderListener listener) {
        }

        public void dispose() {
        }

        public boolean isLabelProperty(Object element, String property) {
            return false;
        }

        public void removeListener(ILabelProviderListener listener) {
        }
    });
    
    section.setClient(composite);
    
    viewer.setInput(createSample());
}

コンテンツプロバイダーにはArrayContentProviderオブジェクトをセットします。ラベルプロバイダーにはITableLabelProviderインタフェースを無名クラスで実装します。いくつかメソッドがありますが、今のところ実装するのはgetColumnText()メソッドのみです。このメソッドでは列にセットする文字列を返すために、1列目の場合はフィールド名、2列目の場合は説明をそれぞれ返すようにしています。それでは実行してみましょう。サンプルのデータが表示されれば成功です。

ビューアーを導入することで、テーブルを直接操作してFieldオブジェクトをセットしているコードがなくなっているのがわかります。

おわりに

今回はレイアウトとビューアーについて説明しました。SWT/JFaceの話が中心になりましたが、Eclipse FormsがSWT/JFaceの上に成り立っているものであることがよくわかります。

次回はMaster(一覧)のボタン部分とDetails(詳細)の入力項目定義フォームを実装します。

おすすめ記事

記事・ニュース一覧