GUI部分を考える
モデルの定義が完了したら、いよいよGUI部分の検討をおこないます。
RCPの雛形を作る
RCPとして最低限動くまでを自分で作成するのはいささか面倒です。ここはEclipseのテンプレートを使って、さくっと自動生成しましょう。
[ファイル]-[新規]-[プロジェクト]-[プラグインプロジェクト]を選び、[ビューを持つRCPアプリケーション]を選んで自動生成を行ってください。
シンプルなビューで画面を作る
前述のポリシー「複雑なビューは作るな、複雑なパースペクティブにせよ」を考慮して画面を作っていきましょう。画面イメージの「ツリー表示」「ブラウザ表示」をそれぞれ独立したViewPartとして実装します。そして、内部に持つコンポーネントはそれぞれひとつだけにします。
public static final String ID = "jp.sourceforge.erep.browser.view.NavigationView"; private TreeViewer viewer; @Override public void createPartControl(Composite parent) { viewer = new TreeViewer(parent, SWT.NONE); viewer.setContentProvider(new CVSContentProvider()); viewer.setLabelProvider(new CVSLabelProvider()); }
public static final String ID = "jp.sourceforge.erep.browser.view.BrowserView"; private Browser browser; @Override public void createPartControl(Composite parent) { browser = new Browser(parent, SWT.NONE); }
public void createInitialLayout(IPageLayout layout) { String editorArea = layout.getEditorArea(); layout.setEditorAreaVisible(false); layout.setFixed(true); layout.addView(NavigationView.ID, IPageLayout.LEFT, 0.3f, editorArea); layout.addView(BrowserView.ID, IPageLayout.RIGHT, 0.7f, editorArea); }
これだけ書けば、画面の概観は出来上がります。

ContentProvider, LabelProvider
定義したViewCVSのモデルを画面に表示できるようにLabelProviderとContentProviderを作成する必要があります。これらについての情報は本稿の趣旨と外れますので、説明を省きます。詳細は参考資料を見てください。
public class CVSContentProvider implements ITreeContentProvider { public Object[] getChildren(Object parentElement) { if (parentElement instanceof List) { return ((List) parentElement).toArray(); } else if (parentElement instanceof CVSFolder) { CVSFolder folder = (CVSFolder) parentElement; return folder.getContents().toArray(); } return null; } public Object getParent(Object element) { return null; } public boolean hasChildren(Object element) { return element instanceof CVSFolder; } public Object[] getElements(Object inputElement) { return getChildren(inputElement); } public void dispose() { } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } }
public class CVSLabelProvider extends LabelProvider { @Override public String getText(Object element) { CVSFile file = (CVSFile) element; return file.getName(); } @Override public Image getImage(Object element) { ISharedImages images = PlatformUI.getWorkbench().getSharedImages(); if (element instanceof CVSModule) { return images.getImage(ISharedImages.IMG_OBJ_ELEMENT); } else if (element instanceof CVSFolder) { return images.getImage(ISharedImages.IMG_OBJ_FOLDER); } else if (element instanceof CVSFile){ return images.getImage(ISharedImages.IMG_OBJ_FILE); } return null; } }
アクションを作る
本稿の主旨は、このアクションの重要性にあります。アクションを経由してモデルを変更することでビューの余計なモデルへの依存を軽減します。また、アクションがモデルを経由してビューを変更することでビュー同士の依存が無くなり、かつ、アクションからビューへの参照もなくなります。
また、アクションは自立的な活性/非活性の切り替えを可能にします。これは単純なボタンでは実現できない、アクションならではの機能です。多くの場合、次の方法でアクションの機能を実現します。
- 各ビューは
ISelectionProviderを使って、選択の変更をWorkbenchWindowに通知する - アクションは
ISelectionListenerを使って、WorkbenchWindow上の選択の変更を受け取るようにする - 変更を受け取ったら、アクションの
setEnable(boolean)を使って活性/非活性を切り替える
@Override public void setFocus() { //Viewの動作 = ViewPartの動作. getSite().setSelectionProvider(viewer); }
public class OpenURLAction extends Action implements IWorkbenchAction, ISelectionListener { private CVSFile file = null; public OpenURLAction(IWorkbenchWindow window) { super(); setActionDefinitionId( "jp.sourceforge.erep.browser.action.openurl"); setId("jp.sourceforge.erep.browser.action.openurl"); setText("開く"); setEnabled(false); setImageDescriptor(PlatformUI.getWorkbench().getSharedImages(). getImageDescriptor(ISharedImages.IMG_OBJ_FOLDER)); window.getSelectionService().addSelectionListener(this); } /** * 選択されているものがCVSFileの場合にアクションが有効. * @see org.eclipse.ui.ISelectionListener */ public void selectionChanged( IWorkbenchPart part, ISelection selection) { IStructuredSelection structuredSelection = (IStructuredSelection) selection; if (structuredSelection.getFirstElement() instanceof CVSFile) { file = (CVSFile) structuredSelection.getFirstElement(); setEnabled(true); } else { setEnabled(false); } } @Override public void run() { file.open(); } /* (非 Javadoc) * @see org.eclipse.ui.actions.ActionFactory.IWorkbenchAction#dispose() */ public void dispose() { } }
今回は次のアクションを作成しました。それぞれのアクションは単純にモデルに対してメソッドを呼び出すだけです。
- アプリケーションが起動している場合は常に有効なCVSモジュール(ViewCVSのURL)を追加するアクション
- ツリーのなんらかのアイテムを選択している場合に有効なURLをブラウザで開くアクション
- ツリー上のフォルダを選択してる場合のみ有効なURL以下の中身を解析するアクション
モデルとビューを連携させる
アクションによって、モデルに対して何らかの変更が与えられると、モデルにフックされたビューに変更が通知されます。ビューが変更を受け取るためには、java.beans.PropertyChangeListenerを実装し、次のような処理を加えます。
/** * -CVSModuleが追加されたらブラウザに指定されたページを表示し解析を行う * -CVSFileに対してopenが呼ばれたらブラウザに指定されたページを表示する * -CVSFolderに対してopenが呼ばれたら指定されたページを表示し解析を行う */ public void propertyChange(PropertyChangeEvent evt) { if (CVSModule.EVENT_UPDATE_URL.equals(evt.getPropertyName())) { CVSModule module = (CVSModule) evt.getSource(); openAndAnalize(module); } else if (CVSFolder.EVENT_OPEN.equals(evt.getPropertyName())) { CVSFile file = (CVSFile) evt.getSource(); browser.setUrl(file.getUrl()); } else if (CVSFolder.EVENT_CONTENTS.equals(evt.getPropertyName())) { openAndAnalize((CVSFolder) evt.getSource()); } }
どのような経緯でモデルの変更が行われたかをビューが知る必要はありません。とにかく、モデルの変更に興味がある場合はビューの中身を更新します。
HTML解析
HTMLの解析は、ブラウザでHTMLを表示した後にJavaからブラウザ上で動的にJavaScriptを実行し、その結果を呼び出し元のJavaで受け取るという少々ひねくれた方法で実現しています。この部分の詳細な説明は本稿の主旨と外れるので、省略します。
動作の流れ
表示するURLの指定から、HTML解析、ビュー更新までの流れをシーケンスで表すと次のようになります。

まとめ
今回はアクションとモデルを使って、いかにEclipse RCPらしい設計を行うかについて説明しました。必ずしも今回の設計指針に従う必要はありませんが、GUIアプリケーションの開発は指針を決めずに実装を行ってしまうとコードが煩雑になりがちです。次回からは、RCPの掲げる目標であるコマーシャル・クオリティの製品開発を容易にするために提供されている数々の組み込みコンポーネントについて説明を行う予定です。

#dispose()
*/