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

第8回画面の作成(3)

前回まででフィールドの一覧を表示できるようになりました。今回はMaster(一覧)のボタンの表示と、Master(一覧)のテーブルで選択されたフィールドの詳細がDetails(詳細)に表示されるようにします。

ボタンの表示

それでは、前回表示したテーブルの右横に[追加⁠⁠、⁠削除⁠⁠、⁠上へ⁠⁠、⁠下へ]ボタンを表示します。まず、テーブルの親コンポジットに新たなコンポジットbuttonsを作成し、これに対して各ボタンを作成していきます。さらに作成する各ボタンを水平方向いっぱいに広げるようにGridDataオブジェクトを設定します。GridLayoutクラスはレイアウト時に各ウィジェットごとに設定されたGridDataオブジェクトを取得し、配置位置を計算するので、同じ設定内容であってもウィジェットごとにインスタンスを設定しなければなりません。

FieldBlockクラスのcreateMasterPart()メソッドでボタンを生成する
protected void createMasterPart(
                IManagedForm managedForm,
                Composite parent) {
    ...

    TableViewer viewer = new TableViewer(table);
    viewer.setContentProvider(new ArrayContentProvider());
    viewer.setLabelProvider(new ITableLabelProvider() {
        ...
    });
    
    Composite buttons = toolkit.createComposite(composite);
    layoutData = new GridData();
    layoutData.verticalAlignment = GridData.FILL;
    buttons.setLayoutData(layoutData);

    buttons.setLayout(new GridLayout());

    Button addButton = toolkit.createButton(buttons, "追加(&A)...", SWT.PUSH);
    layoutData = new GridData();
    layoutData.horizontalAlignment = GridData.FILL;
    addButton.setLayoutData(layoutData);
    
    Button delButton = toolkit.createButton(buttons, "削除(&D)...", SWT.PUSH);
    layoutData = new GridData();
    layoutData.horizontalAlignment = GridData.FILL;
    delButton.setLayoutData(layoutData);

    Button upButton = toolkit.createButton(buttons, "上へ", SWT.PUSH);
    layoutData = new GridData();
    layoutData.horizontalAlignment = GridData.FILL;
    upButton.setLayoutData(layoutData);

    Button downButton = toolkit.createButton(buttons, "下へ", SWT.PUSH);
    layoutData = new GridData();
    layoutData.horizontalAlignment = GridData.FILL;
    downButton.setLayoutData(layoutData);

    section.setClient(composite);
    
    viewer.setInput(createSample());
}

それでは実行してボタンが表示されることを確認しましょう。

各ボタンが表示されました
各ボタンが表示されました

入力項目定義フォームの表示

ボタンが配置できましたので、続いて入力項目定義フォームを表示してみましょう。第3回で行った画面設計のラフスケッチを元に実装していきます。

入力項目定義フォームのラフスケッチ
入力項目定義フォームのラフスケッチ

Master(一覧)はFieldsBlockクラスのcreateMasterPart()メソッドで実装しましたが、Details(詳細)はどこに実装すればよいのでしょうか。ここで、マニフェストエディターの実装を参照してみましょう。フォームデザイナーが参考にした「拡張」ページの実装を見てみます。⁠拡張」ページはExtensionsPageクラスで実装されており、Master/DetailsパターンはMasterDetailsBlockクラスを継承したExtensionsBlockクラスで実装されていました。このExtensionsBlockクラスのregisterPages()メソッドでExtensionsDetailsクラスのインスタンスを生成しています。

ExtensionsBlockクラスのregisterPages()メソッドの実装
protected void registerPages(DetailsPart detailsPart) {
    detailsPart.setPageLimit(10);
    // register static page for the extensions
    detailsPart.registerPage(IPluginExtension.class, 
            new ExtensionDetails(fSection));
    // Register a static page for the extension elements that contain 
    // only body text (no child elements or attributes)
    // (e.g. schema simple type)
    fBodyTextDetails = new ExtensionElementBodyTextDetails(fSection);
    detailsPart.registerPage(ExtensionElementBodyTextDetails.class,
            fBodyTextDetails);
    // register a dynamic provider for elements
    detailsPart.setPageProvider(this);
}

ExtensionsDetailsクラスの実装を見てみると、名前のとおりDetails(詳細)の実装を行っています。以上のことから、registerPages()メソッドでDetails(詳細)を実装したクラスのインスタンスをdetailsPartに登録すればよいということがわかります。ここでMasterDetailsBlockクラスのregisterPages()メソッドのAPIリファレンスを見てみましょう。それによると、このメソッドはモデルとDetails(詳細)を一対一でマッピングを実装するものであると記述されています。さらにregisterPages()メソッドの引数であるDetailsPartクラスのregisterPage()メソッドを調べると第二引数はIDetailsPageインタフェースを実装すればよいことがわかります。

以上の点を踏まえて、FieldsBlockクラスのregisterPages()メソッドを実装してみましょう。

FieldsBlockクラスのregisterPages()メソッドの実装
protected void registerPages(DetailsPart detailsPart) {
    IDetailsPage detailsPage = new IDetailsPage() {
        private IManagedForm fForm;
        
        private Text fNameText;
        private Text fDescriptionText;
        private Button fRequiredYesButton;
        private Button fRequiredNoButton;
        private Text fMessageText;

        public void createContents(Composite parent) {
            GridLayout layout = new GridLayout(1, false);
            layout.marginHeight = 0;
            layout.marginWidth = 0;
            parent.setLayout(layout);

            FormToolkit toolkit = fForm.getToolkit();

            Section section = toolkit.createSection(parent, Section.TITLE_BAR);
            section.setText("フィールド詳細");

            GridData layoutData = new GridData();
            layoutData.grabExcessHorizontalSpace = true;
            layoutData.horizontalAlignment = GridData.FILL;
            section.setLayoutData(layoutData);
            
            Composite composite = toolkit.createComposite(section);
            composite.setLayout(new GridLayout(2, false));

            layoutData = new GridData();
            layoutData.grabExcessHorizontalSpace = true;
            layoutData.horizontalAlignment = GridData.FILL;
            composite.setLayoutData(layoutData);
            
            toolkit.createLabel(composite, "フィールド名");
            fNameText = toolkit.createText(composite, "");
            layoutData.grabExcessHorizontalSpace = true;
            layoutData.horizontalAlignment = GridData.FILL;
            fNameText.setLayoutData(layoutData);

            toolkit.createLabel(composite, "概要");
            fDescriptionText = toolkit.createText(composite, "");
            layoutData.grabExcessHorizontalSpace = true;
            layoutData.horizontalAlignment = GridData.FILL;
            fDescriptionText.setLayoutData(layoutData);

            toolkit.createLabel(composite, "必須");
            Composite requiredComposite = toolkit.createComposite(composite);
            requiredComposite.setLayout(new RowLayout(SWT.HORIZONTAL));
            fRequiredYesButton = toolkit.createButton(requiredComposite, "はい", SWT.RADIO);
            fRequiredNoButton = toolkit.createButton(requiredComposite, "いいえ", SWT.RADIO);

            toolkit.createLabel(composite, "メッセージ");
            fMessageText = toolkit.createText(composite, "");
            layoutData.grabExcessHorizontalSpace = true;
            layoutData.horizontalAlignment = GridData.FILL;
            fMessageText.setLayoutData(layoutData);

            section.setClient(composite);
        }
        ...
        public void initialize(IManagedForm form) {
            fForm = form;
        }
        ...
    };
    detailsPart.registerPage(Field.class, detailsPage);
}

IDetailsPageインタフェースの実装には、無名クラスを使用します。このインタフェースにはメソッドがいくつか定義されていますが、今のところ実装するべきメソッドはcreateContents()メソッドメソッドとinitialize()メソッドのふたつです。 createContents()メソッドではウィジェットの生成と配置を行います。initialize()メソッドでは引数の IManagedFormオブジェクトを無名クラスのプロパティーとして保持します。

これで入力項目定義フォームのウィジェットの生成と配置の実装は完成です。しかし、実際にフィールドの詳細を表示するには以下の実装を行う必要があります。

  1. テーブルで選択されたFieldオブジェクトを取得する
  2. 取得したFieldオブジェクトをDetails(詳細)に通知する
  3. 通知されたFieldオブジェクトの情報をDetails(詳細)に表示する

これらを順番に実装していきます。

フィールドの詳細の表示

まずテーブルで選択されたときにイベントを実行する必要があります。これはTableViewerクラスのaddSelectionChangedListener()メソッドでリスナーを登録することで実行することができます。addSelectionChangedListener()メソッドの引数としてISelectionChangedListenerオブジェクトを渡します。ISelectionChangedListenerインタフェースではselectionChanged()メソッドが定義されており、このメソッドがテーブル選択時に実行されます。さらにこのメソッドの引数SelectionChangedEventオブジェクトから選択されたFieldオブジェクトを取得することが可能です。

次に選択されたFieldオブジェクトを通知するために、第6回で説明したManagedFormクラスを使用します。このクラスのfireSelectionChanged()メソッドを呼び出すことでページで管理しているフォームパートに対してイベントを通知することができます。

テーブルビューアーに選択時リスナーを登録する
protected void createMasterPart(
                final IManagedForm managedForm,
                Composite parent) {
    ...            
    TableViewer viewer = new TableViewer(table);
    viewer.setContentProvider(new ArrayContentProvider());
    viewer.setLabelProvider(new ITableLabelProvider() {
        ...
    });
    final SectionPart part = new SectionPart(section);
    viewer.addSelectionChangedListener(new ISelectionChangedListener() {
        public void selectionChanged(SelectionChangedEvent event) {
            managedForm.fireSelectionChanged(
                            part,  
                            event.getSelection());
        }
    });

    Composite buttons = toolkit.createComposite(composite);
    ...
}

fireSelectionChanged()メソッドは引数に通知元フォームパートが必要なので、SectionPartを事前に生成し、それを指定します。また無名クラスでmanagedFormを使用するためにcreateMasterPart()メソッドの引数managedFormにfinalを指定しています。それでは実行してみましょう。フィールド一覧でフィールドを選択すると Details(詳細)にフィールドの詳細画面が表示されます。しかし、まだDetails(詳細)に配置したウィジェットにFieldオブジェクトの情報を表示する実装を行っていないので、何も表示されません。

Details(詳細)のウィジェットは表示されるが、Fieldオブジェクトの情報はまだ表示されない
Details(詳細)のウィジェットは表示されるが、Fieldオブジェクトの情報はまだ表示されない

それでは通知されたFieldオブジェクトを取得し、Details(詳細)の各ウィジェットに表示してみましょう。fireSelectionChanged()メソッドによる通知はselectionChanged()メソッドで受け取るので、このメソッドを実装します。

通知されたFieldオブジェクトを取得し、各ウィジェットに表示する
protected void registerPages(DetailsPart detailsPart) {
    IDetailsPage detailsPage = new IDetailsPage() {
        ...
        public void selectionChanged(IFormPart part,
                                     ISelection selection) {
            fNameText.setText("");
            fDescriptionText.setText("");
            fRequiredYesButton.setSelection(false);
            fRequiredNoButton.setSelection(false);
            fMessageText.setText("");

            Field field = 
                (Field) ((IStructuredSelection) selection).getFirstElement();
            fNameText.setText(field.getName());
            fDescriptionText.setText(field.getDescription());
            fRequiredYesButton.setSelection(field.isRequired());
            fRequiredNoButton.setSelection(!field.isRequired());
            fMessageText.setText(field.getMessage());
        }
    };
    detailsPart.registerPage(Field.class, detailsPage);
}

それでは実行してみましょう。選択したフィールドの情報がDetails(詳細)に表示されます。

Details(詳細)にFieldオブジェクトの情報が表示されました
Details(詳細)にFieldオブジェクトの情報が表示されました

おわりに

今回は画面のウィジェットの配置を行い、Master(一覧)とDetails(詳細)の連携を実装しました。注目すべき点は、データのやりとりが Fieldオブジェクトを介して行われているという点です。これによって、データとウィジェットの関連部分が局所化され、見やすいコードになっています。

次回は[追加⁠⁠、⁠削除⁠⁠、⁠上へ⁠⁠、⁠下へ]ボタンの実装と、Details(詳細)での変更をFieldオブジェクトに反映する実装を行います。

おすすめ記事

記事・ニュース一覧