Using a non-string property as an element name
By default, DSL Tools V1 uses domain properties of type "string" as names for its elements.
If you try to use a property of some other type, for example Int32, you'll get the following validation message:
"Error 1 Domain Property Name is marked IsName, but has type System.Int32. Unless it has an ElementNameProvider, the type of a Name property should be System.String. C:\dev\test\Language2\Dsl\DslDefinition.dsl 0 1 Dsl".
Unfortunately, this is something of a misleading error message as actually its type MUST be string, regardless of whether it has an ElementNameProvider specified. We don't support IsName=true properties of any other type.
You can however work around this limitation by having a second property of whatever type you like, from which your string Name property is calculated.
Let's say you want to use an Int32 to identify elements by number.
Here's a model fragment of an amended versionof the MinimalLanguageSample:
I've added a second domain property "Number" to the ExampleElement class. I've aslso set IsBrowsable to false for the "Name" property as we don't want end users to see that there is anything other than a "Number" for a given ExampleElement. Unfortunately we can't mark the "Name" property Internal to really hide it from the API as well due to a V1 limitation.
I want the XML to look nice, and also to hide the existence of "Name" from the XML, however I also have to use a property of type string for the XML moniker key that is used to cross-reference elements inside the file. To fake this, I've changed the XmlName for "Name" to be "number" and set the "Number" property to have an XmlName of "number2" and its Representation to be ignore so it is never serialized. I'm not able to choose to just serialize the "Number" property and ignore "Name" because, reasonably enough, the property identified as the moniker key must be serialized.
Next, I set the Kind of the Name property to be Calculated. This means I'll have to write custom code to provide the value of the property. I'm just going to implement it by converting the Number property to a string:
partial class ExampleElement
{
private string GetNameValue()
{
return this.Number.ToString();
}
}
Finally, I have to give any new instances of ExampleElement a sensible number when they are added to the diagram. To do this, I need to use a custom ElementNameProvider:
internal class NumberProvider : Microsoft.VisualStudio.Modeling.ElementNameProvider
{
protected override void SetUniqueNameCore(Microsoft.VisualStudio.Modeling.ModelElement element,
string baseName,
IDictionary<string, Microsoft.VisualStudio.Modeling.ModelElement> siblingNames)
{
ExampleElement example = element as ExampleElement;
if (example != null)
{
example.Number = siblingNames.Count + 1;
}
}
}
This code is called when a name needs to be giben to an element. I'm just using the number of existing siblings to give me a sensible initial number. To hook this class into the model, you have to tell your DSL about it by adding an external type with the matching name and namespace. Then you need to select the "Name" property on ExampleElement and in its properties you should now be able to pick NumberProvider as its ElementNamerProvider. You'll need to re-transform to regenerate your code of course.
Now when a new ExampleElement is added, the NumberProvider should get called, which will set the new ExampleElement's "Number" property to a value one more than the count of siblings. The "Name" of the new ExampleElement will then be calculated from its Number and saved to the XML file with the tag "number".
There's one flaw in this. Because the property we're actually saving is the "Name" property (albeit with the tag "number") which is a Calculated property, then when the file is read back in, the value is simply thrown away on the assumption that it can be recalculated. What we really want is for the number tag to cause the "Number" property to get populated.
To do this, we'll need to switch the "Name" property from Kind=Calculated to Kind=CustomStorage. This means we have to have custom code for both getting and setting the value and in the setter we can parse the number and store it in the "Number" property. Effectively the "Number" property is providing backing store for the "Name" property. Here's a simplified version of that with no error checking or globalization:
private void SetNameValue(string newValue)
{
if (!this.Store.InUndoRedoOrRollback)
{
this.Number = Int32.Parse(newValue);
}
}
The check for InUndoRedoOrRollback is necessary because an undo operation will perform a blanket ovewrite of the values of both "Number" and "Name" with their previous values, making the parse unneccesary.
Phew! I think its fair to say this is a little overcomplicated. We'll review the scenarios here for a future release.
Comments
- Anonymous
June 08, 2009
PingBack from http://quickdietsite.info/story.php?id=2910