Erweitern von DSL mittels MEF

Sie können Ihre domänenspezifische Sprache (Domain-specific Language, DSL) mithilfe des Managed Extensibility Framework (MEF) erweitern. Sie oder andere Entwickler*innen haben die Möglichkeit, Erweiterungen für die DSL zu schreiben, ohne die DSL-Definition und den Programmcode zu ändern. Zu diesen Erweiterungen gehören Menübefehle, Drag-and-Drop-Handler und Validierungen. Benutzer*innen können Ihre DSL installieren und dann optional Erweiterungen dafür installieren.

Wenn Sie das MEF in Ihrer DSL aktivieren, kann es auch einfacher sein, einige der Features Ihrer DSL zu schreiben, auch wenn sie alle zusammen mit der DSL gebaut sind.

Weitere Informationen zum MEF finden Sie unter Managed Extensibility Framework (MEF).

Aktivieren der Erweiterung Ihrer DSL durch das MEF

  1. Erstellen Sie einen neuen Ordner namens MefExtension im DslPackage-Projekt. Fügen Sie dem Projekt die folgenden Dateien hinzu:

    Dateiname: CommandExtensionVSCT.tt

    Wichtig

    Legen Sie die GUID in dieser Datei so fest, dass sie mit der GUID CommandSetId übereinstimmt, die in DslPackage\GeneratedCode\Constants.tt definiert ist.

    <#@ 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" #>
    

    Dateiname: CommandExtensionRegistrar.tt

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

    Dateiname: ValidationExtensionEnablement.tt

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

    Dateiname: ValidationExtensionRegistrar.tt

    Wenn Sie diese Datei hinzufügen, müssen Sie die Validierung in Ihrer DSL aktivieren, indem Sie mindestens einen der Parameter in EditorValidation im DSL-Explorer verwenden.

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

    Dateiname: PackageExtensionEnablement.tt

    <#@ Dsl processor="DslDirectiveProcessor" requires="fileName='..\..\Dsl\DslDefinition.dsl'" #>
    <#@ include file="DslPackage\PackageExtensionEnablement.tt" #>
    
  2. Erstellen Sie einen neuen Ordner namens MefExtension innerhalb des Dsl-Projekts. Fügen Sie dem Projekt die folgenden Dateien hinzu:

    Dateiname: DesignerExtensionMetaDataAttribute.tt

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

    Dateiname: GestureExtensionEnablement.tt

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

    Dateiname: GestureExtensionController.tt

    <#@ Dsl processor="DslDirectiveProcessor" requires="fileName='..\..\Dsl\DslDefinition.dsl'" #>
    <#@ include file="Dsl\GestureExtensionController.tt" #>
    
  3. Fügen Sie der vorhandenen Datei namens DslPackage\Commands.vsct die folgende Zeile hinzu:

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

    Fügen Sie die Zeile nach der vorhandenen <Include>-Anweisung ein.

  4. Öffnen Sie DslDefinition.dsl.

  5. Wählen Sie im DSL-Explorer Editor\Validation aus.

  6. Stellen Sie im Eigenschaftenfenster sicher, dass mindestens eine der Eigenschaften namens Usestrue lautet.

  7. Klicken Sie auf der Symbolleiste Projektmappen-Explorer auf Alle Vorlagen transformieren.

    Untergeordnete Dateien werden unter den Dateien angezeigt, die Sie hinzugefügt haben.

  8. Erstellen Sie die Projektmappe, und führen Sie sie aus, um zu überprüfen, ob sie noch funktioniert.

Ihre DSL ist jetzt MEF-fähig. Sie können Menübefehle, Gestenhandler und Gültigkeitseinschränkungen als MEF-Erweiterungen schreiben. Sie können diese Erweiterungen zusammen mit anderem benutzerdefinierten Code in Ihre DSL-Projektmappe schreiben. Darüber hinaus können Sie oder andere Entwickler*innen separate Visual Studio-Erweiterungen schreiben, die Ihre DSL erweitern.

Erstellen einer Erweiterung für eine MEF-fähige DSL

Wenn Sie Zugriff auf eine MEF-fähige DSL haben, die Sie selbst oder eine andere Person erstellt hat, können Sie Erweiterungen dafür schreiben. Die Erweiterungen können verwendet werden, um Menübefehle, Gestenhandler oder Validierungseinschränkungen hinzuzufügen. Zum Erstellen dieser Erweiterungen verwenden Sie eine Projektmappe für eine Visual Studio-Erweiterung (VSIX). Die Projektmappe umfasst zwei Teile: ein Klassenbibliotheksprojekt, das die Codeassembly erstellt, und ein VSIX-Projekt, das die Assembly packt.

Erstellen einer VSIX-Datei für eine DSL-Erweiterung

  1. Erstellen Sie ein neues Klassenbibliotheksprojekt.

  2. Fügen Sie im neuen Projekt einen Verweis auf die Assembly der DSL hinzu.

    • Diese Assembly hat in der Regel einen Namen, der mit „.Dsl.dll“ endet.

    • Wenn Sie Zugriff auf das DSL-Projekt haben, finden Sie die Assemblydatei unter dem Verzeichnis Dsl\bin\*.

    • Wenn Sie Zugriff auf die DSL-VSIX-Datei haben, können Sie nach der Assembly suchen, indem Sie die Erweiterung der VSIX-Datei in „.zip“ ändern. Dekomprimieren Sie die ZIP-Datei.

  3. Fügen Sie Verweise auf die folgenden .NET-Assemblys hinzu:

    • 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. Erstellen Sie ein neues VSIX-Projekt.

  5. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das VSIX-Projekt, und wählen Sie anschließend Als Startprojekt festlegen aus.

  6. Öffnen Sie im neuen Projekt source.extension.vsixmanifest.

  7. Klicken Sie auf Inhalt hinzufügen. Legen Sie im Dialogfeld Inhaltstyp auf MEF-Komponente und Quellprojekt auf Ihr Klassenbibliotheksprojekt fest.

  8. Fügen Sie einen VSIX-Verweis auf die DSL hinzu.

    1. Klicken Sie in source.extension.vsixmanifest auf Verweis hinzufügen.

    2. Klicken Sie im Dialogfeld auf Nutzdaten hinzufügen, und suchen Sie dann die VSIX-Datei der DSL. Die VSIX-Datei ist in der DSL-Lösung in DslPackage\bin\* integriert.

      Auf diese Weise können Benutzer*innen die DSL und Ihre Erweiterung gleichzeitig installieren. Wenn Benutzer*innen die DSL bereits installiert haben, wird nur Ihre Erweiterung installiert.

  9. Überprüfen und aktualisieren Sie die anderen Felder von source.extension.vsixmanifest. Klicken Sie auf Editionen auswählen, und überprüfen Sie, ob die richtigen Visual Studio-Editionen festgelegt sind.

  10. Fügen Sie dem Klassenbibliotheksprojekt Code hinzu. Verwenden Sie die Beispiele im nächsten Abschnitt als Referenz.

    Sie können eine beliebige Anzahl von Befehls-, Gesten- und Validierungsklassen hinzufügen.

  11. Drücken Sie F5, um die Erweiterung zu testen. Erstellen oder öffnen Sie in der experimentellen Instanz von Visual Studio eine Beispieldatei der DSL.

Schreiben von MEF-Erweiterungen für DSLs

Sie können Erweiterungen im Assemblycodeprojekt einer separaten DSL-Erweiterungsprojektmappe schreiben. Sie können das MEF auch in Ihrem DslPackage-Projekt verwenden, um Befehle, Gesten und Validierungscode als Teil der DSL zu schreiben.

Definieren Sie zum Schreiben eines Menübefehls eine Klasse, die ICommandExtension implementiert, und stellen Sie der Klasse das Attribut voran, das in Ihrer DSL (namens YourDslCommandExtension) definiert ist. Sie können mehrere Menübefehlsklassen schreiben.

QueryStatus() wird aufgerufen, wenn Benutzer*innen mit der rechten Maustaste auf das Diagramm klicken. Das Element sollte die aktuelle Auswahl prüfen und command.Enabled so festlegen, dass angezeigt wird, wann der Befehl anwendbar ist.

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"; }
    }
  }
}

Gestenhandler

Ein Gestenhandler kann Objekte verarbeiten, die von einem beliebigen Punkt innerhalb oder außerhalb von Visual Studio auf das Diagramm gezogen werden. Im folgenden Beispiel können Benutzer*innen Dateien aus dem Windows-Explorer auf das Diagramm ziehen. Daraufhin werden Elemente erstellt, die die Dateinamen enthalten.

Sie können Handler schreiben, um Ziehvorgänge über andere DSL-Modelle und UML-Modelle zu verarbeiten. Weitere Informationen finden Sie unter Hinzufügen eines Drag-and-Drop-Handlers.

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();
      }
    }
  }
}

Validierungseinschränkungen

Validierungsmethoden werden durch das ValidationExtension-Attribut gekennzeichnet, das von der DSL und ValidationMethodAttribute generiert wird. Die Methode kann in jeder Klasse angezeigt werden, die nicht durch ein Attribut gekennzeichnet ist.

Weitere Informationen finden Sie unter Validierung in einer domänenspezifischen Sprache.

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);
} } } }