モデル要素の作成と削除、プロパティの設定、要素間のリンクの作成と削除を行うコードを記述できます。 すべての変更は、トランザクション内で行う必要があります。 要素が図に表示されている場合、ダイアグラムはトランザクションの終了時に自動的に "固定" されます。
DSL 定義の例
これは、このトピックの例の DslDefinition.dsl の主要な部分です。
このモデルは、次の DSL のインスタンスです。
参照と名前空間
このトピックのコードを実行するには、次を参照する必要があります。
Microsoft.VisualStudio.Modeling.Sdk.11.0.dll
コードでは、次の名前空間を使用します。
using Microsoft.VisualStudio.Modeling;
さらに、DSL が定義されているプロジェクトとは異なるプロジェクトでコードを記述する場合は、Dsl プロジェクトによってビルドされたアセンブリをインポートする必要があります。
モデル内を移動する
プロパティ
DSL 定義で定義したドメイン プロパティは、プログラム コードでアクセスできるプロパティになります。
Person henry = ...;
if (henry.BirthDate < 1500) ...
if (henry.Name.EndsWith("VIII")) ...
プロパティを設定する場合は、 トランザクション内で設定する必要があります。
henry.Name = "Henry VIII";
DSL 定義でプロパティの Kind が 計算される場合は、設定できません。 詳細については、「 計算およびカスタム ストレージのプロパティ」を参照してください。
人間関係
DSL 定義で定義したドメイン リレーションシップは、リレーションシップの各端にあるクラス上の 1 つのプロパティのペアになります。 プロパティの名前は、DslDefinition ダイアグラムに、リレーションシップの各側のロールのラベルとして表示されます。 ロールの多重度に応じて、プロパティの型はリレーションシップのもう一方の端にあるクラスか、そのクラスのコレクションのいずれかになります。
foreach (Person child in henry.Children) { ... }
FamilyTreeModel ftree = henry.FamilyTreeModel;
関係の両端にある属性は常に相互です。 リンクが作成または削除されると、両方の要素のロール プロパティが更新されます。 次の式 ( System.Linq の拡張を使用) は、この例の ParentsHaveChildren リレーションシップに対して常に当てはまります。
(Person p) => p.Children.All(child => child.Parents.Contains(p))
&& p.Parents.All(parent => parent.Children.Contains(p));
ElementLinks。 リレーションシップは、ドメイン リレーションシップの種類のインスタンスである リンクと呼ばれるモデル要素によっても表されます。 リンクには、常に 1 つのソース要素と 1 つのターゲット要素があります。 ソース要素とターゲット要素は同じにすることができます。
リンクとそのプロパティにアクセスできます。
ParentsHaveChildren link = ParentsHaveChildren.GetLink(henry, edward);
// This is now true:
link == null || link.Parent == henry && link.Child == edward
既定では、リレーションシップの複数のインスタンスは、モデル要素のペアをリンクできません。 ただし、DSL 定義で、リレーションシップの Allow Duplicates フラグが true の場合は、複数のリンクが存在する可能性があるため、 GetLinksを使用する必要があります。
foreach (ParentsHaveChildren link in ParentsHaveChildren.GetLinks(henry, edward)) { ... }
リンクにアクセスするための他の方法もあります。 例えば次が挙げられます。
foreach (ParentsHaveChildren link in ParentsHaveChildren.GetLinksToChildren(henry)) { ... }
隠れた役割 DSL 定義で、特定のロールに対して Is Property Generated が false の場合、そのロールに対応するプロパティは生成されません。 ただし、リレーションシップのメソッドを使用して、リンクにアクセスしたり、リンクを走査したりすることはできます。
foreach (Person p in ParentsHaveChildren.GetChildren(henry)) { ... }
最も頻繁に使用される例は、モデル要素を図に表示する図形にリンクする PresentationViewsSubject リレーションシップです。
PresentationViewsSubject.GetPresentation(henry)[0] as PersonShape
要素ディレクトリ
要素ディレクトリを使用して、ストア内のすべての要素にアクセスできます。
store.ElementDirectory.AllElements
次のような要素を検索するメソッドもあります。
store.ElementDirectory.FindElements(Person.DomainClassId);
store.ElementDirectory.GetElement(elementId);
クラス情報へのアクセス
DSL 定義のクラス、リレーションシップ、およびその他の側面に関する情報を取得できます。 例えば次が挙げられます。
DomainClassInfo personClass = henry.GetDomainClass();
DomainPropertyInfo birthProperty =
personClass.FindDomainProperty("BirthDate")
DomainRelationshipInfo relationship =
link.GetDomainRelationship();
DomainRoleInfo sourceRole = relationship.DomainRole[0];
モデル要素の先祖クラスは次のとおりです。
ModelElement - すべての要素とリレーションシップが ModelElements です
ElementLink - すべてのリレーションシップが ElementLinks です
トランザクション内で変更を実行する
プログラム コードがストア内の何かを変更するたびに、トランザクション内で行う必要があります。 これは、すべてのモデル要素、リレーションシップ、図形、ダイアグラム、およびそれらのプロパティに適用されます。 詳細については、Transactionを参照してください。
トランザクションを管理する最も便利な方法は、using ステートメントで囲まれたtry...catch ステートメントを使用することです。
Store store; ...
try
{
using (Transaction transaction =
store.TransactionManager.BeginTransaction("update model"))
// Outermost transaction must always have a name.
{
// Make several changes in Store:
Person p = new Person(store);
p.FamilyTreeModel = familyTree;
p.Name = "Edward VI";
// end of changes to Store
transaction.Commit(); // Don't forget this!
} // transaction disposed here
}
catch (Exception ex)
{
// If an exception occurs, the Store will be
// rolled back to its previous state.
}
1 つのトランザクション内で任意の数の変更を行うことができます。 アクティブなトランザクション内で新しいトランザクションを開くことができます。
変更を永続的にするには、トランザクションを破棄する前に Commit する必要があります。 トランザクション内でキャッチされない例外が発生した場合、ストアは変更前の状態にリセットされます。
モデル要素の作成
次の例では、既存のモデルに要素を追加します。
FamilyTreeModel familyTree = ...; // The root of the model.
using (Transaction t =
familyTree.Store.TransactionManager
.BeginTransaction("update model"))
{
// Create a new model element
// in the same partition as the model root:
Person edward = new Person(familyTree.Partition);
// Set its embedding relationship:
edward.FamilyTreeModel = familyTree;
// same as: familyTree.People.Add(edward);
// Set its properties:
edward.Name = "Edward VII";
t.Commit(); // Don't forget this!
}
この例では、要素の作成に関する次の重要な点を示します。
ストアの特定のパーティションに新しい要素を作成します。 モデル要素とリレーションシップの場合、形状は除き、通常、これは既定のパーティションです。
埋め込みリレーションシップのターゲットにします。 この例の DslDefinition では、各 Person が、リレーションシップ FamilyTreeHasPeople を埋め込むターゲットである必要があります。 これを実現するには、Person オブジェクトの FamilyTreeModel ロール プロパティを設定するか、FamilyTreeModel オブジェクトの People ロール プロパティに Person を追加します。
新しい要素のプロパティ、特に DslDefinition で
IsNameが true であるプロパティを設定します。 このフラグは、要素を所有者内で一意に識別するために機能するプロパティをマークします。 この場合、Name プロパティにはそのフラグがあります。この DSL の DSL 定義がストアに読み込まれている必要があります。 メニュー コマンドなどの拡張機能を記述する場合、これは通常、既に true になります。 また、モデルをストアに明示的に読み込んだり、 ModelBus を使用してモデルを読み込んだりすることもできます。 詳細については、「 方法: プログラム コードでファイルからモデルを開く」を参照してください。
この方法で要素を作成すると、図形が自動的に作成されます (DSL に図がある場合)。 自動的に割り当てられた場所に、既定の図形、色、その他の機能が表示されます。 関連付けられている図形の表示場所と方法を制御する場合は、「 要素とその図形の作成」を参照してください。
リレーションシップ リンクの作成
DSL 定義の例では、2 つのリレーションシップが定義されています。 各リレーションシップは、リレーションシップの各端にあるクラスの ロール プロパティ を定義します。
リレーションシップのインスタンスを作成するには、3 つの方法があります。 これら 3 つのメソッドはそれぞれ同じ効果があります。
ソース ロール プレーヤーのプロパティを設定します。 例えば次が挙げられます。
familyTree.People.Add(edward);edward.Parents.Add(henry);
ターゲット ロール プレーヤーのプロパティを設定します。 例えば次が挙げられます。
edward.familyTreeModel = familyTree;このロールの多重度は
1..1であるため、値を割り当てます。henry.Children.Add(edward);このロールの多重度は
0..*ので、コレクションに追加します。
リレーションシップのインスタンスを明示的に構築します。 例えば次が挙げられます。
FamilyTreeHasPeople edwardLink = new FamilyTreeHasPeople(familyTreeModel, edward);ParentsHaveChildren edwardHenryLink = new ParentsHaveChildren(henry, edward);
最後のメソッドは、リレーションシップ自体にプロパティを設定する場合に便利です。
この方法で要素を作成すると、ダイアグラム上のコネクタが自動的に作成されますが、既定の図形、色、およびその他の特徴があります。 関連付けられているコネクタの作成方法を制御するには、「 要素とその図形の作成」を参照してください。
要素の削除
Delete()を呼び出して要素を削除します。
henry.Delete();
この操作では、次の内容も削除されます。
要素へのリンクと要素からのリンクによるリレーションシップ。 たとえば、
edward.Parentsにはhenryは含めなくなります。PropagatesDeleteフラグが true であるロールの要素。 たとえば、要素を表示する図形は削除されます。
既定では、すべての埋め込みリレーションシップはターゲットロールにおいてPropagatesDeleteがtrueである。
henryを削除してもfamilyTreeは削除されませんが、familyTree.Delete()はすべてのPersonsを削除します。
既定では、 PropagatesDelete は参照リレーションシップのロールには当てはまりません。
オブジェクトを削除すると、削除規則によって特定の伝達が省略される可能性があります。 これは、ある要素を別の要素に置き換える場合に便利です。 削除の伝播を防止するために、1つ以上の役割のGUIDを指定します。 GUID はリレーションシップ クラスから取得できます。
henry.Delete(ParentsHaveChildren.SourceDomainRoleId);
(この特定の例は、PropagatesDeleteリレーションシップのロールに対してfalseがParentsHaveChildrenされているため、効果はありません)。
場合によっては、要素自体に対するロックや、伝達によって削除される要素上のロックが存在することで、削除が防止されます。
element.CanDelete()を使用して、要素を削除できるかどうかを確認できます。
リレーションシップ リンクの削除
ロール プロパティから要素を削除することで、リレーションシップ リンクを削除できます。
henry.Children.Remove(edward); // or:
edward.Parents.Remove(henry); // or:
リンクを明示的に削除することもできます。
edwardHenryLink.Delete();
これら 3 つのメソッドはすべて同じ効果を持ちます。 必要なのは、そのうちの 1 つのみです。
ロールに 0..1 または 1..1 の多重度がある場合は、 nullまたは別の値に設定できます。
edward.FamilyTreeModel = null; 又は:
edward.FamilyTreeModel = anotherFamilyTree;
リレーションシップのリンクの順序を再作成する
特定のモデル要素をソースまたはターゲットとする特定のリレーションシップのリンクには、特定のシーケンスがあります。 これらは、追加された順序で表示されます。 たとえば、このステートメントは常に同じ順序で子を生成します。
foreach (Person child in henry.Children) ...
リンクの順序を変更できます。
ParentsHaveChildren link = GetLink(henry,edward);
ParentsHaveChildren nextLink = GetLink(henry, elizabeth);
DomainRoleInfo role =
link.GetDomainRelationship().DomainRoles[0];
link.MoveBefore(role, nextLink);
Locks
変更がロックによって防止される場合があります。 ロックは、個々の要素、パーティション、ストアで設定できます。 これらのレベルのいずれかに、行おうとする変更を妨げるロックがある場合、試みるときに例外が発生する可能性があります。 要素を使用してロックが設定されているかどうかを検出できます。GetLocks() は、名前空間 Microsoft.VisualStudio.Modeling.Immutabilityで定義されている拡張メソッドです。
詳細については、「 ロック ポリシーを定義して Read-Only セグメントを作成する」を参照してください。
コピーと貼り付け
要素または要素のグループを IDataObjectにコピーできます。
Person person = personShape.ModelElement as Person;
Person adopter = adopterShape.ModelElement as Person;
IDataObject data = new DataObject();
personShape.Diagram.ElementOperations
.Copy(data, person.Children.ToList<ModelElement>());
要素は、シリアル化された要素グループとして格納されます。
IDataObject の要素をモデルにマージできます。
using (Transaction t = targetDiagram.Store.
TransactionManager.BeginTransaction("paste"))
{
adopterShape.Diagram.ElementOperations.Merge(adopter, data);
}
Merge () は、 PresentationElement または ModelElementを受け取ることができます。
PresentationElementを指定する場合は、ターゲットダイアグラム上の位置を 3 番目のパラメーターとして指定することもできます。
ダイアグラムの移動と更新
DSL では、Person や Song などの概念を表すドメイン モデル要素は、図に表示される内容を表す shape 要素とは別です。 ドメイン モデル要素には、概念の重要なプロパティとリレーションシップが格納されます。 図形要素は、図上のオブジェクトのビューのサイズ、位置、色、およびコンポーネント パーツのレイアウトを格納します。
プレゼンテーション要素
DSL 定義では、指定する各要素によって、次のいずれかの標準クラスから派生したクラスが作成されます。
| 要素の種類 | 基底クラス |
|---|---|
| ドメイン クラス | ModelElement |
| ドメインの関係 | ElementLink |
| 図形 | NodeShape |
| Connector | BinaryLinkShape |
| ダイアグラム | Diagram |
図の要素は、通常、モデル要素を表します。 通常は (必ずしも)、 NodeShape はドメイン クラス インスタンスを表し、 BinaryLinkShape はドメイン リレーションシップ インスタンスを表します。 PresentationViewsSubjectリレーションシップは、ノードまたはリンク図形を、それが表すモデル要素にリンクします。
すべてのノードまたはリンク図形は、1 つの図に属します。 バイナリ リンク図形は、2 つのノード図形を接続します。
図形には、2 つのセットの子図形を含めることができます。
NestedChildShapes セット内の図形は、親の境界ボックス内に制限されます。
RelativeChildShapes リスト内の図形は、親の境界の外側または一部の外側 (ラベルやポートなど) に表示できます。 図には RelativeChildShapes がなく、 Parentもありません。
図形と要素間の移動
ドメイン モデル要素と図形要素は、 PresentationViewsSubject リレーションシップによって関連付けられます。
// using Microsoft.VisualStudio.Modeling;
// using Microsoft.VisualStudio.Modeling.Diagrams;
// using System.Linq;
Person henry = ...;
PersonShape henryShape =
PresentationViewsSubject.GetPresentation(henry)
.FirstOrDefault() as PersonShape;
同一の関係が、ダイアグラム上のコネクタに関係を結び付けます。
Descendants link = Descendants.GetLink(henry, edward);
DescendantConnector dc =
PresentationViewsSubject.GetPresentation(link)
.FirstOrDefault() as DescendantConnector;
// dc.FromShape == henryShape && dc.ToShape == edwardShape
このリレーションシップは、モデルのルートも図にリンクします。
FamilyTreeDiagram diagram =
PresentationViewsSubject.GetPresentation(familyTree)
.FirstOrDefault() as FamilyTreeDiagram;
図形によって表されるモデル要素を取得するには、次の値を使用します。
henryShape.ModelElement as Person
diagram.ModelElement as FamilyTreeModel
ダイアグラム内を移動する
一般に、図の図形とコネクタの間を移動することはお勧めしません。 モデル内のリレーションシップを管理するために、図形やコネクタの間を移動することを控え、図の外観を操作する必要があるときのみそれを行うことをお勧めします。 これらのメソッドは、コネクタを各端の図形にリンクします。
personShape.FromRoleLinkShapes, personShape.ToRoleLinkShapes
connector.FromShape, connector.ToShape
多くの図形は複合です。親図形と 1 つ以上の子層で構成されます。 別の図形に対して相対的に配置される図形は、その 子と言われます。 親図形が移動すると、子は一緒に移動します。
子要素 は、親図形の境界ボックスの外側に表示できます。 入れ子になった子は 、親の境界内に厳密に表示されます。
図の最上位の図形セットを取得するには、次の値を使用します。
Diagram.NestedChildShapes
図形とコネクタの先祖クラスは次のとおりです。
-- ShapeElement
----- NodeShape
------- Diagram
------- YourShape
----- LinkShape
------- BinaryLinkShape
--------- YourConnector
図形とコネクタのプロパティ
ほとんどの場合、図形を明示的に変更する必要はありません。 モデル要素を変更すると、"修正" ルールによって図形とコネクタが更新されます。 詳細については、「 変更への対応と反映」を参照してください。
ただし、モデル要素に依存しないプロパティ内の図形を明示的に変更すると便利です。 たとえば、次のプロパティを変更できます。
Size - 図形の高さと幅を決定します。
Location - 親図形または図を基準とした位置
StyleSet - 図形またはコネクタの描画に使用されるペンとブラシのセット
Hide - 図形を非表示にする
Show - 次の後に図形を表示します。
Hide()
要素とその図形の作成
要素を作成し、それを埋め込みリレーションシップのツリーにリンクすると、図形が自動的に作成され、それに関連付けられます。 これは、トランザクションの最後に実行される "修正" ルールによって行われます。 ただし、図形は自動的に割り当てられた場所に表示され、その図形、色、その他のフィーチャには既定値が設定されます。 図形の作成方法を制御するには、マージ関数を使用します。 最初に要素グループに追加する要素を追加してから、グループをダイアグラムにマージする必要があります。
このメソッドは:
要素名としてプロパティを割り当てた場合は、名前を設定します。
DSL 定義で指定した要素マージ ディレクティブを観察します。
次の使用例は、ユーザーが図をダブルクリックしたときに、マウス位置に図形を作成します。 このサンプルの DSL 定義では、FillColorの ExampleShape プロパティが公開されています。
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
partial class MyDiagram
{
public override void OnDoubleClick(DiagramPointEventArgs e)
{
base.OnDoubleClick(e);
using (Transaction t = this.Store.TransactionManager
.BeginTransaction("double click"))
{
ExampleElement element = new ExampleElement(this.Store);
ElementGroup group = new ElementGroup(element);
{ // To use a shape of a default size and color, omit this block.
ExampleShape shape = new ExampleShape(this.Partition);
shape.ModelElement = element;
shape.AbsoluteBounds = new RectangleD(0, 0, 1.5, 1.0);
shape.FillColor = System.Drawing.Color.Azure;
group.Add(shape);
}
this.ElementOperations.MergeElementGroupPrototype(
this,
group.CreatePrototype(),
PointD.ToPointF(e.MousePosition));
t.Commit();
}
}
}
複数の図形を指定する場合は、 AbsoluteBoundsを使用して相対的な位置を設定します。
このメソッドを使用して、コネクタの色やその他の公開プロパティを設定することもできます。
トランザクションの使用
図形、コネクタ、および図は、 ModelElement のサブタイプであり、ストアに存在します。 そのため、トランザクション内でのみ変更を加える必要があります。 詳細については、「 方法: トランザクションを使用してモデルを更新する」を参照してください。
ドキュメント ビューとドキュメント データ
ストア パーティション
モデルが読み込まれると、付随するダイアグラムが同時に読み込まれます。 通常、モデルは Store.DefaultPartition に読み込まれ、ダイアグラムの内容は別のパーティションに読み込まれます。 通常、各パーティションの内容は読み込まれ、別のファイルに保存されます。