Partager via


Creating a Windows Forms-Based Domain-Specific Language

You can use Windows Forms to display the state of a domain-specific language (DSL) model, instead of using a DSL diagram. This topic walks you through binding a Windows Form to a DSL, using the Visual Studio Visualization and Modeling SDK.

In this walkthrough, you will create a domain-specific language (DSL) for describing instances of a composite design pattern.

Creating a WinForms DSL

Create a DSL from the Minimal WinForm Designer template.

In this walkthrough, the following names are assumed:

  • Solution and DSL name = WinFormsDsl.

  • Template = Minimal WinForm Designer.

  • File name extension = winFormsDsl.

Build and Run the Sample

To help you understand how binding to a form works, it is useful to build and run the new solution before you change it.

Press CTRL+F5. In the experimental instance of Visual Studio, open the Sample file in the debugging project.

Notice that it is displayed in a WinForms control.

You can also see the elements of the model displayed in the Explorer.

Add some elements either in the form or Explorer, and notice that they appear in the other display.

The WinForm DSL Solution

In the main instance of Visual Studio, notice the following points about the DSL solution:

  • In addition to the Dsl and DslPackage projects, the solution contains a third project named UI that contains the definition of a Windows Forms control. DslPackage depends on UI, and UI depends on Dsl.

  • In the DslPackage project, UI\DocView.cs contains the code that displays the WinForms control defined in the UI project.

  • DslDefinition.dsl contains no diagram elements. This is because you will not use DSL diagrams to view instance models of this DSL. Instead, you will bind a Windows Form to the model, and the elements on the form will display the model.

  • The UI project contains a working sample of a form control bound to the DSL. However, it will not work when you have changed the DSL Definition. The UI project contains:

    • A Windows Forms class named ModelViewControl.

    • A file named DataBinding.cs that contains an additional partial definition of ModelViewControl. To see its content, right-click the file in Solution Explorer, and click View Code.

Updating the WinForms DSL Solution

When you update the DSL Definition file to define your own DSL, you will have to update the control in the UI project to display your DSL. Unlike the Dsl and DslPackage projects, the sample UI project is not generated from DslDefinitionl.dsl. However, you can add .tt files to do this if you want.

Customizing the Default DSL Definition

In this section, you customize the DSL definition to contain some of the variable aspects of the composite design pattern:

  • The name of the base component or interface, whether child components are ordered, and whether each component contains a reference to its parent component.

  • The operations that every component supports.

  • The composite and leaf classes that comprise the types of components available in this instance.

To update the domain elements

  1. From Solution Explorer, open the DslDefinition.dsl file in the DSL designer.

  2. Remove the default elements and properties that are not needed for this DSL.

    1. Delete the ExampleElement domain class.

    2. From the ExampleModel domain class, delete the Color domain property.

  3. Rename the ExampleModel domain class to CompositeModel.

  4. To the CompositeModel domain class, add the following domain properties:

    1. Add a Name domain property of type String, and set its Is Element Name property to True.

    2. Add an IsOrdered domain property of type Boolean.

    3. Add a ReferencesParent domain property of type Boolean.

  5. Add a named domain class, and set its name to OperationClass.

  6. Add an embedding relationship to embed the OperationClass domain class in the CompositeModel domain class.

    1. Rename the OperationClasses role to Operations.

    2. Rename the CompositeModel role to Model.

  7. Add a named domain class, and set its name to ComponentClass.

    1. Add a Type domain property of type String, and set its Kind property to Calculated.

    2. In the Properties Window for the ComponentClass domain class, set the Inheritance Modifier to abstract.

  8. Add an embedding relationship to embed the ComponentClass domain class in the CompositeModel domain class.

    1. Rename the ComponentClasses role to Components.

    2. Rename the CompositeModel role to Model.

  9. Add two domain classes, and name them LeafClass and CompositeClass.

    • Add two inheritance relationships to derive both of the new classes from the ComponentClass domain class.

In addition, provide an implementation for the calculated property that you added to the ComponentClass domain class.

To implement the calculated Type property

  1. In Solution Explorer, add a new folder to the Dsl project, and name the folder CustomCode.

  2. To the CustomCode folder, add a new item:

    • Select the Code File template, and set the name to TypeProperty.cs.
  3. Open the TypeProperty.cs file for editing and add the following code to the file:

    namespace CompanyName.ProductName.WinFormsDsl
    {
        public partial class ComponentClass
        {
            public string GetTypeValue()
            {
                return this.GetType().Name;
            }
        }
    }
    

Specifying the Data Source

When creating the WinForms-based designer, you will access a data source associated with the domain model. However, the CompositeModel object is not available to the Data Source Configuration Wizard until you rebuild the Dsl project.

Also, delete the default WinForms control before building the project. You will create your own WinForms control later once you have created the data source.

To build the Dsl project

  1. In Solution Explorer, in the Dsl project, open the UI folder, and delete the Form.cs file.

  2. On the Solution Explorer toolbar, click the Transform All Templates button.

  3. Clean the solution, and build the Dsl project.

    Notes

    At this stage, the DslPackage project will not build without errors. You will update the DslPackage project later.

To create the data source

  1. Open the Data Sources window.

    Notes

    To display the Data Sources window, on the Data menu, click Show Data Sources.

  2. Delete the old data source, CompanyName.ProductName.WinFormsDsl.ExamplModel.

  3. Click Add New Data Source.

    The Data Source Configuration Wizard is displayed.

  4. Select Object as the data source type, and click Next.

  5. For the object to bind to, select the domain class that represents the root of the model:

    1. Expand the Dsl node.

    2. Expand the CompanyName.ProductName.WinFormsDsl node.

    3. Select the CompositeModel class.

  6. Click Finish.

    The CompositeModel data source object is displayed in the Data Sources window.

Creating the WinForms Editor

Create a WinForms user control that will function as the WinForms-based editor for the DSL.

To create the user control that will function as the WinForms-based editor

  • In Solution Explorer in the Dsl project under the UI folder, create a new user control named WinFormsDesigner.cs.

    1. Open the WinFormsDesigner.cs file for editing, and change the namespace for the control from CompanyName.ProductName.WinFormsDsl.UI to CompanyName.ProductName.WinFormsDsl.

    2. Open the WinFormsDesigner.Designer.cs file for editing, and make the same change to the namespace statement.

    3. Save and close the two code files.

To add the data source to the user control

  1. Open the WinFormsDesigner.cs file in design view.

  2. In the Data Sources window, update the control that will be created for each object and property in the CompositeModel data source:

    1. For the CompositeModel object, select Details.

    2. For the Name property of the model, select TextBox.

  3. From the Data Sources window, drag the CompositeModel object onto the user control.

    Visual Studio adds controls for the Name, IsOrdered, and ReferencesParent properties of the model to the user control. Visual Studio also creates a binding source and a binding navigator that are associated with the CompositeModel object.

    1. Delete the binding navigator, compositeModelBindingNavigator.

    2. Select the text box that Visual Studio created for the Name property, nameTextBox.

      In the Properties Window, locate and expand the (Data Bindings) property node. The (Data Bindings) property node in category view is under the Data tab.

      Notice that the Text property is bound to the Name property of the compositeModelBindingSource object.

  4. Select the nameTextBox control, expand the (DataBindings) property node, select the (Advanced) property, and then click the button to edit the property.

    The Formatting and Advanced Binding dialog is displayed.

    Set the Data Source Update Mode to OnPropertyChanged, and click OK.

    Repeat this step for the isOrderedCheckBox and the referencesParentCheckBox controls.

  5. Layout the controls on the form and modify their Text properties as necessary.

To add the operations and components collections to the user control.

  1. Add controls for the operations collection:

    1. From the Toolbox, add a DataGridView control to the user control and name it operationsDataGridView.

    2. From the Toolbox, add a ModelingBindingSource component and name it operationsModelingBindingSource.

      Notes

      To add the ModelingBindingSource component to the Toolbox, right-click the Data tab, and click Choose Items. In the Choose Toolbox Items dialog, select ModelingBindingSource from the .NET Framework Tab, and click OK.

      Set the DataSource property of operationsModelingBindingSource to compositeModelBindingSource, and set the DataMember property to Operations.

    3. Click the right-pointing arrow on the upper-right corner of the operationsDataGridView control to open the DataGridView Tasks menu.

      Set the data source to the operationsModlelingBindingSource object.

      Deselect Enable Adding. Then click outside the DataGridView Tasks menu to close it.

    4. Add a label above the data grid view control, and set its Text property to Operations.

    5. Add a button below the data grid view control, set its Text property to New Operation, and rename the button to newOperationButton.

  2. Add controls for the components collection:

    1. From the Toolbox, add a DataGridView control to the user control and name it componentsDataGridView.

    2. From the Toolbox, add a second ModelingBindingSource component and name it componentsModelingBindingSource.

      Set the DataSource property of componentsModelingBindingSource to compositeModelBindingSource, and set the DataMember property to Components.

    3. Click the right-pointing arrow on the upper-right corner of the componentsDataGridView control to open the DataGridView Tasks menu.

      Set the data source to the componentsModlelingBindingSource object.

      Deselect Enable Adding. Then click outside the DataGridView Tasks menu to close it.

    4. Add a label above the data grid view control, and set its Text property to Components.

    5. Add a button below the data grid view control, set its Text property to New Leaf, and rename the button to newLeafButton.

    6. Add a second button below the data grid view control, set its Text property to New Composite, and rename the button to newCompositeButton.

Update the Code to Support the Designer

You need to update the DocView text template to reflect the change in the name of the WinForms user control.

To update the DocView text template in the DslPackage project

  1. In the DslPackage project, under the UI folder:

    1. Open the DocView.tt file.

    2. Replace the three instances of the string ExampleModelForm with the string WinFormsDesigner.

    3. Save and close the DocView.tt file.

  2. Transform all templates.

Update the WinForms control code to access the bound data.

To update the WinFormsDesigner.cs file

  1. Open the WinFormsDesigner.cs file for editing.

  2. Add the following using statements to the file.

    using Microsoft.VisualStudio.Modeling;
    using Microsoft.VisualStudio.Modeling.Design;
    
  3. Add the following line to the WinFormsDesigner constructor, after the call to the InitializeComponent method.

    this.compositeModelBindingSource.CurrentChanged +=
        new System.EventHandler(
            compositeModelBindingSource_CurrentChanged);
    
  4. Add the following members to the WinFormsDesigner class.

    public IContainer Components { get { return components; } }
    
    /// <summary>Binds the WinForms data source object to the DSL model.
    /// </summary>
    /// <param name="nodelRoot">The root element of the model.</param>
    public void DataBind(ModelElement modelRoot)
    {
        WinFormsDataBindingHelper.PreInitializeDataSources(this);
        this.compositeModelBindingSource.DataSource = modelRoot;
        WinFormsDataBindingHelper.InitializeDataSources(this);
        this.UpdateFormState();
    }
    
    /// <summary>Handles the CurrentChanged event for the binding source
    /// for the copositeModel object.</summary>
    public void compositeModelBindingSource_CurrentChanged(
        object sender, System.EventArgs e)
    {
        this.UpdateFormState();
    }
    
    /// <summary>Updates the non-data-bound elements of the form based on
    /// the current state of the data-bound data.</summary>
    /// <remarks>Add code as necessary.</remarks>
    private void UpdateFormState()
    {
    }
    

Add the following button click event handlers.

To allow the addition of operations and components to the DSL

  1. Open the WinFormsDesigner.cs file for editing.

  2. Add the button click event handlers as follows:

    private void NewOperationButton_Click(object sender, EventArgs e)
    {
        CompositeModel modelRoot =
            This.compositeModelBindingSource.DataSource as CompositeModel;
        if (modelRoot == null) return;
    
        modelRoot.Operations.AddNew();
    }
    
    private void NewLeafButton_Click(object sender, EventArgs e)
    {
        CompositeModel modelRoot =
            This.compositeModelBindingSource.DataSource as CompositeModel;
        if (modelRoot == null) return;
    
        using (Transaction t =
            modelRoot.Store.TransactionManager.BeginTransaction("New Leaf"))
        {
            ComponentClass newLeaf = new LeafClass(modelRoot.Partion);
            ElementGroup group = new ElementGroup(modelRoot.Partion);
            group.Add(newLeaf);
            group.MarkAsToot(newLeaf);
            ElementOperations ops =
                new ElementOperations(modelRoot.Store, modelRoot.Partion);
            ops.MergeElementGroup(modelRoot, group);
    
            t.Commint();
        }
    }
    
    private void NewCompositeButton_Click(object sender, EventArgs e)
    {
        CompositeModel modelRoot =
            This.compositeModelBindingSource.DataSource as CompositeModel;
        if (modelRoot == null) return;
    
        using (Transaction t =
            modelRoot.Store.TransactionManager.BeginTransaction("New Composite"))
        {
            ComponentClass newComposite = new CompositeClass(modelRoot.Partion);
            ElementGroup group = new ElementGroup(modelRoot.Partion);
            group.Add(newComposite);
            group.MarkAsToot(newComposite);
            ElementOperations ops =
                new ElementOperations(modelRoot.Store, modelRoot.Partion);
            ops.MergeElementGroup(modelRoot, group);
    
            t.Commint();
        }
    }
    
  3. Open the WinFormsDesigner.cs file in design mode.

  4. To attach the event handlers to the buttons:

    1. In the Properties window set the Click event handler for the New Operation button to NewOperationButton_Click.

    2. Set the Click event handler for the New Leaf button to NewLeafButton_Click.

    3. Set the Click event handler for the New Composite button to NewCompositeButton_Click.

Testing the Language

The next step is to build and run the DSL designer in a new instance of Visual Studio so that you can verify that the data binding is working correctly.

To exercise the language

  1. Rebuild and start debugging your project.

    The experimental build of Visual Studio opens the Debugging solution, which contains an empty test file.

  2. In Solution Explorer, double-click the Test.winFormsDsl file to open it in the designer.

  3. Select the WinFormsDsl Explorer window to view the explorer for your language as you make changes.

    Notes

    To make the WinFormsDsl Explorer window visible, from the View menu, point to Other Windows, and click WinFormsDsl Explorer.

  4. Use the form to update the Name and the Is Ordered and References Parent properties.

    The corresponding values in the Properties window are updated to match the values on the form.

  5. Using the Properties window, update the properties of the Composite Model node.

    The form is updated to match the updated properties.

  6. Use the buttons on the form to add operations and components.

    Use the WinFormsDsl Explorer and the Properties window to review the changes in the model.

  7. Save the solution, and then close the experimental build.