Create 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 by using the Visual Studio Visualization and Modeling SDK.
The following image shows a Windows Form UI and the model explorer for a DSL instance:
Create a Windows Forms DSL
The Minimal WinForm Designer DSL template creates a minimal DSL that you can modify to suit your own requirements.
Create a DSL from the Minimal WinForm Designer template.
In this walkthrough, the following names are assumed:
- Solution and DSL name:
FarmApp
- Namespace:
Company.FarmApp
- Solution and DSL name:
Experiment with the initial example that the template provides:
Transform All Templates.
Build and run the sample (Ctrl+F5).
In the experimental instance of Visual Studio, open the
Sample
file in the debugging project.Notice that it is displayed in a Windows Forms control.
You can also see the elements of the model displayed in the Explorer.
Add some elements either in the form or the Explorer, and notice that they appear in the other display.
In the main instance of Visual Studio, notice the following points about the DSL solution:
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.In addition to the
Dsl
andDslPackage
projects, the solution contains a third project namedUI.
UI project contains the definition of a Windows Forms control.DslPackage
depends onUI
, andUI
depends onDsl
.In the
DslPackage
project,UI\DocView.cs
contains the code that displays the Windows Forms control that is defined in theUI
project.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. TheUI
project contains:A Windows Forms class named
ModelViewControl
.A file named
DataBinding.cs
that contains an additional partial definition ofModelViewControl
. To see its content, in Solution Explorer, open the shortcut menu for the file and choose View Code.
About the UI project
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
. You can add .tt files to generate the code if you want, although that is not covered in this walkthrough.
Update the DSL definition
The following image is the DSL definition used in this walkthrough.
Open DslDefinition.dsl in the DSL designer.
Delete ExampleElement
Rename the ExampleModel domain class to
Farm
.Give it additional domain properties named
Size
of type Int32, andIsOrganic
of type Boolean.Note
If you delete the root domain class and then create a new root, you will have to reset the Editor Root Class property. In DSL Explorer, select Editor. Then in the Properties window, set Root Class to
Farm
.Use the Named Domain Class tool to create the following domain classes:
Field
- Give this an additional domain property namedSize
.Animal
- In the Properties window, set Inheritance Modifier to Abstract.
Note
The Named Domain Class tool and the other tools mentioned in this section are found in the Toolbox tool window. You can open or hide this window with View > Toolbox.
Use the Domain Class tool to create the following classes:
Sheep
Goat
Use the Inheritance tool to make
Goat
andSheep
inherit fromAnimal
.Use the Embedding tool to embed
Field
andAnimal
underFarm
.You might want to tidy the diagram. To reduce the number of duplicate elements, use the Bring Subtree Here command on the shortcut menu of leaf elements.
Transform All Templates in the toolbar of Solution Explorer.
Build the Dsl project.
Note
At this stage, the other projects will not build without errors. However, we want to build the Dsl project so that its assembly is available to the Data Source Wizard.
Update the UI project
Now you can create a new user control that will display the information that is stored in the DSL model. The easiest way to connect the user control to the model is through data bindings. The data binding adaptor type named ModelingBindingSource is specifically designed to connect DSLs to non-VMSDK interfaces.
Define your DSL model as a data source
On the Data menu, choose Show Data Sources.
The Data Sources window opens.
Choose Add New Data Source. The Data Source Configuration Wizard opens.
Choose Object, Next.
Expand Dsl, Company.FarmApp, and select Farm, which is the root class of your model. Choose Finish.
In Solution Explorer, the UI project now contains Properties\DataSources\Farm.datasource
The properties and relationships of your model class appear in the Data Sources window.
Connect your model to a form
In the UI project, delete all the existing .cs files.
Add a new User Control file named
FarmControl
to the UI project.In the Data Sources window, on the drop-down menu on Farm, choose Details.
Leave the default settings for the other properties.
Open FarmControl.cs in the design view.
Drag Farm from the Data Sources window onto FarmControl.
A set of controls appears, one for each property. The relationship properties do not generate controls.
Delete farmBindingNavigator. This is also automatically generated in the
FarmControl
designer, but it is not useful for this application.Using the toolbox, create two instances of DataGridView, and name them
AnimalGridView
andFieldGridView
.Note
An alternative step is to drag the Animals and Fields items from the Data Sources window onto the control. This action automatically creates data grids and bindings between the grid view and the data source. However, this binding does not work correctly for DSLs. Therefore it is better to create the data grids and bindings manually.
If the Toolbox does not contain the ModelingBindingSource tool, add it. On the shortcut menu of the Data tab, choose Choose Items. In the Choose Toolbox Items dialog, select ModelingBindingSource from the .NET Framework tab.
Using the Toolbox, create two instances of ModelingBindingSource, and name them
AnimalBinding
andFieldBinding
.Set the DataSource property of each ModelingBindingSource to farmBindingSource.
Set the DataMember property to Animals or Fields.
Set the DataSource properties of
AnimalGridView
toAnimalBinding
, and ofFieldGridView
toFieldBinding
.Adjust the layout of the Farm control to your taste.
The ModelingBindingSource is an adapter that performs several functions that are specific to DSLs:
It wraps updates in a VMSDK Store Transaction.
For example, when the user deletes a row from the data view grid, a regular binding would result in a transaction exception.
It ensures that, when the user selects a row, the Properties window displays the properties of the corresponding model element, instead of the data grid row.
Schema of links between data sources and views.
Complete the bindings to the DSL
Add the following code in a separate code file in the UI project:
using System.ComponentModel; using Microsoft.VisualStudio.Modeling; using Microsoft.VisualStudio.Modeling.Design; namespace Company.FarmApp { partial class FarmControl { public IContainer Components { get { return components; } } /// <summary>Binds the WinForms data source to the DSL model. /// </summary> /// <param name="nodelRoot">The root element of the model.</param> public void DataBind(ModelElement modelRoot) { WinFormsDataBindingHelper.PreInitializeDataSources(this); this.farmBindingSource.DataSource = modelRoot; WinFormsDataBindingHelper.InitializeDataSources(this); } } }
In the DslPackage project, edit DslPackage\DocView.tt to update the following variable definition:
string viewControlTypeName = "FarmControl";
Test the DSL
The DSL solution can now build and run, although you might want to add further improvements later.
Build and run the solution.
In the experimental instance of Visual Studio, open the Sample file.
In the FarmApp Explorer, open the shortcut menu on the Farm root node, and choose Add New Goat.
Goat1
appears in the Animals view.Warning
You must use the shortcut menu on the Farm node, not the Animals node.
Select the Farm root node and view its properties.
In the form view, change the Name or Size of the farm.
When you navigate away from each field in the form, the corresponding property changes in the Properties window.
Enhance the DSL
Make the properties update immediately
In the design view of FarmControl.cs, select a simple field such as Name, Size or IsOrganic.
In the Properties window, expand DataBindings and open (Advanced).
In the Formatting and Advanced Binding dialog, under Data Source Update Mode, choose OnPropertyChanged.
Build and run the solution.
Verify that when you change the content of the field, the corresponding property of the Farm model changes immediately.
Provide Add buttons
In the design view of FarmControl.cs, use the toolbox to create a button on the form.
Edit the name and text of the button, for example to
New Sheep
.Open the code behind the button (for example by double-clicking it).
Edit it as follows:
private void NewSheepButton_Click(object sender, EventArgs e) { using (Transaction t = farm.Store.TransactionManager.BeginTransaction("Add sheep")) { elementOperations.MergeElementGroup(farm, new ElementGroup(new Sheep(farm.Partition))); t.Commit(); } } // The following code is shared with other add buttons: private ElementOperations operationsCache = null; private ElementOperations elementOperations { get { if (operationsCache == null) { operationsCache = new ElementOperations(farm.Store, farm.Partition); } return operationsCache; } } private Farm farm { get { return this.farmBindingSource.DataSource as Farm; } }
You will also need to insert the following directive:
using Microsoft.VisualStudio.Modeling;
Add similar buttons for Goats and Fields.
Build and run the solution.
Verify that the new button adds an item. The new item should appear in both the FarmApp Explorer and in the appropriate data grid view.
You should be able to edit the name of the element in the data grid view. You can also delete it from there.
About the code to add an element
For the new element buttons, the following alternative code is slightly simpler.
private void NewSheepButton_Click(object sender, EventArgs e)
{
using (Transaction t = farm.Store.TransactionManager.BeginTransaction("Add sheep"))
{
farm.Animals.Add(new Sheep(farm.Partition)); ;
t.Commit();
}
}
However, this code does not set a default name for the new item. It does not run any customized merge that you might have defined in the Element Merge Directives of the DSL, and it does not run any custom merge code that might have been defined.
Therefore we recommend that you use ElementOperations to create new elements. For more information, see Customizing Element Creation and Movement.