目次


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

グラフィック指向の Eclipse 開発者のための GEF、およびその他の選択肢を導入する方法

Comments

編集者からの注記: この記事は、2003年7月に Randy Hudson が書いた記事を Chris Aniszczyk が 2007年3月に更新したものです。

この記事では、GEF (Graphical Editing Framework) を使用するための手順をステップごとに説明します。各ステップはその説明だけで終わらせるのではなく、まずはアプリケーションのモデルのサブセットを使ってそれを動作させてみます。

例えば、初めはコネクションを無視したり、あるいはアプリケーションのグラフィック要素のサブセットだけに焦点を絞るという場合もあります。

その後に取り上げるのは、グラフィカルな編集をアプリケーションに採り入れるために使える GEF 以外の技術です。以前はスタンドアロンの GEF が Eclipse でのグラフィカルな編集に用意された唯一の選択肢でしたが、Eclipse の進化と共に事情は変わってきています。

GEF の概要

GEF では、開発者がモデルをグラフィカルに表示して編集することを前提として、Eclipse ワークベンチのどこででも使える (EditPartViewer 型の) ビューアーを用意しています。JFace ビューアーと同じく、GEF ビューアーは SWT コントロールを基にしたアダプターですが、類似点はこれ以外にありません。GEF ビューアーはモデル・ビュー・コントローラー (MVC) アーキテクチャーに基づいているからです。

コントローラーは、ビューとモデルの橋渡しをします (図 1 を参照)。各コントローラー (ここではコントローラーは EditPart と呼びます) が行うことは、モデルをビューへマッピングすることと、モデルの変更を行うことです。さらに EditPart はモデルを観察し、モデルの状態の変化に合わせてビューを更新します。EditPart はユーザーが操作するオブジェクトです。EditPart については後で詳しく説明します。

図 1. モデル・ビュー・コントローラー
図 1. モデル・ビュー・コントローラー

GEF には、グラフィカルなビューアーとツリー・ベースのビューアーの 2 種類のビューアーがあり、それぞれ提供するビューのタイプが異なります。グラフィカルなビューアーが使用するのは、SWT の Canvas で描画する図形です。これらの図形は、GEF の一部として組み込まれている Draw2D プラグイン内で定義されています。一方、TreeViewer は SWT の Tree と TreeItem をそのビューに使用します。

ステップ 1: 独自のモデルを持ち込む

GEF はモデルに関する情報を持っていません。そのため、以下に説明する特性に適合する限り、どのモデル・タイプでも使えるはずです。

モデルに含まれるものとは

モデルにはあらゆるものが含まれます。保持されて、復元されるのは、唯一モデルだけなので、アプリケーションではすべての重要なデータをモデルに保管します。編集、取り消し、やり直しの過程で保持されるのはモデルのみです。図形と EditPart は、時間が経つとガーベッジ・コレクションが行われて作成し直されます。

ユーザーが EditPart を操作しても、EditPart によってモデルが直接操作されることはなく、代わりに変更内容をカプセル化した Command が作成されます。Command は、ユーザー操作の検証や、取り消し/やり直しの操作をサポートするために使用できます。

厳密に言うと、概念的には Command もモデルの一部です。モデルそれ自体ではありませんが、モデルを編集する手段となります。ユーザーの取り消しが可能な変更はすべて、Command を使用して実行されます。理想的には、Command はモデルだけを認識し、EditPart や図形を参照しないようにしなければなりません。同様に、Command からユーザー・インターフェース (ポップアップ・ダイアログなど) を呼び出すことは極力避ける必要があります。

2 つのモデルについて

単純な GEF アプリケーションとしては、図を描画するエディターがあります (ここでは、図とはクラス図のことではなく単なるピクチャーを意味しています)。図は何らかの形状としてモデル化できます。その形状は位置や色といったプロパティーを持つこともあり、また複数の形状をまとめたグループ構造であることもあります。このアプリケーションには意外なことは何もなく、前に説明した要件を守るのも簡単です。

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

もうひとつの一般的な GEF アプリケーションとしては、クラス図エディターなどの UML エディターがあります。このダイアグラムに含まれる重要な情報の 1 つは、クラスを表示する (x, y) の位置です。モデルが記述するクラスには x プロパティーと y プロパティーがあるのが当然だと思うかもしれませんが、たいていの開発者は、モデルを意味のない属性で複雑にすることは避けたがるものです。そのようなアプリケーションでは、重要なセマンティックの詳細が保存されている基本モデルのことを、ビジネス・モデルという言葉で呼ぶことができます。一方、ダイアグラムに固有の情報はビュー・モデルに保管されます (ビュー・モデルとはビジネス・モデルに含まれる内容のビューのことで、1 つのダイアグラムに 1 つのオブジェクトが繰り返し表示されることもあります)。この分割はワークスペース内にも反映されることがあり、その場合、異なるリソースを使用してダイアグラムとビジネス・モデルを別々に維持することも可能です。また、1 つのビジネス・モデルに対して複数のダイアグラムがある場合もあります (図 3 を参照)。ビュー・モデルは、一般的な言葉では、表記モデルです。

図 3. ビジネス・モデルとビュー・モデルのモデル分割
図 3. ビジネス・モデルとビュー・モデルのモデル分割
図 3. ビジネス・モデルとビュー・モデルのモデル分割

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

通知ストラテジー

ビューの更新は必ずと言っていいほど、モデルからの通知の結果として行われます。そのため、モデルは何らかの通知メカニズムを提供し、そのメカニズムはアプリケーション内の適切な更新にマッピングされる必要があります。例外として考えられるのは、読み取り専用モデルや通知が不可能なモデルです。その例として、ファイル・システム、あるいはリモート接続が挙げられます。

通知ストラテジーは通常、分散型 (オブジェクトごと) にするか、集中型 (ドメインごと) にするかのどちらかです。ドメイン通知機能は、モデル内のあらゆるオブジェクトに対するすべての変更を認識し、その変更をドメイン・リスナーにブロードキャストします。アプリケーションにこの通知モデルを採用する場合は、ビューアーごとにドメイン・リスナーを追加することになります。リスナーは変更通知を受信すると、変更の影響のある EditPart を調べ、その変更を適切に再ディスパッチします。一方、アプリケーションに分散型の変更通知機能を適用する場合は、各 EditPart が、EditPart に影響を及ぼすモデル・オブジェクトに独自のリスナーを追加するのが一般的です。

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

次のステップは、Draw2D プラグインの図形を使ってモデルの表示方法を決めることです。この図形にはモデル・オブジェクトを表示するために直接使うことができるものがあります。例えば、Image と String を表示するには Label 図形を使用できます。目的の結果を得るためには、複数の図形、レイアウト・マネージャー、あるいは外枠を組み合わせなければならないこともあります。結局は、アプリケーションに固有の方法で描画する、独自の図形実装を作成することになる場合もあります。図形およびレイアウトの構成または実装方法については、GEF SDK 付属の Draw2D 開発者向けガイドに詳細が記載されています。

GEF で Draw2D を使用する際には、以下のガイドラインに従うとプロジェクトが管理しやくなり、要件の変更にも一層柔軟に対応できるようになります。

わざわざ一からやり直さないこと
用意されているレイアウト・マネージャーを組み合わせれば、たいていのものはレンダリングできます。ツールバーのレイアウト (縦または横の向き) と外枠のレイアウトを組み合わせて、複数の図形をいろいろと構成してみてください。独自のレイアウト・マネージャーを作成するのは、あくまでも最後の手段です。参考として、GEF に用意されているパレットを見てください。このパレットは、Draw2D の標準図形およびレイアウトを多数使用してレンダリングされています。
EditPart と図形を明確に区別しておくこと
EditPart が複数の図形、レイアウト、外枠からなる複合構造を使用している場合、その詳細情報はできるだけ EditPart に持たせないようにしてください。EditPart だけですべてを構築することは可能ですが (いい考えではありません)、それではコントローラーとビューを明確に分離することができません。EditPart が図形構造に関する詳細な情報を持っていると、類似した EditPart がその構造を再利用できなくなってしまいます。また、外観や構造を変更すると、予期しないバグが発生する原因にもなります。そこで必要となるのは、図形構造の詳細情報を持たせるサブクラスを独自に作成することです。そのサブクラスには、EditPart (コントローラー) がビューの更新に使用する最小限の API を定義します。コンサーンの分離と呼ばれるこの手法を使うと、再利用が促進され、バグも減少する結果となります。
図形からモデルまたは EditPart を参照しないこと
図形は EditPart またはモデルにアクセスしないようにしてください。場合よっては、EditPart 自体をリスナーとして図形に追加することもありますが、その場合は EditPart としてではなく、リスナーとしてだけ認識されるようにします。このような分離手法によっても再利用が促進されます。
コンテンツ・ペインを使用すること
別のグラフィック要素を含めるコンテナーを用意することがありますが、コンテナーの外側を囲む装飾が必要になります。例えば UML クラスは一般的にボックスとして表示されます。この場合、ボックスの上部にはクラス名、そして場合によってはステレオタイプのラベルが付き、下部は属性とメソッド用に予約されています。このようなボックスは、複数の図形を組み合わせることで実現できます。最初の図形はクラスのタイトル・ボックスです。もうひとつの図形はコンテンツ・ペインとして指定し、ここに属性とメソッド用の図形を含めます。こうしておけば、後で EditPart 実装を作成するときに、コンテンツ・ペインをすべての子要素の親として使用するように簡単に指定することができるのです。

ステップ 3: EditPart を作成する

次に、コントローラー (EditPart) でモデルとビューを橋渡しします。これは、GEF に「フレームワーク」を用意するステップです。用意されているクラスは抽象クラスなので、クライアントが実際にコードを作成しなければなりません。実際にコードを作成してみると、サブクラス化は馴染み深いだけでなく、モデルからビューへマッピングする方法として最も柔軟でわかりやすい方法であることがわかります。

サブクラス化には、3 つの基本実装が用意されています。ツリー・ビューアーに表示される EditPart には、AbstractTreeEditPart を使用してください。一方、グラフィカル・ビューアーでは AbstractGraphicalEditPart と AbstractConnectionEditPart を継承します。ここでは、グラフィカルな EditPart に焦点を絞りますが、ツリー・ビューアーで使用するときも原則は同じです。

EditPart のライフ・サイクル

独自の EditPart を作成する前に、EditPart はいつ構築されて、不要になった時点でどこへ行くのかを知っておくと役に立ちます。各ビューアーは、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 の一例です。このコンテンツ 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 の図形がダイアグラムの図形に追加されます。デフォルトでは、getModelChildren() は空のリストを返し、子がないことを示します。

よりグラフィカルな EditPart

残りの (ダイアグラムの中の項目を表す) EditPart には、おそらくグラフィカルに表示されるデータが含まれています。これらの EditPart が独自の構造 (コネクションやそれ自体の子など) を持つ場合もありますが、多くの GEF アプリケーションでは、ラベル付きアイコンがアイコン間のコネクションを伴って表示されます。例えば、EditPart が Label をその図形として使用し、モデルが名前、アイコン、そしてラベルに向かうコネクションとラベルから出るコネクションを提供するとします。リスト 2 は、このようなタイプの EditPart を実装する際の最初のコードです。

リスト 2. ノード EditPart の初期実装
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」となっているのは、図形には適切なサイズが使用されるという意味です。

refreshVisuals() メソッドは EditPart の初期化中に 1 度呼び出されるだけで、その後再び呼び出されることはありません。モデルの通知に応答する際の図形の更新に必要であれば、アプリケーションがその役割として refreshVisuals() メソッドを再度呼び出します。パフォーマンスを改善するには、モデル属性ごとのコードを固有のメソッド (または「切り替え」機能がある単一のメソッド) に分解するという方法が考えられます。このようにすると、モデルの通知時に最小限のコードを実行して、変更されたものだけを更新できます。

もう 1 つ、コネクションをサポートするためのコードの違いも注目に値する点です。getModelChildren() と同じく、getModelSourceConnections() と getModelTargetConnections() もノード間のリンクを表すモデル・オブジェクトを返します。スーパークラスが必要に応じて、対応する EditPart を作成し、ソースおよびターゲットコネクション EditPart のリストに追加します。ここで注意しなければならないのは、コネクションは両端のノードで参照されますが、コネクション EditPart を作成するのは 1 回だけにしなければならないということです。GEF は、最初にビューアーでコネクションの有無をチェックするため、コネクションが複数回作成されることはありません。

コネクションの作成

コネクション EditPart の実装を作成する方法も、大した違いはありません。まず、AbstractConnectionEditPart のサブクラス化から取り掛かってください。今までと同じく refreshVisuals() を実装すれば、モデルから図形に属性をマッピングできます。コネクションが制約を持つこともありますが、この場合の制約は今までとは多少違います。コネクションの制約は、コネクション・ルーターがコネクションを曲げるために使用します。さらに、コネクション EditPart の図形は Draw2D の Connection でなければならないため、これによりコネクション・アンカーという要件がもう 1 つ追加されます。

コネクションの両端には、ConnectionAnchor でアンカーを設定する必要があります。そのため、コネクション EditPart かノード実装のいずれかに、使用するアンカーを指定しなければなりません。GEF はデフォルトでは、NodeEditPart インターフェースを実装することにより、ノード EditPart が アンカーを提供していることを想定しています。その理由の 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());
    }

    ...
}

上記で引数にコネクションを取るメソッドは、既存のコネクション EditPart にアンカーを設定するときに使用するメソッドです。引数にリクエストを取る残りの 2 つのメソッドは、ユーザーが新規コネクションを作成するときの編集に使われます。上記の例では、いずれのメソッドも chopbox アンカーを返します。chopbox アンカーは単に、ノードの図形に含まれる境界ボックスと線が交差する点を検出するだけです。コネクション EditPart は比較的簡単に実装できます。デフォルトで作成される PolylineConnection は、ほとんどの用途に適しているので、図形を作成する必要すらないので、ご注意ください。

リスト 4. コネクション 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));
    }
}

モデルのリスニング

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 クラスの振る舞いは、EditPolicies と呼ばれる一連のプラグ可能ヘルパーによって決定されます。これまで説明した例では createEditPolicies() メソッドを無視してきました。このメソッドを実装すれば EditPart での作業はほとんど終わったことになります。あとはもちろん、アプリケーションのモデルを修正する方法を規定する編集ポリシーを作成するだけです。編集の振る舞いはプラグ可能なので、さまざまな EditPart 実装を開発する際に、モデルからビューへのマッピング、そしてモデルの更新処理といったタスクを中心として、クラス階層構造を作成できます。

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

ここまでのところで、モデルをグラフィカルに表示するために必要なすべての部品が揃いました。最終的な組み立てを行うには、IEditorPart を使用します。ただし GEF のビューアーは、ビューやダイアログの中でも使用でき、あるいはコントロールを配置できる場所であれば、ほとんどどこででも使用することができます。このステップに必要なのは、UI プラグインです。この UI プラグインで、開かれているリソースに対してエディターとファイル拡張子を定義することになります。モデルは同じプラグイン、あるいは別のプラグインの中で定義される可能性があります。また、編集機能がまだないので、事前にデータを設定したモデルも必要です。

サンプル・モデル・データを提供する方法はいくつかあります。このサンプルでは、エディターを開いたときにコード内でモデルを作成し、ファイルの実際のコンテンツは無視するので、テスト・ファクトリーが存在することを前提とします。あるいは、リソースにデータを事前入力するサンプル・ウィザードを作成したり (一般的なウィザードが作成するのは空のダイアグラムです)、最後の手段としてテキスト・エディターを使って文書のコンテンツを手作業で作成することもできます。

サンプル・モデルが用意できたら、モデルを表示するエディターの部分を作成してみましょう。この作業を手軽に開始する方法は、GEF のGraphicalEditor をサブクラス化するか、またはコピーすることです。このクラスは ScrollingGraphicalViewe のインスタンスを作成し、エディターのコントロールとして機能するキャンバスを構成します。これは GEF に取り掛かりやすいように用意されたコンビニエンス・クラスですが、Eclipse エディターが適切に振る舞うようにするには、ペシミスティック・チーム環境、削除あるいは移動されるリソースなど、他にも考慮すべきことがたくさんあります。

リスト 6 は、エディター実装の一例です。実装しなければならない抽象メソッドはいくつかありますが、モデル・パーシスタンスとマーカーはこの記事の範囲外なので無視します。グラフィカル・ビューアーにダイアグラムを表示するには、2 つの作業が必要です。まず、独自の EditPart ファクトリーでビューアーを構成してステップ 3 の EditPart を構築し、次にダイアグラム・モデル・オブジェクトをビューアーに渡します。

リスト 6. エディター部分の実装
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 については、GEF SDK に付属している「GEF Programmer's Guide」を読むと詳細がわかります。この記事に直接付属しているサンプルはないので、GEF の初心者には、Bo Majewski が書いた優れた記事「A Shape Diagram Editor」に付属の形状サンプルを参照することをお勧めします (「参考文献」を参照)。

GEF の完成度が高くなるにつれ、Eclipse でグラフィカル・エディターを開発しやすくするために GEF をベースにビルドされた強固なフレームワークもいくつか登場しています。これらのフレームワークについて触れないわけにはいきません。開発者たちは多くの場合、GEF をそのまま使うのではなく、これらのフレームワークを利用しているからです。

GMF (Graphical Modeling Framework)

GMF プロジェクトは、グラフィカル・エディターを手作業で作成する際の (とくに、Eclipse Modeling Framework を使用する中での) フラストレーションから誕生しました 。GMF を使うと、セマンティック (ビジネス・ロジック) モデルを概念 (グラフィック) モデルに効率的にマッピングすることができます (図 5 を参照)。数少ない構成ファイルでこのマッピングを完成させると、GMF は完全に機能するグラフィカル・エディターを自動的に生成してくれます。

図 5. GMF での開発フロー
図 5. GMF での開発フロー
図 5. GMF での開発フロー

モデルに Eclipse Modeling Framework を使用するような使用事例では、GEF を直接使用する代わりに、GMF を利用することを断然お勧めします。詳しくは、Chris Aniszczyk の「Eclipse GMF を 15 分で学ぶ」を読んでください (「参考文献」を参照)。

Zest

Zest は軽量の視覚化ツールキット (図 6 を参照) で、従来の GEF エディターを JFace のようにラップします。Zest は JFace をモデルにしているため、すべての Zest ビューは既存の Eclipse ビューと同じ標準および規約に準拠します (ラベルおよびコンテンツのプロバイダーだと思ってください)。つまり、既存のアプリケーションで使用しているプロバイダー、アクション、そしてリスナーを Zest 内でも利用できるということです。さらに Zest には、視覚化に簡単に適用できる再利用可能なレイアウトが用意されています。

図 6. Zest による視覚化の例
図 6. Zest による視覚化の例
図 6. Zest による視覚化の例

Zest API は単純なので、新規 Graph オブジェクトを作成してノードとエッジをそれぞれ追加するだけで、グラフを作成することができます。

リスト 7 Zest コードの例
public static void main(String[] args) {
		 		 // Create the shell
		 		 Display d = new Display();
		 		 Shell shell = new Shell(d);
		 		 shell.setText("GraphSnippet1");
		 		 shell.setLayout(new FillLayout());
		 		 shell.setSize(400, 400);

		 		 Graph g = new Graph(shell, SWT.NONE);

		 		 GraphNode n = new GraphNode(g, SWT.NONE, "Paper");
		 		 GraphNode n2 = new GraphNode(g, SWT.NONE, "Rock");
		 		 GraphNode n3 = new GraphNode(g, SWT.NONE, "Scissors");
		 		 new GraphConnection(g, SWT.NONE, n, n2);
		 		 new GraphConnection(g, SWT.NONE, n2, n3);
		 		 new GraphConnection(g, SWT.NONE, n3, n);
                 g.setLayoutAlgorithm(
                     new SpringLayoutAlgorithm(
                         LayoutStyles.NO_LAYOUT_NODE_RESIZING),true);

		 		 shell.open();
		 		 while (!shell.isDisposed()) {
		 		 		 while (!d.readAndDispatch()) {
		 		 		 		 d.sleep();
		 		 		 }
		 		 }
		 }

編集ではなく視覚化のみを主眼とした使用事例では、Zest が提供する機能を使用することをお勧めします。すでに別の視覚化ツールキットで作業しているとしても、Zest のレイアウトは、他のツールキットでも再利用できるように作られています。

まとめ

この記事が目的としたのは、GEF の概要をしっかりと説明することです。記事の最初の 5 つのセクションでその目的を果たした後、GEF の領域を外れて、グラフィック指向の Eclipse 開発者が使用可能な選択肢をいくつか取り上げました。結局は、自分の使用事例を評価して、どのグラフィック・フレームワークがニーズに最も適しているかを調べることが重要となります。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

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