巡覽及更新程式碼中的模型
您可以撰寫程式碼以建立和刪除模型元素、設定其屬性,以及建立和刪除元素之間的連結。 所有變更都必須在交易內進行。 如果在圖表上檢視元素,圖表將在交易結束時自動「修正」。
範例 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 定義中,如果屬性的 [類型] 為 [計算],您就無法加以設定。 如需詳細資訊,請參閱計算和自訂儲存體屬性。
關聯性
您在 DSL 定義中定義的領域關聯會成為屬性配對,關聯兩端的類別各有一個。 屬性的名稱會在 DslDefinition 圖表中顯示為關聯兩端角色的標籤。 根據角色的多重性,屬性的類型會是關聯另一端的類別,或是該類別的集合。
foreach (Person child in henry.Children) { ... }
FamilyTreeModel ftree = henry.FamilyTreeModel;
關聯另一端的屬性一律是相互的。 在連結建立或刪除時,兩個元素的角色屬性都會更新。 對範例中的 ParentsHaveChildren 關聯而言,下列運算式 (使用 System.Linq
的延伸模組) 始終成立:
(Person p) => p.Children.All(child => child.Parents.Contains(p))
&& p.Parents.All(parent => parent.Children.Contains(p));
ElementLinks。 關聯也以名為連結的模型元素表示,這是領域關聯類型的執行個體。 連結一律有一個來源元素和一個目標元素。 來源元素與目標元素可以相同。
您可以存取連結及其屬性:
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 - 所有元素和關聯都是 ModelElement
ElementLink - 所有關聯都是 ElementLink
在交易內執行變更
每當程式碼變更存放區中的所有項目時,都必須在交易內執行此動作。 這適用於所有模型元素、關聯、圖形、圖表及其屬性。 如需詳細資訊,請參閱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.
}
您可以在一個交易內進行任何數量的變更。 您可以在作用中交易內開啟新交易。
若要進行永久變更,您應在交易受到處置之前加以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 角色屬性,或將 Person 新增至 FamilyTreeModel 物件的 People 角色屬性。
設定新元素的屬性,特別是 DslDefinition 中將
IsName
設為 true 的屬性。 此旗標會標示屬性,用來唯一識別其擁有者內的元素。 在此案例中,Name 屬性具有該旗標。此 DSL 的 DSL 定義必須已載入至存放區中。 如果您正在撰寫延伸模組 (例如功能表命令),則通常已符合此條件。 在其他情況下,您可以明確地將模型載入存放區中,或使用 ModelBus 加以載入。 如需詳細資訊,請參閱操作說明:在程式碼中開啟檔案的模型。
以此方式建立元素時,會自動建立圖形 (如果 DSL 有圖表)。 它會出現在自動指派的位置,具有預設的圖形、色彩和其他特徵。 如果您想要控制相關聯的圖形顯示的位置與方式,請參閱建立元素及其圖形。
建立關聯連結
範例 DSL 定義中定義了兩個關聯。 每個關聯都會在關聯兩端的類別上定義角色屬性。
有三種方式可讓您建立關聯的執行個體。 這三種方法具有相同效果:
設定來源角色扮演者的屬性。 例如:
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 之角色的元素。 例如,將會刪除顯示元素的圖形。
根據預設,每個內嵌關聯在目標角色上都會有值為 true 的 PropagatesDelete
。 刪除 henry
並不會刪除 familyTree
,但 familyTree.Delete()
會刪除所有的 Persons
。
參考關聯的角色預設的 PropagatesDelete
不是 true。
在刪除物件時,您可能會使刪除規則省略特定傳播。 如果要替換為另一個元素,這會很有用。 請提供一或多個不應傳播刪除之角色的 GUID。 GUID 可從關聯類別取得:
henry.Delete(ParentsHaveChildren.SourceDomainRoleId);
(此範例不會有任何作用,因為就 ParentsHaveChildren
關聯的角色而言,PropagatesDelete
為 false
。)
在某些情況下,元素上或將被傳播刪除的元素上若有鎖定存在,則可防止刪除。 您可以使用 element.CanDelete()
來檢查元素是否可刪除。
刪除關聯連結
您可以從角色屬性移除元素,以刪除關聯連結:
henry.Children.Remove(edward); // or:
edward.Parents.Remove(henry); // or:
您也可以明確地刪除連結:
edwardHenryLink.Delete();
這三種方法的效果全都相同。 您只需要使用其中之一。
如果角色具有 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);
鎖定
鎖定可能會禁止變更。 鎖定可設定於個別元素、分割區和存放區上。 如果其中任一層級有鎖定禁止您要進行的變更類型,當您嘗試變更時,可能會擲回例外狀況。 您可以使用 element.GetLocks() 來探索是否設定了鎖定,這是命名空間 Microsoft.VisualStudio.Modeling.Immutability 中定義的擴充方法。
如需詳細資訊,請參閱定義鎖定原則以建立唯讀區段。
複製與貼上
您可以將元素或元素群組複製到 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
,則也可以在目標圖表上指定一個位置作為第三個參數。
瀏覽和更新圖表
在 DSL 中,代表 Person 或 Song 等概念的領域模型元素與圖形元素不同,後者代表您在圖表上看到的內容。 領域模型元素會儲存概念的重要屬性和關聯。 圖形元素會儲存圖表上物件檢視的大小、位置和色彩,及其元件組件的配置。
簡報元素
在您的 DSL 定義中,您指定的每個元素都會建立衍生自下列其中一個標準類別的類別。
元素種類 | 基底類別 |
---|---|
領域類別 | ModelElement |
領域關聯 | ElementLink |
形狀 | NodeShape |
Connector | BinaryLinkShape |
圖表 | Diagram |
圖表上的元素通常代表模型元素。 NodeShape 通常 (但不一定) 代表領域類別執行個體,而 BinaryLinkShape 則代表領域關聯執行個體。 PresentationViewsSubject 關聯會將節點或連結圖形連結至其代表的模型元素。
每個節點或連結圖形都屬於一個圖表。 二元連結圖形會連接兩個節點圖形。
圖形可以有兩個集合的子圖形。 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
許多圖形都是複合圖形;由父圖形和一或多個子系的圖層組成。 相對於另一個圖形放置的圖形,稱為子系。 父圖形移動時,子系會隨之移動。
相對子系可出現在父圖形的週框方塊外。 巢狀子系僅可出現在父代的界限內。
若要取得圖表中最上層的圖形集,請使用:
Diagram.NestedChildShapes
圖形和連接線的上階類別為:
-- ShapeElement
----- NodeShape
------- Diagram
------- YourShape
----- LinkShape
------- BinaryLinkShape
--------- YourConnector
圖形和連接線的屬性
多數情況下,無需對圖形進行明確的變更。 當您變更模型元素時,「修正」規則會更新圖形和連接線。 如需詳細資訊,請參閱回應及傳播變更。
不過,在與模型元素無關的屬性中對圖形進行一些明確的變更,將有其效用。 例如,您可以變更下列屬性:
Size - 決定圖形的高度和寬度。
Location - 與父圖形或圖表的相對位置
StyleSet - 一組用來繪製圖形或連接線的畫筆和筆刷
Hide - 隱藏圖形
Show - 使圖形在
Hide()
之後顯示
建立元素及其圖形
當您建立元素並將其連結至內嵌關聯的樹狀結構時,將會自動建立圖形並與其產生關聯。 這是由交易結束時執行的「修正」規則所完成的。 不過,圖形會出現在自動指派的位置,且其圖形、色彩和其他特徵會具有預設值。 若要控制圖形的建立方式,您可以使用合併函式。 您必須先新增要新增至 ElementGroup 的元素,再將該群組合併到圖表中。
此方法會:
在您已將屬性指派為元素名稱時設定名稱。
觀察您在 DSL 定義中指定的任何元素合併指示詞。
此範例會在使用者按兩下圖表時,於滑鼠位置建立圖形。 在此範例的 DSL 定義中,已公開 ExampleShape
的 FillColor
屬性。
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 中,而圖表內容會載入另一個分割區中。 一般情況下,會載入每個分割區的內容,並儲存至個別檔案。