Partager via


Extension de votre DSL à l'aide de MEF

Vous pouvez étendre votre langage dédié (DSL) avec Managed Extensibility Framework (MEF). Vous ou d’autres développeurs pouvez écrire des extensions pour le DSL sans changer la définition DSL et le code du programme. Ces extensions comprennent des commandes de menu, des gestionnaires de glisser-déposer et la validation. Les utilisateurs peuvent installer votre DSL, puis éventuellement installer des extensions pour celui-ci.

Par ailleurs, l’activation de MEF dans votre DSL peut vous permettre d’écrire plus facilement certaines fonctionnalités de votre DSL, même si elles sont générées en même temps que le DSL.

Pour plus d’informations sur MEF, consultez Managed Extensibility Framework (MEF).

Pour permettre l’extension de votre DSL par MEF

  1. Créez un dossier nommé MefExtension dans le projet DslPackage. Ajoutez-y les fichiers suivants :

    Nom de fichier : CommandExtensionVSCT.tt

    Important

    Définissez le GUID de ce fichier sur le GUID CommandSetId défini dans DslPackage\GeneratedCode\Constants.tt

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

    Nom de fichier : CommandExtensionRegistrar.tt

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

    Nom de fichier : ValidationExtensionEnablement.tt

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

    Nom de fichier : ValidationExtensionRegistrar.tt

    Si vous ajoutez ce fichier, vous devez activer la validation dans votre DSL en utilisant au moins un commutateur dans EditorValidation dans l’Explorateur DSL.

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

    Nom de fichier : PackageExtensionEnablement.tt

    <#@ Dsl processor="DslDirectiveProcessor" requires="fileName='..\..\Dsl\DslDefinition.dsl'" #>
    <#@ include file="DslPackage\PackageExtensionEnablement.tt" #>
    
  2. Créez un dossier nommé MefExtension dans le projet Dsl. Ajoutez-y les fichiers suivants :

    Nom de fichier : DesignerExtensionMetaDataAttribute.tt

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

    Nom de fichier : GestureExtensionEnablement.tt

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

    Nom de fichier : GestureExtensionController.tt

    <#@ Dsl processor="DslDirectiveProcessor" requires="fileName='..\..\Dsl\DslDefinition.dsl'" #>
    <#@ include file="Dsl\GestureExtensionController.tt" #>
    
  3. Ajoutez la ligne suivante au fichier existant nommé DslPackage\Commands.vsct :

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

    Insérez la ligne après la directive <Include> existante.

  4. Ouvrez DslDefinition.dsl.

  5. Dans l’Explorateur DSL, sélectionnez Éditeur\Validation.

  6. Dans la fenêtre Propriétés, vérifiez qu’au moins une des propriétés nommées Uses est true.

  7. Dans la barre d’outils de l’Explorateur de solutions, cliquez sur Transformer tous les modèles.

    Des fichiers subsidiaires s’affichent sous chacun des fichiers que vous avez ajoutés.

  8. Générez et exécutez la solution pour vérifier qu’elle fonctionne toujours.

Votre DSL est désormais activé pour MEF. Vous pouvez écrire des commandes de menu, des gestionnaires de mouvements et des contraintes de validation sous forme d’extensions MEF. Vous pouvez écrire ces extensions dans votre solution DSL avec un autre code personnalisé. Par ailleurs, vous ou d’autres développeurs pouvez écrire des extensions Visual Studio distinctes qui étendent votre DSL.

Créer une extension pour un DSL compatible MEF

Si vous avez accès à un DSL compatible MEF que vous ou quelqu’un d’autre avez créé, vous pouvez écrire des extensions pour celui-ci. Les extensions peuvent être utilisées pour ajouter des commandes de menu, des gestionnaires de mouvements ou des contraintes de validation. Pour créer ces extensions, vous utilisez une solution d’extension Visual Studio (VSIX). La solution comprend deux parties : un projet de bibliothèque de classes qui génère l’assembly de code et un projet VSIX qui package l’assembly.

Pour créer une extension DSL VSIX

  1. Créez un projet de Bibliothèque de classes.

  2. Dans le nouveau projet, ajoutez une référence à l’assembly du DSL.

    • Cet assembly a généralement un nom qui se termine par « .Dsl.dll ».

    • Si vous avez accès au projet DSL, vous trouvez le fichier d’assembly sous le répertoire Dsl\bin\*

    • Si vous avez accès au fichier VSIX DSL, vous pouvez trouver l’assembly en remplaçant l’extension de nom de fichier du fichier VSIX par « .zip ». Décompressez le fichier .zip.

  3. Ajoutez des références aux assemblys .NET suivants :

    • 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. Créez un projet VSIX.

  5. Dans l’Explorateur de solutions, cliquez avec le bouton droit sur le projet VSIX, puis choisissez Définir comme projet de démarrage.

  6. Dans le nouveau projet, ouvrez source.extension.vsixmanifest.

  7. Cliquez sur Ajouter du contenu. Dans la boîte de dialogue, définissez Type de contenu sur Composant MEF et Projet source sur votre projet de bibliothèque de classes.

  8. Ajoutez une référence VSIX au DSL.

    1. Dans source.extension.vsixmanifest, cliquez sur Ajouter une référence

    2. Dans la boîte de dialogue, cliquez sur Ajouter une charge utile, puis recherchez le fichier VSIX du DSL. Le fichier VSIX est généré dans la solution DSL, dans DslPackage\bin\*.

      Cela permet aux utilisateurs d’installer le DSL et votre extension en même temps. Si l’utilisateur a déjà installé le DSL, seule votre extension est installée.

  9. Passez en revue et mettez à jour les autres champs de source.extension.vsixmanifest. Cliquez sur Sélectionner les éditions et vérifiez que les éditions Visual Studio appropriées sont définies.

  10. Ajoutez du code au projet de bibliothèque de classes. Utilisez les exemples de la section suivante comme guide.

    Vous pouvez ajouter n’importe quel nombre de classes de commande, de mouvement et de validation.

  11. Pour tester l’extension, appuyez sur F5. Dans l’instance expérimentale de Visual Studio, créez ou ouvrez un exemple de fichier du DSL.

Écriture d’extensions MEF pour les DSL

Vous pouvez écrire des extensions dans le projet de code d’assembly d’une solution d’extension DSL distincte. Vous pouvez également utiliser MEF dans votre projet DslPackage, comme un moyen pratique d’écrire des commandes, des mouvements et du code de validation dans le cadre du DSL.

Pour écrire une commande de menu, définissez une classe qui implémente ICommandExtension et préfixez la classe avec l’attribut défini dans votre DSL, nommé YourDslCommandExtension. Vous pouvez écrire plusieurs classes de commandes de menu.

QueryStatus() est appelé chaque fois que l’utilisateur clique avec le bouton droit sur le diagramme. Il doit inspecter la sélection actuelle et définir command.Enabled pour indiquer quand la commande est applicable.

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

Gestionnaires de mouvements

Un gestionnaire de mouvements peut traiter des objets glissés sur le diagramme indépendamment de leur emplacement d’origine, à l’intérieur ou à l’extérieur de Visual Studio. L’exemple suivant permet à l’utilisateur de faire glisser des fichiers de l’Explorateur Windows sur le diagramme. Il crée des éléments qui contiennent les noms de fichiers.

Vous pouvez écrire des gestionnaires pour traiter les glissements à partir d’autres modèles DSL et modèles UML. Pour plus d’informations, consultez Guide pratique pour ajouter un gestionnaire de glisser-déposer.

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

Contraintes de validation

Les méthodes de validation sont marquées par l’attribut ValidationExtension généré par le DSL, ainsi que par ValidationMethodAttribute. La méthode peut apparaître dans n’importe quelle classe qui n’est pas marquée par un attribut.

Pour plus d’informations, consultez Validation dans un langage dédié.

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