次の方法で共有


方法: ドメイン固有言語デザイナーを拡張する

DSL 定義の編集に使用するデザイナーを拡張できます。 作成できる拡張機能の種類として、メニュー コマンドの追加、ドラッグ操作とダブルクリック操作用のハンドラーの追加、および特定の型の値またはリレーションシップが変更されたときにトリガーされる規則があります。 式を Visual Studio Integration Extension (VSIX) のパッケージに格納し、他のユーザーに配布できます。

この機能のサンプル コードと詳細については、Visual Studio Visualization and Modeling SDK (VMSDK) の Web サイトを参照してください。

ソリューションの設定

拡張機能のコードを含むプロジェクトと、プロジェクトをエクスポートする VSIX プロジェクトを設定します。 ソリューションには、同じ VSIX に組み込まれた他のプロジェクトを含めることができます。

DSL デザイナー拡張ソリューションを作成するには

  1. [クラス ライブラリ] プロジェクト テンプレートを使用して、新しいプロジェクトを作成します。 [新しいプロジェクト] ダイアログ ボックスで、[Visual C#] をクリックし、中央のウィンドウで [クラス ライブラリ] をクリックします。

    このプロジェクトは、独自の拡張機能のコードを含みます。

  2. VSIX プロジェクト テンプレートを使用し、新しいプロジェクトを作成します。 [新しいプロジェクト] ダイアログ ボックスで、[Visual C#] を展開し、[機能拡張] をクリックします。次に、中央のウィンドウで [VSIX プロジェクト] をクリックします。

    [ソリューションに追加] を選択します。

    Source.extension.vsixmanifest は VSIX マニフェスト エディターで開きます。

  3. [コンテンツ] フィールドの上の [コンテンツの追加] をクリックします。

  4. [コンテンツの追加] ダイアログ ボックスで、[コンテンツ タイプの選択][MEF コンポーネント] に設定し、[プロジェクト] をクラス ライブラリ プロジェクトに設定します。

  5. [Select Editions] (エディションの選択) をクリックし、[Visual Studio Ultimate] がオンになっていることを確認します。

  6. VSIX プロジェクトがソリューションのスタートアップ プロジェクトであることを確認します。

  7. クラス ライブラリ プロジェクトで、次のアセンブリへの参照を追加します。

    Microsoft.VisualStudio.CoreUtility

    Microsoft.VisualStudio.Modeling.Sdk.11.0

    Microsoft.VisualStudio.Modeling.Sdk.Diagrams.11.0

    Microsoft.VisualStudio.Modeling.Sdk.DslDefinition.11.0

    Microsoft.VisualStudio.Modeling.Sdk.Integration.11.0

    System.ComponentModel.Composition

    System.Drawing

    System.Drawing.Design

    System.Windows.Forms

テストと配置

このトピックの拡張機能をテストするには、ソリューションをビルドして実行します。 Visual Studio の実験用のインスタンスが開きます。 この場合、DSL ソリューションを開きます。 DslDefinition ダイアグラムを編集します。 拡張機能の動作を表示できます。

メインの Visual Studio、および他のコンピューターに拡張機能を配置するには、次の手順に従います:

  1. VSIX プロジェクトの bin\*\*.vsix で VSIX インストール ファイルを見つけます。

  2. 次に、ターゲット コンピューターに、エクスプローラー (または) ファイル エクスプローラーでこのファイルをダブルクリックし、コピーします。

    拡張機能がインストールされていることを確認するために、Visual Studio 拡張機能マネージャーが開きます。

拡張機能をアンインストールするには、次の手順に従います。

  1. Visual Studio で、[ツール] メニューの [拡張機能マネージャー] をクリックします。

  2. 拡張機能を選択して削除します。

ショートカット メニュー コマンドの追加

DSL デザイナー画面または DSL のエクスプローラー ウィンドウにショートカット メニュー コマンドを表示するには、次のようなクラスを記述します。

クラスは ICommandExtension を実装し、DslDefinitionModelCommandExtension 属性を持つ必要があります。

using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Diagrams.ExtensionEnablement;
using Microsoft.VisualStudio.Modeling.DslDefinition;
using Microsoft.VisualStudio.Modeling.DslDefinition.ExtensionEnablement;
using Microsoft.VisualStudio.Modeling.DslDesigner;
using Microsoft.VisualStudio.Modeling.ExtensionEnablement;

namespace Fabrikam.SimpleDslDesignerExtension
{
  /// <summary>
  /// Command extending the DslDesigner.
  /// </summary>
  [DslDefinitionModelCommandExtension] 
  public class MyDslDesignerCommand : ICommandExtension
  {
    /// <summary>
    /// Selection Context for this command
    /// </summary>
    [Import]
    IVsSelectionContext SelectionContext { get; set; }

    /// <summary>
    /// Is the command visible and active?
    /// This is called when the user right-clicks.
    /// </summary>
    public void QueryStatus(IMenuCommand command)
    {
      command.Visible = true;
      // Is there any selected DomainClasses in the Dsl explorer?
      command.Enabled = 
        SelectionContext.AtLeastOneSelected<DomainClass>();

      // Is there any selected ClassShape on the design surface?
      command.Enabled |= 
        (SelectionContext.GetCurrentSelection<ClassShape>()
                .Count() > 0);
    }
    /// <summary>
    /// Executes the command 
    /// </summary>
    /// <param name="command">Command initiating this action</param>
    public void Execute(IMenuCommand command)
    {
      ...
    }
    /// <summary>
    /// Label for the command
    /// </summary>
    public string Text
    {
      get { return "My Command"; }
    }
  }
}

マウス ジェスチャの処理

コードは、メニュー コマンドのコードに似ています。

[DslDefinitionModelGestureExtension]
 class MouseGesturesExtensions : IGestureExtension
 {
  /// <summary>
  /// Double-clicking on a shape representing a Domain model element displays this model element in a dialog box
  /// </summary>
  /// <param name="targetElement">Shape element on which the user has clicked</param>
  /// <param name="diagramPointEventArgs">event args for this double-click</param>
  public void OnDoubleClick(ShapeElement targetElement,
       DiagramPointEventArgs diagramPointEventArgs)
  {
   ModelElement modelElement = PresentationElementHelper.
        GetDslDefinitionModelElement(targetElement);
   if (modelElement != null)
   {
    MessageBox.Show(string.Format(
      "Double clicked on {0}", modelElement.ToString()), 
      "Model element double-clicked");
   }
  }


  /// <summary>
  /// Tells if the DslDesigner can consume the to-be-dropped information
  /// </summary>
  /// <param name="targetMergeElement">Shape on which we try to drop</param>
  /// <param name="diagramDragEventArgs">Drop event</param>
  /// <returns><c>true</c> if we can consume the to be dropped data, and <c>false</c> otherwise</returns>
  public bool CanDragDrop(ShapeElement targetMergeElement,
        DiagramDragEventArgs diagramDragEventArgs)
  {
   if (diagramDragEventArgs.Data.GetDataPresent(DataFormats.FileDrop))
   {
    diagramDragEventArgs.Effect = DragDropEffects.Copy;
    return true;
   }
   return false;
  }


  /// <summary>
  /// Processes the drop by displaying the dropped text
  /// </summary>
  /// <param name="targetMergeElement">Shape on which we dropped</param>
  /// <param name="diagramDragEventArgs">Drop event</param>
  public void OnDragDrop(ShapeElement targetDropElement, DiagramDragEventArgs diagramDragEventArgs)
  {
   if (diagramDragEventArgs.Data.GetDataPresent(DataFormats.FileDrop))
   {
    string[] droppedFiles = 
      diagramDragEventArgs.Data.
               GetData(DataFormats.FileDrop) as string[];
    MessageBox.Show(string.Format("Dropped text {0}",
        string.Join("\r\n", droppedFiles)), "Dropped Text");
   }
  }
 }

値変更への対応

このハンドラーは、ドメイン モデルが正常に動作することを必要とします。 簡単なドメイン モデルを提供します。

using System.Diagnostics;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.DslDefinition;
namespace Fabrikam.SimpleDslDesignerExtension
{
  /// <summary>
  /// Rule firing when the type of a domain model property is changed. The change is displayed
  /// in the debugger (Output window of the Visual Studio instance debugging this extension)
  /// </summary>
  [RuleOn(typeof(PropertyHasType))]
  public class DomainPropertyTypeChangedRule : RolePlayerChangeRule
  {
    /// <summary>
    /// Method called when the Type of a Domain Property 
    /// is changed by the user in a DslDefinition
    /// </summary>
    /// <param name="e"></param>
    public override void RolePlayerChanged(RolePlayerChangedEventArgs e)
    {
      // We are only interested in the type
      if (e.DomainRole.Id == PropertyHasType.TypeDomainRoleId)
      {
        PropertyHasType relationship = 
           e.ElementLink as PropertyHasType;
        DomainType newType = e.NewRolePlayer as DomainType;
        DomainType oldType = e.OldRolePlayer as DomainType;
        DomainProperty property = relationship.Property;

         // We write about the Type change in the debugguer
        Debug.WriteLine(string.Format("The type of the Domain property '{0}' of domain class '{1}' changed from '{2}' to '{3}'",
          property.Name,
          property.Class.Name,
          oldType.GetFullName(false),
          newType.GetFullName(false))
} }  }  );

次のコードは、簡単なモデルを実装します。 新しい GUID を作成し、プレースホルダーを置き換えます。

using System;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.DslDefinition;
namespace Fabrikam.SimpleDslDesignerExtension
{
    /// <summary>
    /// Simplest possible domain model 
    /// needed only for extension rules. 
    /// </summary>
    [DomainObjectId(SimpleDomainModelExtension.DomainModelId)]
    public class SimpleDomainModelExtension : DomainModel
    {
        // Id of this domain model extension 
        // Please replace this with a new GUID:
        public const string DomainModelId = 
                  "00000000-0000-0000-0000-000000000000";

        /// <summary>
        /// Constructor for the domain model extension
        /// </summary>
        /// <param name="store">Store in which the domain model will be loaded</param>
        public SimpleDomainModelExtension(Store store)
            : base(store, new Guid(SimpleDomainModelExtension.DomainModelId))
        {

        }

        /// <summary>
        /// Rules brought by this domain model extension
        /// </summary>
        /// <returns></returns>
        protected override System.Type[] GetCustomDomainModelTypes()
        {
            return new Type[] {
                     typeof(DomainPropertyTypeChangedRule)
                              };
        }
    }


    /// <summary>
    /// Provider for the DomainModelExtension
    /// </summary>
    [Export(typeof(DomainModelExtensionProvider))]    [ProvidesExtensionToDomainModel(typeof(DslDefinitionModelDomainModel))]
    public class SimpleDomainModelExtensionProvider 
          : DomainModelExtensionProvider
    {
        /// <summary>
        /// Extension model type
        /// </summary>
        public override Type DomainModelType
        {
            get
            {
                return typeof(SimpleDomainModelExtension);
            }
        }

    }
}