Расширение доменного языка с помощью MEF

Вы можете расширить язык для конкретного домена (DSL) с помощью управляемой платформы расширяемости (MEF). Вы или другие разработчики смогут создавать расширения для DSL, не изменяя определение DSL и код программы. Такие расширения включают команды меню, обработчики перетаскивания и проверку. Пользователи смогут установить dsL, а затем при необходимости установить для него расширения.

Кроме того, при включении MEF в DSL можно упростить написание некоторых функций DSL, даже если все они созданы вместе с DSL.

Дополнительные сведения о MEF см. в статье Об управляемой платформе расширяемости (MEF).

Включение расширения DSL с помощью MEF

  1. Создайте новую папку с именем MefExtension в проекте DslPackage . Добавьте в него следующие файлы:

    Имя файла: CommandExtensionVSCT.tt

    Важно!

    Задайте guid в этом файле так же, как идентификатор GUID CommandSetId, определенный в 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" #>
    

    Имя файла: 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. Создайте новую папку с именем MefExtension в проекте Dsl . Добавьте в него следующие файлы:

    Имя файла: 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 выберите "Редактор\Проверка".

  6. В окно свойств убедитесь, что не менее одного из свойств с именем "Использованиеtrue".

  7. На панели инструментов Обозреватель решений нажмите кнопку "Преобразовать все шаблоны".

    Дочерние файлы отображаются под каждым из добавленных файлов.

  8. Создайте и запустите решение, чтобы убедиться, что он по-прежнему работает.

Теперь DSL включена в MEF. Вы можете создавать команды меню, обработчики жестов и ограничения проверки в виде расширений MEF. Эти расширения можно написать в решении DSL вместе с другим пользовательским кодом. Кроме того, вы или другие разработчики могут создавать отдельные расширения Visual Studio, расширяющие DSL.

Создание расширения для DSL с поддержкой MEF

Если у вас есть доступ к DSL с поддержкой MEF, созданному вами или другим пользователем, вы можете написать для него расширения. Расширения можно использовать для добавления команд меню, обработчиков жестов или ограничений проверки. Чтобы создать эти расширения, используйте решение расширения 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. В новом проекте open source.extension.vsixmanifest.

  7. Нажмите кнопку "Добавить содержимое". В диалоговом окне задайте типконтента для компонента MEF и исходного проекта библиотеки классов.

  8. Добавьте ссылку VSIX на DSL.

    1. В source.extension.vsixmanifest нажмите кнопку "Добавить ссылку "

    2. В диалоговом окне нажмите кнопку "Добавить полезные данные ", а затем найдите VSIX-файл DSL. VSIX-файл построен в решении DSL в DslPackage\bin\*.

      Это позволяет пользователям устанавливать DSL и расширение одновременно. Если пользователь уже установил DSL, будет установлен только расширение.

  9. Просмотрите и обновите другие поля source.extension.vsixmanifest. Щелкните "Выбрать выпуски" и убедитесь, что установлены правильные выпуски Visual Studio.

  10. Добавьте код в проект библиотеки классов. Используйте примеры в следующем разделе в качестве руководства.

    Можно добавить любое количество команд, жестов и классов проверки.

  11. Чтобы проверить расширение, нажмите клавишу F5. В экспериментальном экземпляре Visual Studio создайте или откройте пример файла DSL.

Написание расширений MEF для DSLs

Расширения можно написать в проекте кода сборки отдельного решения расширения 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();
      }
    }
  }
}

Ограничения проверки

Методы проверки помечаются ValidationExtension атрибутом, созданным DSL, а также 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);
} } } }