Graphical Editing Frameworkを使用してEclipseベースのアプリケーションを作成する

GEFを使い始める

この記事では、GEF(Graphical Editing Framework)を使ってEclipseベースのアプリケーションを作成するための最初のステップを説明します。GEFは、Eclipse用の様々なアプリケーションを構築するために使われてきています(ステート図やアクティビティー図、クラス図、AWT用のGUIビルダー、SwingやSWT、プロセス・フロー・エディターなど)。EclipseもGEFも、オープンソースの技術です。また両者は、IBMのWebSphere®Studio Workbenchにも含まれています。

Randy Hudson, Software developer, FIT Team, IBM China Development Lab 

Randy Hudsonは、ノースキャロライナ州Research Triangle ParkにあるIBMのソフトウェア・エンジニアです。彼はGEF(Graphical Editing Framework)の技術リーダーとして、かつては内部プロジェクトであったGEFを、オープンソース技術に移行するために貢献しました。現在は使いやすさや、グラフィカル編集、グラフ・レイアウト、エッジ・ルーティングなどに関する業務を行っています。



2006年 6月 27日

この記事では、GEFを使うための手順を説明します。各ステップを完全に説明するよりも、アプリケーションのモデルのサブセットを使用して、まずそれを動くようにします。例えば、最初はコネクションを無視するかも知れません。あるいはアプリケーションの中の、グラフィック要素型のサブセットのみを扱うかも知れません。

GEFの概要

GEFでは、グラフィカルに表示し、編集したいモデルがある、と想定しています。そのためにGEFには、Eclipseワークベンチのどこででも使用できる(EditPartViewer型の)ビューアーが用意されています。GEFビューアーはJFaceビューアーと同様、SWTコントロールに対するアダプターです。しかし、両者が似ているのは、そこまでです。GEFビューアーは、MVC(Model-View-Controller)に基づいているのです。

『コントローラー』は、ビューとモデルの間を橋渡しします(図1)。各コントローラー(ここでは『EditPart』と呼ぶことにします)は、モデルをビューにマップすることと、モデルに変更を加えることの両方に責任を持ちます。またEditPartはモデルを観察し、モデルの状態変化を反映してビューを更新します。EditPartは、ユーザーが対話動作を行う対象となるオブジェクトです。EditPartについて詳しくは、後ほど説明します。

図1. Model-View-Controller
図1. Model-View-Controller

GEFには、2つのタイプのビューアー、つまりグラフィカルなビューアーと、ツリー・ベースのビューアーが用意されています。グラフィカル・ビューアーは、SWT『キャンバス(canvas)』に描画される『図形(figure)』を使います。図形は、GEFの一部として含まれているDraw2Dプラグインの中で定義されます。TreeViewerは、SWTTreeとTreeItemsを使ってビューを表示します。


ステップ1. モデルを持ち込む

GEFは、モデルについて何も知りません。どんなモデル・タイプでも、下記の特性に合ってさえいれば、動作するはずです。

モデルの中にあるもの

モデルの中には、あらゆるものがあります。持続し、回復されるのは、モデルのみです。アプリケーションは、重要なデータすべてを、モデルの中に保存する必要があります。編集やアンドゥ(undo)、リドゥ(redo)を行う中で、それに耐えて残るのはモデルのみです。図形とEditPartは、時間の経過と共にガーベジ・コレクションされ、再生成されます。

ユーザーがEditPartと対話動作する場合には、モデルはEditPartsによって直接操作されるわけではなく、変更をカプセル化する『コマンド』が作成されるのです。コマンドは、ユーザーの対話動作の検証や、アンドゥ、リドゥをサポートするために使われます。

厳密に言うと、コマンドは、概念的にはモデルの一部でもあります。いわゆるモデルではありませんが、モデルを編集するための手段です。コマンドは、ユーザーが行うアンドゥ可能な変更すべてに使われます。理想的には、コマンドはモデルについてのみ知っているべきであり、EditPartや図形を参照するのは避けるべきです。同様に、コマンドがユーザー・インターフェース(ポップアップ・ダイアログなど)を呼び出すことも、できるだけ避けるべきです。

2つのモデルの物語

単純なGEFアプリケーションとしては、ダイヤグラム描画用のエディターがあります(ここでの『ダイヤグラム』は単なる図を意味し、クラス図などを意味するわけではありません)。ダイヤグラムは、何らかの形としてモデル化されます。形は、位置や色といったプロパティーを持っているかも知れず、また複数の形から成るグループ構造かも知れません。こうしたことは驚くには当たらず、先ほどの要求も容易に維持することができます(図2)。

図2. 単純なモデル
図2. 単純なモデル

もう1つのGEFアプリケーションは、クラス図エディターのようなUMLエディターです。ダイヤグラムの中の重要な情報として、クラスが現れる場所の(x、y)位置があります。この前のセクションでの説明から、モデルは、『x』プロパティーと『y』プロパティーを持つものとして『クラス』を記述しなければならない、と皆さんは思うかも知れません。ほとんどの開発者は、意味をなさない属性で自分たちのモデルを汚すのを避けようとします。そうしたアプリケーションでは、意味体系の持つ重要な詳細が保存されているベース・モデルを指すために、「ビジネス」モデルという用語が使われます。一方、ダイヤグラム特有の情報は、「ビュー」モデルに保存されます(これはつまり、ビジネス・モデルの中にある何かの「ビュー」を意味します。ある1つのオブジェクトは、1つのダイヤグラムの中で複数回ビューされるかも知れません)。場合によると、この分割がワークスペースに反映されることもあります。その場合には、ダイヤグラムとビジネス・モデルを別々に持続させるために、別々のリソースが使われるかも知れません。さらには、同じビジネス・モデルに対して幾つかのダイヤグラムがあるかも知れません(図3)。

図3. ビジネス・モデルとビュー・モデルにモデルを分割する
図3. ビジネス・モデルとビュー・モデルにモデルを分割する

モデルが2つの部分に分割されている場合でも、あるいは複数のリソースに分割されている場合であっても、GEFには関係がありません。モデルという用語は、アプリケーション・モデル全体を指すために使われるのです。画面上のオブジェクトは、モデル中の複数のオブジェクトに対応するかも知れません。GEFは、そうしたマッピングを容易に処理できるように作られています。

通知の戦略

ビューの更新は、ほとんどの場合、モデルからの通知の結果である必要があります。モデルは何らかの通知機構を提供する必要があり、この機構はアプリケーションの更新に対して適切にマップされている必要があります。例外としては、読み取り専用モデル、つまり通知を行えないモデル(ファイル・システムやリモート・コネクションなど)があります。

通知戦略は、通常は分散型(オブジェクト毎)、あるいは中央型(ドメイン毎)のいずれかです。ドメイン・ノーティファイヤー(domainnotifier)は、モデルの中の、どのオブジェクトの、どの変化も知っており、こうした変更をドメイン・リスナーにブロードキャストします。もしアプリケーションがこの通知モデルを使用している場合には、恐らくビューアー毎にドメイン・リスナーを追加する必要があるでしょう。そのリスナーが変更を受信すると、リスナーは影響を受けたEditPartを参照し、次にその変更を適切に再ディスパッチします。一方、アプリケーションが分散通知を使用している場合には、それぞれのEditPartは通常、どのモデル・オブジェクトであれ、自分に影響を与えるものに自分のリスナーを追加します。


ステップ2. ビューを定義する

次のステップは、Draw2Dプラグインからの図形を使って、どのようにモデルを表示するかを決めることです。一部の図形は、モデルのオブジェクトの1つを表示するために、直接使用することができます。例えばLabel図形は、ImageとStringの表示に使うことができます。また場合によると、複数の図形やレイアウト・マネージャー、ボーダーなどを合成することによって、望みの結果を得ることができます。あるいは最終的に、自分のアプリケーション特有の方法で描画するための、独自の図形実装を自分で書く必要があるかも知れません。

図形やレイアウトの合成や実装に関して詳しくは、GEFのSDKに含まれているDraw2D開発者ガイドの中に説明されています。

GEFでDraw2Dを使用する場合、プロジェクトの管理を容易に、また変化する要求に柔軟に対応するためには、下記の指針に従うようにします。

  • 既にあるものを再度作ることを避ける。用意されているレイアウト・マネージャーを組み合わせることで、ほとんどのものを描画することができます。ツールバー・レイアウト(垂直方向あるいは水平方向)やボーダー・レイアウトの組み合わせを使って複数の図形を合成することを考えます。独自のレイアウト・マネージャーを書くのは、最後の手段とすべきです。その参考のために、GEFに用意されているパレットを見てください。このパレットは、Draw2Dの標準図形やレイアウトを使って描画されたものです。
  • EditPartと図形の間の明確な分離を維持する。もし、EditPartが幾つかの図形やレイアウト、ボーダーなどを合成した構造を使っている場合には、その詳細を、できるだけEditPartから見えないようにします。EditPart自体にすべてを構築させることは、(良い考えではありませんが)可能です。しかしそうすると、コントローラーとビューとを明確に分離することができません。EditPartは図形構造を詳しく知っているため、その構造を類似のEditPartで再利用することはできません。また、外観や構造を変更すると、予期せぬバグが発生するかも知れません。そういうことは避け、その構造の詳細を隠すような、独自の、Figureのサブクラスを書くべきです。そうしてから、EditPart(コントローラー)がビューを更新するために使用する、(そのサブクラスに対する)最小限のAPIを定義するのです。『コンサーンの分離(separationof concerns)』と呼ばれる、このプラクティスを守ることによって、再利用を促進でき、またバグを減らすことができます。
  • 図形からのモデルやEditPartを参照しない。図形は、EditPartやモデルにアクセスできるべきではありません。場合によると、EditPartは自分をリスナーとして図形に追加することがありますが、この場合もリスナーとして知られるだけであり、EditPartとして知られるわけではありません。この分離(de-coupling)プラクティスも、再利用を促進します。
  • コンテンツ・ペインを使う。場合によると、コンテナーに他のグラフィック要素が含まれているにもかかわらず、そのコンテナーの外側を装飾しなければならないことがあります。例えば、UMLクラスは通常はボックスとして示されますが、その最上部にはクラス名や何らかのステレオタイプを持つラベルが付けられ、また最下部は属性とメソッドのために予約されています。これは、複数の図形を合成することで実現できます。つまり、最初の図形はクラスに対するタイトル・ボックスであるようにし、また別の図形を『コンテンツ・ペイン』として指定するのです。この図形は最終的に、属性とメソッドに対する図形を含みます。この先のEditPartの実装を読めば分かると思いますが、全ての子要素の親としてコンテンツ・ペインを使うように指示することは、何でもないことなのです。

ステップ3. EditPart、つまりコントローラーを書く

次に、モデルとビューとの間を、コントローラー、つまりEditPartで橋渡しをします。これは、GEFに「フレームワーク」を設定するためのステップです。提供されるクラスは抽象クラスであるため、クライアントは実際にコードを書く必要があります。ここで分かることは、サブクラス化が分かりやすいものであるだけではなく、モデルからビューにマップする方法として、恐らく最も柔軟で直接的であるということです。

サブクラス化のために、3つのベース実装が用意されています。ツリー・ビューアーに現れるEditPartには、AbstractTreeEditPartを使います。AbstractGraphicalEditPartとAbstractConnectionEditPartはグラフィカル・ビューアーの中に拡張します。ここでは、グラフィカルなEditPartsに注目します。ツリー・ビューアーを使う場合も同様です。

EditPartsのライフサイクル

EditPartsを書く前に、それがどこから来るのか、必要なくなった時にどこに行くのかを知っていた方が有利です。各ビューアーは、EditPartsを作成するためのファクトリーを持つようにコンフィギュレーションされています。ビューアーの内容を設定する際には、そのビューアーに対する入力を表すモデル・オブジェクトを提供して設定を行います。通常、この入力は最上部のモデル・オブジェクトであり、そこから他のすべてのオブジェクトをトラバースすることができます。次にビューアーは、自分のファクトリーを使って、その入力オブジェクトに対する『コンテンツEditPart』を構築します。その後は、ビューアーの中の各EditPartは、自分の子EditPart(コネクションEditPart)に中身を入れて管理し、新しいEditPartが必要になった場合にはEditPartファクトリーに権限委譲し、これをビューアーに中身が入るまで続けます。新しいモデル・オブジェクトがユーザーによって追加されると、そうしたオブジェクトが現れるEditPartは、対応するEditPartを構築することで応答します。ビューの構築とEditPartの構築とが並行して行われることに注意してください。従って、各EditPartが構築され、その親であるEditPartに追加されると、それが図形アイテムであれツリー・アイテムであれ、同じことがビューにも起こります。

EditPartは、対応するモデル・オブジェクトをユーザーが削除すると、即座に捨てられます。もしユーザーが削除をアンドゥした場合には、回復されたオブジェクトを表すために再作成されるのは、別のEditPartです。EditPartが長期情報を含むことができず、コマンドで参照すべきでないのは、このためです。

最初のEditPart: コンテンツEditPart

皆さんが書く最初のEditPartは、ダイヤグラムそのものに対応するEditPartです。このEditPartは、ビューアーの『コンテンツ』として参照されます。これはモデルの最上部要素に対応し、その親は、ビューアーの『ルート』EditPartです(図4)。このルートは、様々なグラフィカル・レイヤー(コネクション・レイヤーやハンドル・レイヤーなど、場合によるとビューアー・レベルでのズーム機能など)を提供し、コンテンツに対する基礎をなします。ルートの機能はどのモデル・オブジェクトにも依存しないこと、またGEFには、そのまま使えるルート実装が幾つか用意されていることに注意してください。

図4. ビューアーの中のEditPart
図4. ビューアーの中のEditPart

コンテンツの図形は、あまり面白いものではなく、多くの場合は、ダイヤグラムの子を含んだ単なる空のパネルです。この図形は不透明である必要があり、ダイヤグラムの子をレイアウトするレイアウト・マネージャーで初期化する必要があります。しかしこの図形は、構造を持ちます。ダイヤグラムの直接の子は、戻ってくる子モデル・オブジェクトのリストによって決定されます。リスト1は、不透明な図形を作成するコンテンツEditPartの一例です。この図形はXYLayoutを使って自分の子を配置します。

リスト1. コンテンツEditPartの最初の実装
public class DiagramContentsEditPart extends AbstractGraphicalEditPart {
protected IFigure createFigure() {
Figure f = new Figure();
f.setOpaque(true);
f.setLayoutManager(new XYLayout());
return f;
}

protected void createEditPolicies() {
...
}

protected List getModelChildren() {
return ((MyModelType)getModel()).getDiagramChildren();
}
}

ダイヤグラム上のアイテムを特定するために、getModelChildren() というメソッドが実装されています。このメソッドは、子モデル・オブジェクト(ダイヤグラム中のノード群など)のリストを返します。スーパークラスは、このモデル・オブジェクトのリストを使って、対応するEditPartを作成します。新たに作成されるEditPartは、子EditPartのパーツ・リストに追加されます。これによって今度は、それぞれの子の図形が、ダイヤグラムの図形に追加されます。デフォルトで、空のリスト(子がないことを示します)が返ったはずです。

よりグラフィカルなEditPart

残りのEditPart(ダイヤグラム中のアイテムを表します)は、恐らくグラフィカルに表示すべきデータを持っています。また、コネクションや自分の子など、独自の構造も持っているかも知れません。多くのGEFアプリケーションでは、ラベル付きのアイコンを、それらの間のコネクションとして描きます。ここで、EditPartsが自分の図形としてLabelを使うとし、モデルが名前やアイコン、ラベルとの間のコネクションを提供する、としましょう。リスト2は、このタイプのEditPartsを実装するための最初のパスを示しています。

リスト2. 「ノード」EditPartsの最初の実装
public class MyNodeEditPart extends AbstractGraphicalEditPart {
protected IFigure createFigure() {
return new Label();
}
protected void createEditPolicies() {
...
}
protected List getModelSourceConnections() {
MyModel node = (MyModel)getModel();
return node.getOutgoingConnections();
}
protected List getModelTargetConnections() {
MyModel node = (MyModel)getModel();
return node.getIncomingConnections();
}
protected void refreshVisuals() {
MyModel node = (MyModel)getModel();
Label label = (Label)getFigure();
label.setText(node.getName());
label.setIcon(node.getIcon());

Rectangle r = new Rectangle(node.x, node.y, -1, -1);
((GraphicalEditPart) getParent()).setLayoutConstraint(this, label, r);
}
}

新しいメソッド、refreshVisuals() はオーバーライドされています。このメソッドは、モデルからのデータを使って図形を更新する時になると呼ばれます。この場合、モデルの名前とアイコンは、ラベルに反映されています。しかしもっと重要なことは、ラベルが自分のレイアウト制約を親に渡すことによって、ラベルが配置されていることです。コンテンツEditPartでは、XYレイアウト・マネージャーを使いました。このレイアウトは、Rectangle制約を使って、子図形をどこに置くかを決定します。幅と高さの「-1」は、この図形に適切なサイズを与えるべきであることを示しています。

ヒント1

もう1つ、興味深い違いは、コネクションをサポートするためのコードです。getModelChildren()と同様、getModelSourceConnections() とgetModelTargetConnections() は、ノード間のリンクを表すモデル・オブジェクトを返す必要があります。スーパークラスは必要に応じて対応するEditPartを作成し、それらを、ソースとターゲットのコネクションEditPartのリストに追加します。ここで、コネクションは両端のノードによって参照されますが、そのEditPartは1度だけ作成すればよいことに注意してください。GEFは、ビューアーの中にコネクションが既に存在するかどうかを最初にチェックすることによって、コネクションが1度しか作成されないようにします。

refreshVisuals() というメソッドは、EditPartの初期化中に1度だけ呼ばれ、再度呼ばれることはありません。モデル通知に応答する際に、図形を更新するための必要に応じてrefreshVisuals()を再度呼ぶのは、アプリケーションの責任です。パフォーマンスを改善するためには、各モデル属性に対するコードを、それ自体のメソッド(あるいは「スイッチ」を持つ単一メソッド)の中に出してしまった方がよいかも知れません。そうすれば、モデルが通知した時には、最小限のコードのみを実行し、変化したものだけを更新することができます。

もう1つ、興味深い違いは、コネクションをサポートするためのコードです。getModelChildren()と同様、getModelSourceConnections() とgetModelTargetConnections() は、ノード間のリンクを表すモデル・オブジェクトを返す必要があります。スーパークラスは必要に応じて対応するEditPartを作成し、それらを、ソースとターゲットのコネクションEditPartのリストに追加します。ここで、コネクションは両端のノードによって参照されますが、そのEditPartは1度だけ作成すればよいことに注意してください。GEFは、ビューアーの中にコネクションが既に存在するかどうかを最初にチェックすることによって、コネクションが1度しか作成されないようにします。

コネクションを行う

コネクションEditPart実装を書くのも、それほど大きな違いはありません。まず、AbstractConnectionEditPartをサブクラス化します。これまでと同じく、モデルから図形へ属性をマッピングするためにrefreshVisuals()を実装します。コネクションも制約を持っている場合がありますが、これまでの制約とは少し異なります。ここでは、コネクション・ルーターがコネクションを曲げるために制約が使われます。さらに、コネクションEditPartの図形は、Draw2DConnectionである必要があります。そしてDraw2D Connectionでは、もう1つの重要な要求である、コネクション・アンカーが導入されています。

コネクションは、その両端をConnectionAnchorでアンカーする必要があります。従って、コネクションEditPartまたはノード実装の中で、どのアンカーを使うのかを示す必要があります。GEFではデフォルトで、ノードEditPartsがNodeEditPartインターフェースを実装することでアンカーを提供している、と想定しています。その理由の1つは、アンカーの選択は、それぞれの端のノードが使用している図形に依存するためです。コネクションEditPartは、ノードが使用している図形について、何も知っている必要はありません。もう1つの理由は、ユーザーがコネクションを作成する際にコネクションEditPartは存在しないため、ノードは自分でフィードバックを示せる必要があるためです。ここではリスト2に続いて、必要なアンカー・サポートを追加します(リスト3)。

リスト3. 「ノード」EditPartにアンカー・サポートを追加する
public class MyNodeEditPart
extends AbstractGraphicalEditPart
implements NodeEditPart
{
...
public ConnectionAnchor getSourceConnectionAnchor(ConnectionEditPart connection) {
return new ChopboxAnchor(getFigure());
}
public ConnectionAnchor getSourceConnectionAnchor(Request request) {
return new ChopboxAnchor(getFigure());
}
public ConnectionAnchor getTargetConnectionAnchor(ConnectionEditPart connection) {
return new ChopboxAnchor(getFigure());
}
public ConnectionAnchor getTargetConnectionAnchor(Request request) {
return new ChopboxAnchor(getFigure());
}

...
}

ヒント2

NodeEditPartインターフェースを実際に実装するのを忘れないでください。忘れてしまうと、そのメソッドは決して呼ばれることがありません。

コネクションを使うメソッドは、既存のコネクションEditPartに対してアンカーを設定する際に使われるメソッドです。他の2つのメソッドは、Requestを使います。これらのメソッドは、ユーザーが新しいコネクションを作成する際の編集に使われます。例えばchopboxアンカーは、全てのケースで返されます。chopboxアンカーは単純に、ラインがノードの図形を囲むボックスと交差する点を見つけます。

コネクションEditPartの実装は比較的単純です。図形も作成する必要がないことに注意してください。これは、ほとんどの使い方では、デフォルトで作成されるPolylineConnectionで充分なためです(リスト4)。

リスト4. 最初のConnection EditPart実装
public class MyConnectionEditPart extends AbstractConnectionEditPart {
protected void createEditPolicies() {
...
}

protected void refreshVisuals() {
PolylineConnection figure = (PolylineConnection)getFigure();
MyConnection connx = (MyConnection)getModel();
figure.setForegroundColor(MagicHelper.getConnectionColor(connx));
figure.setRoutingConstraint(MagicHelper.getConnectionBendpoints(connx));
}
}

ヒント3

最も重要なことは、いつConnectionEditPartsを使うべきか、いつ使うべきではないかを知ることです。コネクションEditPartは、ユーザーが選択し、対話動作する相手がある場合に使われます。コネクションEditPartは、恐らくモデル中のオブジェクトに直接の相関があり、また通常は、コネクションEditPartのみを単独で削除することができます。
ラインを引くためのノードまたはコンテナーのみしかない場合には、図形のペイント・メソッドの中でラインを引くか、あるいはPolyline図形を含む図形を合成します。
コネクションには、常にソースとターゲットが必要です。ソースまたはターゲットなしで存在できるコネクションが必要な場合には、AbstractGraphicalEditPartのみを継承し、コネクション図形を使った方が賢明です。

モデルをリスンする

EditPartは、作られると、モデルからの変更通知のリスンを開始します。GEFはモデルに中立なので、全てのアプリケーションは自分のリスナーを追加する必要があり、その結果としての通知を処理する必要があります。ハンドラーは通知を受信すると、提供されたメソッドの1つを使って、強制的に更新を行います。例えば子が削除された場合には、refreshChildren()を呼ぶと、対応するEditPartと図形が削除されます。単純な属性変更に対しては、refreshVisuals()を使います。先ほど触れた通り、表示された全属性を不必要に更新するのを避けるために、このメソッドを複数の部分に分割した方が適切な場合もあります。

よくあるメモリー・リークの原因として、リスナーを追加しておいて削除するのを忘れる、という問題が挙げられます。それを避けるため、リスナーを追加、削除する場所をAPIの中に明確に記述します。EditPartはactivate()を継承して、後で削除すべき全てのリスナーを追加する必要があります。これらのリスナーは、deactivate()を継承して削除します。リスト5は、モデル通知のための、ノードEditPart実装への追加を示しています。

リスト5. 「ノード」EditPartでのモデル変化をリスンする
public class MyNodeEditPart
extends AbstractGraphicalEditPart
implements NodeEditPart, ModelListener
{
...

public void activate() {
super.activate();
((MyModel)getModel()).addModelListener(this);
}

public void deactivate() {
((MyModel)getModel()).removeModelListener(this);
super.deactivate();
}

public void modelChanged(ModelEvent event) {
if (event.getChange().equals("outgoingConnections"))
refreshSourceConnections();
else if (event.getChange().equals("incomingConnections"))
refreshTargetConnections();
else if (event.getChange().equals("icon")
|| event.getChange().equals("name"))
refreshVisuals();
}

...
}

モデルを編集する

ここまで、EditPartがどのように作られ、EditPartがどのように自分の視覚効果を作成し、モデルが変化した場合にEditPartがどのように自分を更新するかについて説明してきました。EditPartはこうしたことの他に、モデルを変更する場合にも中心的な役割を果たします。これが起こるのは、あるコマンドに対するリクエストがEditPartに送信された時です。リクエストは、(例えばマウスをドラッグする際などに)フィードバックを示すようEditPartに依頼するためにも使われます。EditPartは、与えられたリクエストを、サポートするか、防止するか、あるいは無視します。サポートされる、あるいは防止されるリクエストのタイプによって、EditPartの振る舞いが決まります。

ここまでは、モデルの構造とプロパティーをビューの中にマッピングすることに注目してきました。実はEditPartクラスの中で行うこと自体は、それだけなのです。EditPartクラスの振る舞いは、『EditPolicies』と呼ばれる、一連のプラグ可能ヘルパーによって決まります。これまで示した例では、createEditPolicies()というメソッドを無視してきました。このメソッドが実装できれば、EditPartに関しては、ほとんど終わりです。もちろん、この編集ポリシーは用意する必要があります(編集ポリシーは、アプリケーションのモデルの変更方法を知っています)。

振る舞いの編集はプラグ可能なため、様々なEditPart実装を開発する際には、モデルをビューにマッピングしモデル更新を処理するという課題に基づいて、クラス階層構造を作成することができます。


ステップ4. すべてをまとめる

ここまで来ると、モデルをグラフィカルに表示するために必要な全ての断片が揃ったことになります。最終的な組み立てを行うためには、IEditorPartを使います。しかしGEFのビューアーも、ビューの中でもダイアログの中でも、コントロールを配置できる場所であれば、どこででも使うことができます。このステップのためには、自分のUIプラグイン(開かれているリソースに対するエディターとファイル・エクステンションを定義します)を持っている必要があります。モデルは、同じプラグイン、あるいは別のプラグインの中で定義されます。また、まだ編集機能が何もないので、既にデータの入ったモデルも必要です。

サンプルのモデル・データを用意するには幾つかの方法があります。この場合では、エディターが開かれると(実際のファイル内容を無視して)コードでモデルを作成します。そのためには、テスト・ファクトリーがあると想定します。あるいは、リソースにあらかじめデータを追加するサンプル・ウィザードを作成します(通常のウィザードは、単に空のダイヤグラムを作成するだけです)。そして最後に、ドキュメントの内容はテキスト・エディターを使って手動で書くこともできます。

これでサンプル・モデルはできたので、このモデルを表示するエディター部分を作りましょう。手軽な方法としては、GEFのGraphicalEditorをサブクラス化、あるいはコピーします。このクラスは、ScrollingGraphicalViewerのインスタンスを作成し、エディターのコントロールとして動作するキャンバスを構築します。これは、GEFを使い始めるために用意されているコンビニエンス・クラスです。適切に振る舞うEclipseエディターであれば、この他にも考慮に値する機能を幾つも持っています(例えば悲観的チーム環境や、削除あるいは移動されるリソース、など)。

リスト6は、エディター実装の一例です。幾つかの抽象メソッドを実装する必要があります。この記事では、モデル・パーシスタンスとマーカーを無視します。グラフィカル・ビューアーにダイヤグラムが現れるようにするためには、2つのことをする必要があります。まず、自分のEditPartファクトリーでビューをコンフィギュレーションし、ステップ3からのEditPartを構築します。次に、ダイヤグラム・モデル・オブジェクトをビューアーに渡します。

リスト6. Editor Partを実装する
public class MyEditor extends GraphicalEditor {
public MyEditor() {
setEditDomain(new DefaultEditDomain(this));
}

protected void configureGraphicalViewer() {
super.configureGraphicalViewer(); //Sets the \
viewer's background to System "white"
getGraphicalViewer().setEditPartFactory(new MyGraphicalEditpartFactory());
}

protected void initializeGraphicalViewer() {
getGraphicalViewer().setContents(MagicHelper.constructSampleDiagram());
}
public void doSave(IProgressMonitor monitor) {
...
}
public void doSaveAs() {
...
}
public void gotoMarker(IMarker marker) {
...
}
public boolean isDirty() {
...
}
public boolean isSaveAsAllowed() {
...
}
}

ここから先は

単にモデルがあるだけの状態から、そのモデルをグラフィカル・エディターで表示できるところまで来ました。しかし、これは基礎にすぎません。編集ポリシーについては簡単に触れました。編集ポリシーについての詳細は、GEFのSDKに含まれている開発者資料を読んでください。また、GEFのホームページ(参考文献を見てください)には、編集ポリシーの各タイプの使い方を示す例も用意されています。

またGEFには、パレットも用意されています。パレットは、ダイヤグラムの中にオブジェクトを作成するための、一連のツールを表示します。ユーザーはツールをアクティブに、つまりネイティブのドラッグ・アンド・ドロップを使って、パレットからアイテムを直接ドラッグすることができます。またユーザーが内容をカスタム化することもできるようになっています。

GEFでは、幾つかのJFaceアクションも利用可能です。アプリケーションのメニューやツールバー、コンテキスト・メニューの中では、アンドゥや整列、削除などを使うこともできます。

最後に、アプリケーションは、アウトライン・ビューとプロパティー・ビューの両方をサポートする必要があります。アウトライン・ビューは、ナビゲーション型の編集にも限定的な編集にも使用することができます。これには、GEFのTreeViewerや、オーバービュー・ウィンドウも使うことができます。プロパティー・シートを使うと、現在何が選択されていても、その詳細なプロパティーをユーザーが見たり編集したりすることができます。

選択を示し、ユーザーが変更を行えるようにするためには、EditPartに編集ポリシーを追加する必要があります。これについては、GEFのホームページにある例や、GEFのSDKに含まれている開発者資料を参照してください。

GEFやEclipseワークベンチに用意された他の機能は、この記事の範囲外ですが、下記が参考になるかも知れません。

  • パレット: ツール・パレットは、ダイヤグラムの中に新しいオブジェクトを作成するための『デファクト』の方法です。GEFは、表現力豊かなパレットを持っています。このパレットは、ドラッグ・アンド・ドロップや複数ドロワー(multipledrawers)、レイアウト設定、さらにアプリケーションで必要となれば、ユーザーが内容をカスタム化することさえできます。
  • Actionバー: EditorやViewは、ツールバーやメニュー、コンテキスト・メニューなどにActionを付加することができます。GEFには、再利用可能なアクション実装が幾つか用意されていますが、それをどこかに表示するかどうかは、アプリケーション次第です。
  • プロパティー・シート: プロパティー・シートは、選択されたアイテムのプロパティーの詳細を表示するために使われます。GEFでは、EditPartあるいはモデルにプロパティー・シートのサポートを追加することができます。
  • アウトライン: アウトライン・ビューは、ダイヤグラムの構造を表現するために使われる場合が多いのですが、他のものに対しても一般的に使用することができます。GEFのTreeViewerは、アウトライン・ビューでよく使われます。

参考文献

学ぶために

  • Eclipse Projectは、EclipseワークベンチやGEFなどのオープンソース技術のためのホストです。
  • IBM developerWorksのEclipse project resourcesを訪れ、皆さんのEclipseスキルを向上させてください。
  • developerWorksに用意された、Eclipseに関する資料を利用してください。
  • developerWorksのOpen sourceゾーンをご覧ください。オープンソース技術を使った開発や、IBM製品でオープンソース技術を使用するためのハウ・ツー情報やツール、プロジェクトの更新情報など、豊富な情報が用意されています。
  • developerWorks technical events and webcastsを利用して、最新技術を学んでください。

製品や技術を入手するために

  • GEFからプラグインをダウンロードし、またGEFに関する様々な情報を入手してください。
  • 皆さんの次期オープンソース開発プロジェクトを、IBM trial softwareを使って革新してください。ダウンロードまたはDVDで入手することができます。

議論するために

  • GEF newsgroupは、疑問に対する答えを見つけ、また質問をするために最適の場所です。
  • developerWorks blogsからdeveloperWorksコミュニティーに加わってください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Java technology
ArticleID=237195
ArticleTitle=Graphical Editing Frameworkを使用してEclipseベースのアプリケーションを作成する
publish-date=06272006