共用方式為


使用 MEF 擴充您的 DSL

您可以使用 Managed Extensibility Framework (MEF) 來擴充特定領域語言 (DSL)。 您或其他開發人員將能夠在不變更 DSL 定義和程式碼的情況下,撰寫 DSL 的延伸模組。 這類延伸模組包括功能表命令、拖放功能處理常式和驗證。 使用者將能夠安裝您的 DSL,然後選擇性地為其安裝延伸模組。

此外,當您在 DSL 中啟用 MEF 時,將更容易撰寫 DSL 的某些功能,即便這些功能都是與 DSL 一起建置的。

如需 MEF 的詳細資訊,請參閱 Managed Extensibility Framework (MEF)

若要讓 MEF 擴充您的 DSL

  1. DslPackage 專案中建立名為 MefExtension 的新資料夾。 將下列檔案新增至其中:

    檔案名稱:CommandExtensionVSCT.tt

    重要

    將此檔案中的 GUID 設定為與 DslPackage\GeneratedCode\Constants.tt 中定義的 GUID CommandSetId 相同

    <#@ Dsl processor="DslDirectiveProcessor" requires="fileName='..\..\Dsl\DslDefinition.dsl'" #>
    <#
    // CmdSet Guid must be defined before master template is included
    // This Guid must be kept synchronized with the CommandSetId Guid in Constants.tt
    Guid guidCmdSet = new Guid ("00000000-0000-0000-0000-000000000000");
    string menuidCommandsExtensionBaseId="0x4000";
    #>
    <#@ include file="DslPackage\CommandExtensionVSCT.tt" #>
    

    檔案名稱:CommandExtensionRegistrar.tt

    <#@ Dsl processor="DslDirectiveProcessor" requires="fileName='..\..\Dsl\DslDefinition.dsl'" #>
    <#@ include file="DslPackage\CommandExtensionRegistrar.tt" #>
    

    檔案名稱:ValidationExtensionEnablement.tt

    <#@ Dsl processor="DslDirectiveProcessor" requires="fileName='..\..\Dsl\DslDefinition.dsl'" #>
    <#@ include file="DslPackage\ValidationExtensionEnablement.tt" #>
    

    檔案名稱:ValidationExtensionRegistrar.tt

    如果您新增此檔案,則必須使用 DSL 總管中位於 EditorValidation 的至少一個參數,在 DSL 中啟用驗證。

    <#@ Dsl processor="DslDirectiveProcessor" requires="fileName='..\..\Dsl\DslDefinition.dsl'" #>
    <#@ include file="DslPackage\ValidationExtensionRegistrar.tt" #>
    

    檔案名稱:PackageExtensionEnablement.tt

    <#@ Dsl processor="DslDirectiveProcessor" requires="fileName='..\..\Dsl\DslDefinition.dsl'" #>
    <#@ include file="DslPackage\PackageExtensionEnablement.tt" #>
    
  2. Dsl專案內,建立名為 MefExtension 的新資料夾。 將下列檔案新增至其中:

    檔案名稱:DesignerExtensionMetaDataAttribute.tt

    <#@ Dsl processor="DslDirectiveProcessor" requires="fileName='..\..\Dsl\DslDefinition.dsl'" #>
    <#@ include file="Dsl\DesignerExtensionMetadataAttribute.tt" #>
    

    檔案名稱:GestureExtensionEnablement.tt

    <#@ Dsl processor="DslDirectiveProcessor" requires="fileName='..\..\Dsl\DslDefinition.dsl'" #>
    <#@ include file="Dsl\GestureExtensionEnablement.tt" #>
    

    檔案名稱:GestureExtensionController.tt

    <#@ Dsl processor="DslDirectiveProcessor" requires="fileName='..\..\Dsl\DslDefinition.dsl'" #>
    <#@ include file="Dsl\GestureExtensionController.tt" #>
    
  3. 將以下這一行新增至名為 DslPackage\Commands.vsct 的現有檔案:

    <Include href="MefExtension\CommandExtensionVSCT.vsct"/>
    

    在現有的 <Include> 指示詞後面插入這一行。

  4. 開啟 DslDefinition.dsl

  5. 在 DSL 總管中,選取 Editor\Validation

  6. 在 [屬性] 視窗中,確定至少有一個名為 Uses 的屬性為 true

  7. 在 [方案總管] 工具列中,按一下 [轉換所有範本]

    附屬檔案會出現在您新增的每個檔案底下。

  8. 建置並執行解決方案以確認仍有作用。

您的 DSL 現已啟用 MEF。 您可以撰寫功能表命令、手勢處理常式和驗證條件約束,作為 MEF 延伸模組。 您可以將這些延伸模組與其他自訂程式碼一起撰寫在 DSL 解決方案中。 此外,您或其他開發人員可以撰寫個別的 Visual Studio 延伸模組來擴充 DSL。

為已啟用 MEF 的 DSL 建立延伸模組

如果您有權存取已啟用 MEF 的 DSL (由您自己或他人建立),則可以為其撰寫延伸模組。 延伸模組可用來新增功能表命令、手勢處理常式或驗證條件約束。 若要撰寫這些延伸模組,您可以使用 Visual Studio 延伸模組 (VSIX) 解決方案。 解決方案有兩個部分:建置程式碼組件的類別庫專案,以及封裝組件的 VSIX 專案。

建立 DSL 延伸模組 VSIX

  1. 建立新的類別庫專案。

  2. 在新專案中,新增 DSL 組件的參考。

    • 此組件的名稱通常以 ".Dsl.dll" 結尾。

    • 如果您有 DSL 專案的存取權,您可以在 Dsl\bin\* 目錄下找到組件檔

    • 如果您有 DSL VSIX 檔案的存取權,您可以將 VSIX 檔案的副檔名變更為 ".zip" 以尋找組件。 將 .zip 檔案解壓縮。

  3. 新增下列 .NET 組件的參考:

    • Microsoft.VisualStudio.Modeling.Sdk.11.0.dll

    • Microsoft.VisualStudio.Modeling.Sdk.Diagrams.11.0.dll

    • Microsoft.VisualStudio.Modeling.Sdk.Shell.11.0.dll

    • System.ComponentModel.Composition.dll

    • System.Windows.Forms.dll

  4. 建立新的 VSIX 專案

  5. 在 [方案總管] 中,以滑鼠右鍵按一下 VSIX 專案,然後選擇 [設定為啟始專案]

  6. 在新專案中,開啟 source.extension.vsixmanifest

  7. 按一下 [新增內容]。 在對話方塊中,將 [內容類型] 設定為 [MEF 元件],並將 [來源專案] 設定為您的類別庫專案。

  8. 新增對 DSL 的 VSIX 參考。

    1. source.extension.vsixmanifest 中,按一下 [新增參考]

    2. 在對話方塊中按一下 [新增承載],然後找出 DSL 的 VSIX 檔案。 VSIX 檔案建置於 DSL 解決方案中,位置為 DslPackage\bin\*

      這可讓使用者同時安裝 DSL 和您的延伸模組。 如果使用者已安裝 DSL,則只會安裝您的延伸模組。

  9. 檢閱並更新 source.extension.vsixmanifest 的其他欄位。 按一下 [選取版本],並確認已設定正確的 Visual Studio 版本。

  10. 將程式碼新增至類別庫專案。 使用下一節中的範例作為指南。

    您可以新增任意數目的命令、手勢和驗證類別。

  11. 若要測試延伸模組,請按 F5。 在 Visual Studio 的實驗執行個體中,建立或開啟 DSL 的範例檔案。

撰寫 DSL 的 MEF 延伸模組

您可以在個別 DSL 延伸模組解決方案的組件程式碼專案中撰寫延伸模組。 您也可以在 DslPackage 專案中使用 MEF,作為將命令、手勢和驗證程式碼撰寫到 DSL 中的便利途徑。

若要撰寫功能表命令,請定義實作 ICommandExtension 的類別,並在類別前面加上定義於 DSL 中的屬性 (名為 YourDslCommandExtension)。 您可以撰寫多個功能表命令類別。

每當使用者以滑鼠右鍵按一下圖表時,就會呼叫 QueryStatus()。 它應該會檢查目前的選取項目,並設定 command.Enabled 以指出命令何時適用。

using System.ComponentModel.Composition;
using System.Linq;
using Company.MyDsl; // My DSL
using Company.MyDsl.ExtensionEnablement; // My DSL
using Microsoft.VisualStudio.Modeling; // Transactions
using Microsoft.VisualStudio.Modeling.Diagrams.ExtensionEnablement; // IVsSelectionContext
using Microsoft.VisualStudio.Modeling.ExtensionEnablement; // ICommandExtension

namespace MyMefExtension
{
  // Defined in Dsl\MefExtension\DesignerExtensionMetaDataAttribute.cs:
  [MyDslCommandExtension]
  public class MyCommandClass : ICommandExtension
  {
    /// <summary>
    /// Provides access to current document and selection.
    /// </summary>
    [Import]
    IVsSelectionContext SelectionContext { get; set; }

    /// <summary>
    /// Called when the user selects this command.
    /// </summary>
    /// <param name="command"></param>
    public void Execute(IMenuCommand command)
    {
      // Transaction is required if you want to update elements.
      using (Transaction t = SelectionContext.CurrentStore
              .TransactionManager.BeginTransaction("fix names"))
      {
        foreach (ExampleShape shape in SelectionContext.CurrentSelection)
        {
          ExampleElement element = shape.ModelElement as ExampleElement;
          element.Name = element.Name + " !";
        }
        t.Commit();
      }
    }

    /// <summary>
    /// Called when the user right-clicks the diagram.
    /// Determines whether the command should appear.
    /// This method should set command.Enabled and command.Visible.
    /// </summary>
    /// <param name="command"></param>
    public void QueryStatus(IMenuCommand command)
    {
      command.Enabled =
        command.Visible = (SelectionContext.CurrentSelection.OfType<ExampleShape>().Count() > 0);
    }

    /// <summary>
    /// Called when the user right-clicks the diagram.
    /// Determines the text of the command in the menu.
    /// </summary>
    public string Text
    {
      get { return "My menu command"; }
    }
  }
}

手勢處理常式

手勢處理常式可以處理從 Visual Studio 內、外的任意處拖曳到圖表上的物件。 下列範例可讓使用者將檔案從 Windows 檔案總管拖曳到圖表上。 它會建立包含檔案名稱的元素。

您可以撰寫處理常式來處理來自其他 DSL 模型和 UML 模型的拖曳。 如需詳細資訊,請參閱操作說明:新增拖放功能處理常式

using System.ComponentModel.Composition;
using System.Linq;
using Company.MyDsl;
using Company.MyDsl.ExtensionEnablement;
using Microsoft.VisualStudio.Modeling; // Transactions
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Diagrams.ExtensionEnablement;
using Microsoft.VisualStudio.Modeling.ExtensionEnablement;

namespace MefExtension
{
  [MyDslGestureExtension]
  class MyGestureExtension : IGestureExtension
  {
    public void OnDoubleClick(ShapeElement targetElement, DiagramPointEventArgs diagramPointEventArgs)
    {
      System.Windows.Forms.MessageBox.Show("double click!");
    }

    /// <summary>
    /// Called when the user drags anything over the diagram.
    /// Return true if the dragged object can be dropped on the current target.
    /// </summary>
    /// <param name="targetMergeElement">The shape or diagram that the mouse is currently over</param>
    /// <param name="diagramDragEventArgs">Data about the dragged element.</param>
    /// <returns></returns>
    public bool CanDragDrop(ShapeElement targetMergeElement, DiagramDragEventArgs diagramDragEventArgs)
    {
      // This handler only allows items to be dropped onto the diagram:
      return targetMergeElement is MefDsl2Diagram &&
      // And only accepts files dragged from Windows Explorer:
        diagramDragEventArgs.Data.GetFormats().Contains("FileNameW");
    }

    /// <summary>
    /// Called when the user drops an item onto the diagram.
    /// </summary>
    /// <param name="targetDropElement"></param>
    /// <param name="diagramDragEventArgs"></param>
    public void OnDragDrop(ShapeElement targetDropElement, DiagramDragEventArgs diagramDragEventArgs)
    {
      MefDsl2Diagram diagram = targetDropElement as MefDsl2Diagram;
      if (diagram == null) return;

      // This handler only accepts files dragged from Windows Explorer:
      string[] draggedFileNames = diagramDragEventArgs.Data.GetData("FileNameW") as string[];
      if (draggedFileNames == null || draggedFileNames.Length == 0) return;

      using (Transaction t = diagram.Store.TransactionManager.BeginTransaction("file names"))
      {
        // Create an element to represent each file:
        foreach (string fileName in draggedFileNames)
        {
          ExampleElement element = new ExampleElement(diagram.ModelElement.Partition);
          element.Name = fileName;

          // This method of adding the new element allows the position
          // of the shape to be specified:
          ElementGroup group = new ElementGroup(element);
          diagram.ElementOperations.MergeElementGroupPrototype(
            diagram, group.CreatePrototype(), PointD.ToPointF(diagramDragEventArgs.MousePosition));
        }
        t.Commit();
      }
    }
  }
}

驗證條件約束

驗證方法會以 DSL 產生的 ValidationExtension 屬性以及 ValidationMethodAttribute 標示。 此方法可出現在任何未以屬性標示的類別中。

如需詳細資訊,請參閱特定領域語言中的驗證

using Company.MyDsl;
using Company.MyDsl.ExtensionEnablement;
using Microsoft.VisualStudio.Modeling.Validation;

namespace MefExtension
{
  class MyValidationExtension // Can be any class.
  {
    // SAMPLE VALIDATION METHOD.
    // All validation methods have the following attributes.

    // Specific to the extended DSL:
    [MyDslValidationExtension]

    // Determines when validation is applied:
    [ValidationMethod(
       ValidationCategories.Save
     | ValidationCategories.Open
     | ValidationCategories.Menu)]

    /// <summary>
    /// When validation is executed, this method is invoked
    /// for every element in the model that is an instance
    /// of the second parameter type.
    /// </summary>
    /// <param name="context">For reporting errors</param>
    /// <param name="elementToValidate"></param>
    private void ValidateClassNames
      (ValidationContext context,
       // Type determines to what elements this will be applied:
       ExampleElement elementToValidate)
    {
      // Write code here to test values and links.
      if (elementToValidate.Name.IndexOf(' ') >= 0)
      {
        // Log any unacceptable values:
        context.LogError(
          // Description:
          "Name must not contain spaces"
          // Error code unique to this type of error:
          , "MyDsl001"
          // Element to highlight when user double-clicks error:
          , elementToValidate);
} } } }