S-JIS[2013-03-13/2013-05-01] 変更履歴

Eclipse GEF TabbedProperty

Eclipseプラグイン開発GEFでタブプロパティーを使う方法について。


概要

TabbedPropertyは、リッチなプロパティービュー。
タブでページを選択し、それぞれのページでダイアログ(フォーム)の様なレイアウトでプロパティーの入力・変更ができる。

TabbedPropertyを使うには、必須プラグインに「org.eclipse.ui.views.properties.tabbed」を追加する必要がある。

※TabbedProperty自体はGEFでなくても使えるが、GEFの図形(モデル)に対して使うにはちょっとしたポイントがある。


TabbedPropertyの使用方法はEclipse Corner ArticleのThe Eclipse Tabbed Properties Viewに詳しい。
が、そこからサンプルとして『Tabbed Properties Logic Example』へのリンクが張ってあるのだが、これはCVSのサイトを指しており、古い。
ソースはCVSからGitへ移行されたらしく、今のCVSのサイトには「Gitへ移った」ということしか書かれていない。

という訳で、『Tabbed Properties Logic Example』はorg.eclipse.ui.examples.views.properties.tabbed.logicに置かれている。
モデルクラスのソースはorg.eclipse.gef.examples.logicにあるようだ。


Eclipseを起動したときにプロパティービューが出ていない場合は、以下の手順で表示できる。

  1. Eclipseのメニューバーの「ウィンドウ」→「ビューの表示」→「その他」で「ビューの表示」ダイアログを開く。
  2. 「一般」→「プロパティー」を選択してOKボタンを押す。

一行だけプロパティーを表示してみる例。

プロパティービューにどのようなタブ(ページ)・行を表示するかについては、行単位でplugin.xmlに定義を書く必要がある。

plugin.xml:

   <extension
         point="org.eclipse.ui.editors">
      <editor
            class="com.example.MyEditor"
            default="false"
            filenames="*.mygui"
            id="plugin.my-gui-editor"
            name="My GUI Editor">
      </editor>
   </extension>
   <extension
         point="org.eclipse.ui.views.properties.tabbed.propertyContributor">
      <propertyContributor
            contributorId="plugin.my-gui-editor">
         <propertyCategory
               category="example.propertyTab.category"></propertyCategory>
      </propertyContributor>
   </extension>
   <extension
         point="org.eclipse.ui.views.properties.tabbed.propertyTabs">
      <propertyTabs
            contributorId="plugin.my-gui-editor">
         <propertyTab
               category="example.propertyTab.category"
               id="example.genericTab"
               label="generic">
         </propertyTab>
      </propertyTabs>
   </extension>
   <extension
         point="org.eclipse.ui.views.properties.tabbed.propertySections">
      <propertySections
            contributorId="plugin.my-gui-editor">
         <propertySection
               class="com.example.property.NameSection"
               id="example.nameSection"
               tab="example.genericTab">
            <input
                  type="com.example.editpart.MyModelEditPart">
            </input>
         </propertySection>
      </propertySections>
   </extension>

まず、propertyContributorでcontributorIdとカテゴリーIDを定義する。
contributorIdは何でもいいと思うが、『Tabbed Properties Logic Example』ではエディターのIDと同じものにしていた。

次に、propertyTabsでタブ(ページ)を定義する。
タブにIDを付け、属するカテゴリーとラベルを指定する。

最後に、propertySectionsでタブ(ページ)内のセクションを定義する。
どうも、プロパティーの1行につき1つのセクションを定義しないといけないようだ。
セクションにIDを付け、属するタブを指定する。
classには、表示・編集方法を記述するクラスを指定する。
inputタグで、どのクラス(モデル)に対するものなのかを指定する。今回はEditPartクラスを指定してみた。

Tabbed Properties Logic Example』では、inputにモデルクラスそのものを指定している。
この場合は、propertyContributortypeMapperを指定し、EditPartからモデルクラスへの変換を定義する必要がある。


EditorクラスはITabbedPropertySheetPageContributorインターフェースを実装する必要がある。

GraphicalEditorクラス

import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
public class MyEditor extends GraphicalEditorWithFlyoutPalette implements ITabbedPropertySheetPageContributor {
〜
	@Override
	public String getContributorId() {
		return getSite().getId();
	}

ITabbedPropertySheetPageContributorで実装すべきメソッドは、getContributorId()のみ。
plugin.xmlに書かれているcontributoerIdを指定するが、「getSite().getId()」はエディターそのもののID
Tabbed Properties Logic Example』ではcontributoerIdをエディターのIDと同一にしているので、こういう実装になっている。

	@Override
	public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) {
		if (IPropertySheetPage.class.isAssignableFrom(adapter)) {
			return new TabbedPropertySheetPage(this);
		}
		return super.getAdapter(adapter);
	}
}

そして、getAdapter()でIPropertySheetPageを返すようにする。
TabbedPropertySheetPageインスタンスを作って返せばよい。
(TabbedPropertySheetPageのコンストラクターの引数がITabbedPropertySheetPageContributorなので、thisはITabbedPropertySheetPageContributorを実装する必要がある)

TabbedPropertySheetPageではISelectionChangedListenerが実装されており、エディター上で図形を選択する度に呼ばれる。
選択された図形のEditPartに応じてplugin.xmlに書かれたタブ定義を選択し、それをプロパティービュー上に描画するようになっている。


ISection(AbstractPropertySection)を実装し、実際に入力するエリアを作成する。これにはSWTを使用する。

import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.views.properties.tabbed.AbstractPropertySection;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertyConstants;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetWidgetFactory;
public class NameSection extends AbstractPropertySection {
	private Text nameText;

	private ModifyListener listener = new ModifyListener() {
		@Override
		public void modifyText(ModifyEvent event) {
			String name = nameText.getText();
			model.setName(name);
		}
	};

	private MyModel model;
	@Override
	public void createControls(Composite parent, TabbedPropertySheetPage aTabbedPropertySheetPage) {
		super.createControls(parent, aTabbedPropertySheetPage);

		TabbedPropertySheetWidgetFactory factory = getWidgetFactory();
		Composite composite = factory.createFlatFormComposite(parent);

		{
			nameText = factory.createText(composite, "");
			FormData data = new FormData();
			data.left = new FormAttachment(0, STANDARD_LABEL_WIDTH);
			data.right = new FormAttachment(100, 0);
			data.top = new FormAttachment(0, ITabbedPropertyConstants.VSPACE);
			nameText.setLayoutData(data);
			nameText.addModifyListener(listener);
		}
		{
			CLabel nameLabel = factory.createCLabel(composite, "name:");
			FormData data = new FormData();
			data.left = new FormAttachment(0, 0);
			data.right = new FormAttachment(nameText, -ITabbedPropertyConstants.HSPACE);
			data.top = new FormAttachment(nameText, 0, SWT.CENTER);
			nameLabel.setLayoutData(data);
		}
	}

createControls()でフォームを作成する。
基本的にはSWTだが、getWidgetFactory()でファクトリーオブジェクトを取得して使うことが出来る。
普通は「ラベル: 入力エリア」という行を作ろうと思ったらラベル→入力エリアの順でコンポーネントを作るのだが、ここでは逆の順序になっている。
FormDataで位置を指定しているので、その都合だろう。

	@Override
	public void setInput(IWorkbenchPart part, ISelection selection) {
		super.setInput(part, selection);

		IStructuredSelection ss = (IStructuredSelection) selection;
		MyModelEditPart editPart = (MyModelEditPart) ss.getFirstElement();
		model = editPart.getModel();
	}

選択されたEditPartがsetInput()に渡ってくるので、モデル(プロパティー変更対象のオブジェクト)をフィールドに保持しておく。

	@Override
	public void refresh() {
		nameText.removeModifyListener(listener);
		nameText.setText(model.getName());
		nameText.addModifyListener(listener);
	}
}

refresh()でプロパティー上のコンポーネントを更新する。

リスナーでは、プロパティーの値を変更する度にモデルの値を更新している。


モデルの変更をプロパティービューに反映

モデルの値(プロパティー)を変更しても、デフォルトではプロパティービューに反映されない。[2013-04-19]
モデルがPropertyChangeSupportを利用し、EditPartがPropertyChangeListenerを実装してプロパティー変更イベントを受け取るようになっている場合、
EditPartで変更を検知してプロパティービューを再描画(リフレッシュ)すればいい。

HogeEditPart.java:

import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.views.properties.PropertySheet;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
public class HogeEditPart extends AbstractGraphicalEditPart implements PropertyChangeListener {
	@Override
	public void propertyChange(PropertyChangeEvent event) {
		refreshPropertySheet();
	}
	private void refreshPropertySheet() {
		IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
		PropertySheet view =(PropertySheet) activePage.findView("org.eclipse.ui.views.PropertySheet");
		if (view == null) {
			return;
		}
		TabbedPropertySheetPage page = (TabbedPropertySheetPage) view.getCurrentPage();
		page.refresh();
	}

PlatformUIを使ってアクティブページを取り出し、その中からfindView()でプロパティーシート(プロパティービュー)を探す。
"org.eclipse.ui.views.PropertySheet"はPropertySheetビュー(クラス)のID。どこかに定数を用意してくれればいいのに。

参考: stackoverflowのIn the activator class of an Eclipse plugin how can I find view instances?


typeMapper

plugin.xmlにプロパティー(セクション)を表示すべき条件(クラス)を指定するのは、EditPartでなくモデルを指定する方が良い。[2013-05-01]
(プロパティーに表示したいのはモデルだから、モデルの種類に応じて表示するかどうかが変化する為。
 また、アウトラインページにツリーを表示して、そのツリーの要素(モデル)のプロパティーを表示するにもモデルの方が都合が良い)
こういう場合に、EditPartからモデル(条件判定用のクラス)へ変換するのがtypeMapper。

plugin.xml:

   <extension
         point="org.eclipse.ui.views.properties.tabbed.propertyContributor">
      <propertyContributor
            contributorId="plugin.my-gui-editor"
            typeMapper="com.example.MyTypeMapper">
         <propertyCategory
               category="example.propertyTab.category"></propertyCategory>
      </propertyContributor>
   </extension>

TypeMapperクラス

import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import org.eclipse.ui.views.properties.tabbed.AbstractTypeMapper;
public class MyTypeMapper extends AbstractTypeMapper {
	@Override
	public Class<?> mapType(Object object) {
		if (object instanceof AbstractGraphicalEditPart) {
			AbstractGraphicalEditPart part = (AbstractGraphicalEditPart) object;
			return part.getModel().getClass();
		}

		return super.mapType(object);
	}
}

EditPart(具体的にはAbstractGraphicalEditPartのサブクラス)からはgetModel()でモデルが取得できるので、そのクラスを返している。
これで、plugin.xmlのセクションの条件にモデルクラス名を指定できる。

plugin.xml:

   <extension
         point="org.eclipse.ui.views.properties.tabbed.propertySections">
      <propertySections
            contributorId="plugin.my-gui-editor">
         <propertySection
               class="com.example.property.NameSection"
               id="example.nameSection"
               tab="example.genericTab">
            <input
                  type="com.example.model.MyModel">		←セクションの条件にモデルクラス名(typeMapperで返るもの)を指定する
            </input>
         </propertySection>
      </propertySections>
   </extension>

GEFへ戻る / Eclipseプラグインへ戻る / Eclipseへ戻る / 技術メモへ戻る
メールの送信先:ひしだま