ビューの準備
グラフィカル・ビューアーを使用する場合、ここでの作業は、モデルのデータをどのように表示するかを決定し、必要があれば独自のフィギュア・クラスを作成するということになります。
ビューとしてどのようなフィギュアを使用するかについては基本的に自由ですが、対応するモデルの形態によって、使用するフィギュアのタイプはある程度限定されます。
| モデルのタイプ | ビューとして使用するフィギュアのタイプ |
| ほかのモデルの親となるモデル | この場合、ビューは、子モデルに対応するビューを格納することのできるコンテナーになります。したがって、このフィギュアは、レイアウト・マネージャー(あるいはそれと同様のもの)を使用して、子フィギュアを配置できるようにする必要があります。 |
| 子モデルを持たないモデル | どのようなフィギュアでもかまいません。 |
| モデル間の関係を表すモデル | この場合、ビューとして使用できるのは、Draw2DのConnectionインターフェイスを実装した、コネクション・フィギュアです。通常は、PolylineConnectionクラスか、そのサブクラスが使用されます。 |
また、複数のフィギュアで1つのビューを構成することもできます。サンプルでは、LabeledConnectionモデルを表現するビューとして、PolylineConnectionとLabelフィギュアを組み合わせたものを使用しています。
ビューアーとして、ツリー・ビューアーを使用する場合は、ビューはSWTのTreeItemを使用することが決まっているので、TreeItemのラベル文字列とアイコンを提供するだけです。
EditPartの作成
EditPartは、GEFに用意されている3つのクラスのうちの、どれかを拡張することで作成します。どのクラスを選択するかは、対応するビューのタイプによって決まります。
| ビューのタイプ | 使用するEditPartクラス |
| コネクション・フィギュア | AbstractConnectionEditPart |
| 通常のフィギュア | AbstractGraphicalEditPart |
TreeItem |
AbstractTreeEditPart(この記事では使用しません) |
この内、AbstractGraphicalEditPartを使用する場合、そのEditPartに対してコネクションを張る必要があれば、さらに「NodeEditPart」インターフェイスを実装する必要があります。このような「ノードEditPart」は、コネクションを接続するために、コネクション・アンカーを提供する必要があります。
サンプル・アプリケーションでは、「LabelNode」と「EllipseNode」に対応するEditPartがノードEditPartになります。また、「ExampleDiagram」に対応するEditPartは、コネクションを接続する必要がないので、AbstractGraphicalEditPartクラスのサブクラスとして作成します。「LabeledConnection」と「SolidConnection」モデルに対応するEditPartは、コネクション・フィギュアをビューとして使用するので、AbstractConnectionEditPartクラスを使用して作成します。
前述の通り、EditPartは、ビューの作成と更新、および、モデルの状態変化の監視を行います。EditPartとしてAbstractGraphicalEditPartクラスを使用したとしても、AbstractConnectionEditPartクラスを使用したとしても、これらの機能を実装する方法は同じです。
ビューの作成
ビューの作成は、EditPartクラスのcreateFigure()メソッド内で行います。AbstractConnectionEditPartクラスの場合は、デフォルトでPolylineConnectionフィギュアが作成されます。
public class ExampleDiagramEditPart extends BaseGraphicalEditPart { ... protected IFigure createFigure() { // ビューアー全体を占めるビューを作成 IFigure figure = new FreeformLayer(); figure.setLayoutManager(new FreeformLayout()); return figure; } ... }
public class SolidConnectionEditPart extends BaseConnectionEditPart { ... protected IFigure createFigure() { PolylineConnection connection = new PolylineConnection(); // コネクションに加えるデコレーション(デフォルトは三角型) PolygonDecoration decoration = new PolygonDecoration(); // デコレーションの形を矢印型にする PointList pointList = new PointList(); pointList.addPoint(0,0); pointList.addPoint(-2,2); pointList.addPoint(-1,0); pointList.addPoint(-2,-2); decoration.setTemplate(pointList); // デコレーションをコネクションのターゲット側に配置 connection.setTargetDecoration(decoration); return connection; } ... }
モデルの変更監視と、ビュー、EditPartの更新処理
モデル・クラスでは、PropertyChangeSupportを使用して、モデルの変更を通知できるようになっているので、EditPartクラスでは、PropertyChangeListener インターフェイスを実装して、その通知を受け取ることができるようにします。したがって、ビューの更新は、propertyChangeメソッド内で行えばよいことになります。
また、モデルの変更が行われる可能性があるのは、EditPartがアクティブな状態のときだけですから、EditPartのactivate()メソッドで、状態監視を開始し、deactivate()メソッドで監視を終了するようにします。
public abstract class BaseGraphicalEditPart extends AbstractGraphicalEditPart implements PropertyChangeListener { public void activate() { super.activate(); // モデルの状態変化の監視を開始 getModelElement().addPropertyChangeListener(this); } public void deactivate() { // モデルの状態変化の監視の終了 getModelElement().removePropertyChangeListener(this); super.deactivate(); } private ModelElement getModelElement() { return (ModelElement)getModel(); } public void propertyChange(PropertyChangeEvent event) { // このメソッドをオーバーライドして更新処理を行う } }
このような処理は、BaseConnectionEditPartクラスでも同様に行われています。
モデルから変更通知を受けた場合(propertyChangeメソッドが呼び出された場合)は、以下のようなEditPartクラスに用意されている更新メソッドを呼び出して、ビューを更新したり、EditPartの構造を更新したりします。
| 更新メソッド | 意味 |
refreshVisuals() |
ビューを更新します。デフォルトでは何もしません。 |
refreshChildren() |
子EditPartの構造を更新します。子モデルが追加されたり、削除されたりした場合、モデルの構造をEditPartに反映させるために呼び出します。このメソッドを呼び出せば、削除されたモデルに対応しているEditPartは自動的に削除されますし、追加されたモデルに対応するEditPartは作成・追加されます。 |
refreshSourceConnections() |
このEditPartをソースとするコネクションEditPartの構造を更新します。コネクションが追加、あるいは削除された場合に呼び出します。 |
refreshTargetConnections() |
このEditPartをターゲットとするコネクションEditPartの構造を更新します。コネクションが追加、あるいは削除された場合に呼び出します。 |
refresh() |
上記全てのメソッドを呼び出します。このメソッドは、EditPartが作成・初期化されるときに一度呼び出されます。独自の更新メソッドを用意する場合は、refresh()をオーバーライドして、その更新メソッドも呼び出すようにしておきます。 |
上記の更新メソッドの中で、refreshVisuals()メソッドは、オーバーライドしてモデルのプロパティをビューに反映させる処理を記述する必要があります。
public abstract class BaseNodeEditPart extends BaseGraphicalEditPart implements NodeEditPart { ... protected void refreshVisuals() { // モデルから制約を取得してビューの位置とサイズを更新する Rectangle constraint = (Rectangle) getNodeElement().getConstraint(); ((AbstractGraphicalEditPart) getParent()) .setLayoutConstraint(this, getFigure(), constraint); } public void propertyChange(PropertyChangeEvent event) { String propName = event.getPropertyName(); if (NodeElement.ID_CONSTRAINT.equals(propName)) refreshVisuals(); else if (NodeElement.ID_SOURCE_CONNECTION.equals(propName)) refreshSourceConnections(); else if (NodeElement.ID_TARGET_CONNECTION.equals(propName)) refreshTargetConnections(); } }
上記のコードのpropertyChangeメソッド内で更新処理の振り分けに使用しているプロパティ名は、モデル・クラスのsetterメソッドで設定したものです。また、refreshVisuals()メソッド内で行っている、ビューの制約(位置とサイズ)を更新は、親EditPartから行うことに注意してください。
EditPartの構造
ここまでの処理で、EditPartの主な役割である、ビューの作成・更新と、モデルの変更監視の機能を追加することができましたが、各EditPartは、モデルの構造を基した、階層構造をとります。このようなEditPartの構造を構築するために、子モデルを持つモデルを管理するEditPartは、getModelChildren()メソッドをオーバーライドして、子モデルのリストを返すようにしておく必要があります。親EditPartは、返された子モデルのリストを使用して、子EditPartを作成します。
また、この階層構造は、トップレベル・モデル(このサンプルではExampleDiagram)を管理するEditPartを頂点とした構造になります。このトップレベル・モデルに対応するEditPartは、「コンテンツ」と呼ばれます。コンテンツEditPartは、ビューとしてグラフィカル・ビューアーの描画領域全体を占めるフィギュアを提供することになります。実際には、コンテンツEditPartの上位に「RootEditPart」と呼ばれるEditPartが存在しますが、このEditPartは対応するモデルを持たない特殊なEditPartで、コンテンツには含まれません(RootEditPartについては後述します)。
public class ExampleDiagramEditPart extends BaseGraphicalEditPart { ... protected IFigure createFigure() { // ビューアー全体を占めるビューを作成 IFigure figure = new FreeformLayer(); figure.setLayoutManager(new FreeformLayout()); return figure; } ... private ExampleDiagram getExampleDiagram() { return (ExampleDiagram)getModel(); } ... protected List getModelChildren() { // ExampleDiagramモデルの子モデルを返す return getExampleDiagram().getChildren(); } ... }
また、コネクションEditPartは、他のEditPartとは異なる方法で構築されます。これは、コネクションEditPartがソースとターゲットを必要とするためです(Draw2Dでも、コネクションを表示するためには、ソース・アンカーとターゲット・アンカーが必要です)。
このため、コネクションEditPartを作成するノードEditPartクラスでは、getModelSourceConnections()メソッドと、getModelTargetConnections()メソッドという2つのメソッドを通して、コネクションEditPartの作成を行います。これらのメソッドは、先ほどのgetModelChildren()メソッドに相当するものです。
また、ノードEditPartは、NodeEditPartインターフェイスで定義されたメソッドを実装して、コネクション・アンカーを提供する必要があります。これらのメソッドの内、引数としてRequestが渡されるものは、コネクションの作成、または再接続操作が行われている間に表示される、フィードバック用コネクションで使用されるアンカーを作成するためのものです。
public abstract class BaseNodeEditPart extends BaseGraphicalEditPart implements NodeEditPart { ... private NodeElement getNodeElement() { return (NodeElement)getModel(); } protected List getModelSourceConnections() { // このEditPartをソース(接続元)とする // コネクション・モデルを返す return getNodeElement().getSourceConnections(); } protected List getModelTargetConnections() { // このEditPartをターゲット(接続先)とする // コネクション・モデルを返す return getNodeElement().getTargetConnections(); } 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()); } ... }
RootEditPart
前述の通り、RootEditPartは、モデルを持たないEditPartで、コンテンツEditPartの上位に位置します。RootEditPartは、ビューアーにダイアグラムを描画するための基礎となるレイヤーと、それらのレイヤーを格納するためのレイヤー・ペインを提供します。
RootEditPartには、いくつかの種類があり、どのRootEditPartを使用するかによって、ビューアーの機能が異なってきます。RootEditPartは、GraphicalViewer#setRootEditPartメソッドを使用してデフォルトのものと置き換えることができます。
| RootEditPartクラス | ビューアーの機能 |
FreeformGraphicalRootEditPart |
ビューがマイナス位置に移動された場合、自動的にビューアーの描画領域を拡張します。このRootEditPartを使用する場合は、コンテンツEditPartのビューとして、FreeformLayoutが設定された、FreeformFigureを使用する必要があります。 |
ScalableFreeformRootEditPart |
ビューアーにズーム・サポートを追加します。また、ビューがマイナス位置に移動された場合、自動的にビューアーの描画領域を拡張します。また、コンテンツEditPartのビューは、FreeformGraphicalRootEditPartの場合と同じ条件を満たす必要があります。 |
ScalableRootEditPart |
グラフィカル・ビューアーを使用する場合のデフォルトRootEditPart。ビューアーにズーム・サポートを追加します。 |
RootTreeEditPart |
ツリー・ビューアーを使用する場合のデフォルトRootEditPart。 |
サンプル・アプリケーションでは、ズーム機能は使用しないので、FreeformGraphicalRootEditPartクラスをRootEditPartとして使用しています。
EditPartFactory
全てのEditPartは、EditPartViewerに設定される「EditPartFactory」のcreateEditPartメソッドを通して作成されます。
public class GraphicalEditPartsFactory implements EditPartFactory { public EditPart createEditPart(EditPart context, Object model) { EditPart part = null; // モデルに対応するEditPartを作成する if (model instanceof ExampleDiagram) part = new ExampleDiagramEditPart(); else if (model instanceof EllipseNode) part = new EllipseNodeEditPart(); else if (model instanceof LabelNode) part = new LabelNodeEditPart(); else if (model instanceof SolidConnection) part = new SolidConnectionEditPart(); else if (model instanceof LabeledConnection) part = new LabeledConnectionEditPart(); if (part != null) part.setModel(model); // EditPartにモデルを設定する return part; } }
createEditPartメソッドには、モデル・クラスのインスタンスが渡されるので、対応するEditPartを作成し、モデルを設定してから返します。このcreateEditPartメソッドの呼び出しは、以下のようにして行われます。
- トップレベルのモデルが、
EditPartViewer#setInputメソッドによって設定されると、EditPartFactory#createEditPartメソッドが呼び出され、対応するEditPart(コンテンツEditPart)の作成が要求されます。 - 作成された
EditPartのgetModelChildrenメソッドが呼び出され、子モデルが要求されます。 - 子モデルが返された場合は、各子モデルごとに、
EditPartFactory#createEditPartメソッドが呼び出され、対応するEditPartの作成が要求されます。 - 作成された
EditPartのgetModelChildrenメソッドが空のリストを返すまで、2の手順が繰り返されます。
また、作成されたEditPartがノードEditPartの場合は、getModelChildrenメソッドの場合と同じように、getModelSourceConnections、getModelTargetConnectionsメソッドが呼び出され、コネクション・モデルが返された場合は、createEditPartメソッドによって、コネクションEditPartが作成されます。
EditPartの階層構造は、上記のような流れで作られます。例えば、サンプル・アプリケーションで、ビューアーが下図のように表示されている場合は、EditPartの構造、およびEditPartの作成に使用されるメソッドとの関係は、以下のようになっています。

上図のコネクションEditPartが、2つのノードEditPartによって管理されていることに注意してください。コネクションEditPartは、getModelSourceConnectionsメソッドと、getModelTargetConnectionsメソッドの両方で同じコネクション・モデルが返されなければ、作成されません。
