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を起動したときにプロパティービューが出ていない場合は、以下の手順で表示できる。
一行だけプロパティーを表示してみる例。
プロパティービューにどのようなタブ(ページ)・行を表示するかについては、行単位で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にモデルクラスそのものを指定している。
この場合は、propertyContributorでtypeMapperを指定し、EditPartからモデルクラスへの変換を定義する必要がある。
EditorクラスはITabbedPropertySheetPageContributorインターフェースを実装する必要がある。
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で変更を検知してプロパティービューを再描画(リフレッシュ)すればいい。
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?
plugin.xmlにプロパティー(セクション)を表示すべき条件(クラス)を指定するのは、EditPartでなくモデルを指定する方が良い。[2013-05-01]
(プロパティーに表示したいのはモデルだから、モデルの種類に応じて表示するかどうかが変化する為。
また、アウトラインページにツリーを表示して、そのツリーの要素(モデル)のプロパティーを表示するにもモデルの方が都合が良い)
こういう場合に、EditPartからモデル(条件判定用のクラス)へ変換するのがtypeMapper。
<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>
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のセクションの条件にモデルクラス名を指定できる。
<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>