Each element you can see in a DSL Diagram within the DSL editor is based on a Domain Class defined in your DSL Definition. If the user selects on of these shapes in the Editor he can change the values of the defined Domain Properties in the well known Properties Window of Visual Studio, but only Properties declared at design time will be shown in the Properties Window.
For some reason I want to change the number, name and type of the properties at runtime. E.g. some code will add some properties to one shape while the user works with the DSL model. Different shapes based on the same Domain Class should be able to display different properties as well. Such a behavior is not support by the DSL Tools in the current version, so I build a small library as a workaround to provide this functionality. As I know some of you have a similar request, I will explain my approach here, and of cause publish the sourcecode of my library below.
What you get…
But first of all, let me show you what you can expect. So you can decide to stop reading my article right at this point 😉
I designed a new Class DynamicPropertyCollection
which is a container of all new added properties. You will add a Domain Property of this type to your Domain Class at design time and then you can add more properties to this collection at runtime. I will care for showing these properties in the Properties Window.
You can see here the Properties Window while one Example Element is selected. The Name is the only regular designed Domain Property. The field Props is also specified at design time as a Domain Property of the type DynamicPropertyCollection
, but the user can not insert any text to this row. But as you can see with the minus symbol in front of the Props row there are some subproperties. These are the dynamic added properties and you can change these list at runtime as promised. The subproperties can contain any data type that can be used with the properties grid, and even more DynamicPropertyCollection
s. The even more row contains such an element:
There are two additional properties hidden below the even more row and as you can see, all user interface features like the calendar on DateTime
values are supported out of the box (all this comes with the great properties grid control).
Maybe you can claim I did not really keep all my promises because I did not add additional properties to the Example Element rather to using these sub properties. That’s right, but I think it’s very close and there are many advantages with this: You do not need to add code to the Domain Class, the Shape or anywhere within the generated classes and you can only use my DynamicPropertyCollection
class as a black box (although I try to whiten it here a little bit).
How does all this magic work?
The trick comes with the ICustomTypeDescriptor Interface. Normally the properties grid will use reflection to access all public properties of a class and shows these properties in the grid. But if you implement this interface the grid will ask you for a list of all properties in a PropertyDescriptorCollection. This question I can answer with another list of properties each time and so I can show dynamic properties for each element.
In my code you find a DynamicPropertyCollection
class that implements this interface and uses for most methods a default implementation, but some special code for public PropertyDescriptorCollection GetProperties(Attribute[] attributes). Internally there is a private readonly List<DynamicProperty> properties that contains all these properties.
The DynamicPropertyDescriptor
objects returned by GetProperties()
contain every information the property grid needs to show the list. Further more it will delegate get and set operations for the properties to this DynamicPropertyDescriptor
which stores the values in the underlying DynamicProperty
objects.
As a user you will hardly see the DynamicPropety
or DynamicPropertyDescriptor
class and need not to worry about these details. You can only work with the public interface of DynamicPropertyCollection
:
public void AddProperty(string name, Type type)
public void AddProperty(string name, Type type, object value)
public void RemoveProperty(string name)
public bool ExistsProperty(string name)
public Type GetPropertyType(string name)
public object GetPropteryValue(string name)
public void SetPropertyValue(string name, object value)
With this few classes and only few lines of code you get the properties shown as in the screenshots above!
…but how to serialize?
But this is only half the way to go. We need to serialize all the properties with the Domain Model and deserialize them when loading a model. Since the DynamicPropertyCollection
is part of the Domain Class as a Domain Property this seems to be the right and natural place to store all the values. The DSL Tools will use the TypeConverter of each type to convert the object to a string, so we have to provide a TypeConverter
for the DynamicPropertyCollection
:
public class DynamicPropertyCollectionTypeConverter : ExpandableObjectConverter
and attach this TypeConverter
to the DynamicPropertyCollection
:
[TypeConverter("BenjaminSchroeter.DynamicDslProperties.DynamicPropertyCollectionTypeConverter")]
public class DynamicPropertyCollection : ICustomTypeDescriptor
(There is a known issue and a workaround I described here with the TypeConverterAttribute
and the DSL Tools…)
The DynamicPropertyCollectionTypeConverter
inherits from ExpandableObjectConverter
to provide the expandable functionality in the properties grid, but all the other code is used for serialization only.
I have to provide Code to convert from and to string. In my string representation I have to store all properties with there name, the type and the value, of cause. To store the different values I use a TypeConverter
and convert the objectvalues to string as well. So you can only use types that can be converted to and from string, but that are all types most people use in the Properties Window since the user can only type and see strings there.
The representation in the XML file of your DSL will look like this:
<exampleElement name="ExampleElement1">
<props>
name_1="some text" type_1="System.String" value_1="hello"
name_2="some number" type_2="System.Int32" value_2="42"
name_3="yes or no" type_3="System.Boolean" value_3="True"
</props>
|
As you see, it is still human readable.
What should I do?
If you only want to use the library look at the following steps:
- Compile the
DynamicDslProperties
code as a library and add a reference to your DSL project. Maybe you want change the key file for signing.
- In the DSL Explorer you have to add a External Type with Namespace = "
BenjaminSchroeter.DynamicDslProperties
" and Name = "DynamicPropertyCollection
".
- Now you can add a Domain Property of type
DynamicPropertyCollection
to any Domain Class.
- Change the Default Value of the property to " " (one space only). This is crucial to initialize the class on creation of new Domain Elements! Otherwise the new Domain Element will have a null value for this property and you must handle it at various places.
- In the DSL Explorer look at the Xml Serialization Behavior of the property of this Domain Class and change the field Representation from Attribute to Element. This step is optional but provides you a much nicer XML representation.
- Add somewhere code to add properties to the
DynamicPropertyCollection
. Remember to wrap this and all changes to properties in a transaction.
For testing and debugging
For a fast jumpstart and to test the features I provide a small dialog to add and delete properties.
You can simply add some code to a shape to open this dialog on double click.
partial class ExampleShape
{
public override void OnDoubleClick(DiagramPointEventArgs e)
{
ExampleElement elem = e.HitDiagramItem.Shape.ModelElement as ExampleElement;
if ( elem != null)
{
EditDynamicPropertiesDialog f;
f = new EditDynamicPropertiesDialog(elem.Props, elem.Store);
f.ShowDialog();
}
base.OnDoubleClick(e);
}
}
Of cause this window and the DoubleClick event are only good while debugging and testing. For real world projects you will use the public interface described above to add and remove properties.
The code
In the zip file you will find the whole sourcecode of the described library to use in your projects. Just leave my copyright and the link to this article in the header of each file and do not expect any warranty. If you extend the library, find or fix some bugs I would appreciate a comment here.
There is also a whole DSL project in the zip file to try my code right away.
dynamicdslpropertiessource.zip
Update
This code is now part of the JaDAL – Just another DSL-Tools Addon Library project. Please download the current version from that page. The download over at CodePlex contains the source code described here, an example DSL language and the library as a binary. Future enhancements of the code will be published there, too.
Recent Comments