Finding and removing a default command in DSL Tools (part 4 of Compartment Mappings)
Tags: dotnet, dsl, JaDAL, orcas, visual studio, vsx2 Comments »
[Update (2008-05-21): This code is now hosted at CodePlex as part of JaDAL. And a follow-up article was published.]
Previously on…
This article is part of a series. A table of contents can be found at the end of the first article. Part 2 contains a user guide and in part 3 I showed most of the internals of the library.
The "Reroute" command
For each connector there is a command named "Reroute" in the context menu of the DSL editor. Now I have to remove this command for the compartment entry mappings. If the user would use it, the layout of my connectors will be destroyed. Unfortunately I did not find an option, an extension point or anything else where I could change or handle this particular command or the context menu of the connectors.
The generated code
First I looked at the generated code of my Dsl
and DslPackage
project. There is a GeneratedVSCT.vsct
file. In vsct
files the commands are defined, but in this one I could not find anything with "Reroute". Then I searched for the string "Reroute" in all files and found nothing.
The DSL binaries
I found no documentation for this command and was a little desperate. I needed to find the place where it comes from. I searched the whole VS SDK folder (text and binary files) for the string "reroute". A promising hint was found in the Microsoft.VisualStudio.Modeling.Sdk.Shell.dll file. Now let’s go to the Reflector and take a deeper look inside.
After a while I found the Microsoft.VisualStudio.Modeling.Shell.CommandSet class. And hey, in the msdn documentation article for this class there is also the Reroute Line command mentioned. With this knowledge one can easily find the generated CommandSet.cs
file as part of the DslPackage
. In this file there are two classes AbcCommandSetBase
and AbcCommandSet
where Abc
is the name of you DSL project.
The design pattern of this classes is called double derived and can be found quiet often within the generated DSL code. The ...Base
class stays abstract and derives from a class defined in the Microsoft Visual Studio SDK libraries, in this case from the CommandSet class I found above. All code created by the code generator is added to this ...Base
class. The other class derives from the ...Base
class and is empty – all parts of the project uses this one. Since it is declared with the partial
keyword, the user can override ALL methods and change the behavior of all aspects of this class.
And that is what we are going to do: override the GetMenuCommands()
method and remove the reroute command:
protected override IList<MenuCommand> GetMenuCommands() { // get the base list IList<MenuCommand> cmds = base.GetMenuCommands(); // find the reroute command MenuCommand rerouteCommand = cmds.First( c => c.CommandID == CommonModelingCommands.RerouteLine); // if found, remove it if (rerouteCommand != null) cmds.Remove(rerouteCommand); // and return the changed list return cmds; }
That’s it. Pretty easy, but you have to discover where to add this logic.
Connectors between compartment shape entries with DSL Tools – part 3
Tags: dotnet, dsl, JaDAL, orcas, visual studio, vsx3 Comments »
[Update (2008-05-21): This code is now hosted at CodePlex as part of JaDAL. And a follow-up article was published.]
Previously on…
This article is part of a series. The table of contents can be found at the end of the first article. In that article you can also find a brief overview. In the second part there is a short user guide and a download link of the source code and binaries.
How does it all work?
I don’t want to explain every single detail of my code. If you want to go that deep, you have to read the source code yourself and maybe you will find some comments. I will only show you the fundamental parts of the CompartmentMapping library.
There are a few things you have to consider to achieve the aim of the library:
- When creating a connector from Shape A to Shape B how should I get and store the Compartment Entry information for the Reference Relationship?
- After that, the connector representing this relationship must be drawn in such a way that it seems to be a connector from one entry to another.
- What can a user do to destroy the layout of my connectors? And even more important: how can I prevent him from doing so?
- If the user deletes a compartment entry that is part of a relationship, the relationship needs to be deleted, too.
The Connection Builder
There is the concept of Connection Builders used by the DSL Tools. While your day to day use of DSL, you don’t have to worry about Connection Builders since they will be generated by the DSL Tools code generator. But you can turn this generation off and provide your implementation of a Connection Builder for a certain relationship. (see number 5 in the user guide).
A Connection Builder is a static class and will be assigned to a connection toolbar item of your editor. This class contains four interesting methods:
-
bool CanAcceptSource(ModelElement)
-
bool CanAcceptTarget(ModelElement)
-
bool CanAcceptSourceAndTarget(ModelElement, ModelElement)
-
ElementLink Connect(ModelElement, ModelElement)
The first two methods are used to determine if a particular ModelElement
can act as source or target of you connection. For example you can check the ModelElement
for a certain property to be set and only allow elements with this property as source and with another property as target, or whatever.
With CanAcceptSourceAndTarget()
you can check whenever a concrete combination of source and target ModelElements
is allowed to be connected by your relationship.
Last but not least with the Connect()
method you have to create this new relationship for the two selected ModelElements
.
In such a Connection Builder I will add the logic to check not only the model elements but also the selected entry inside the Compartment Shape. At this point I will mix the model representation (containing of Domain Classes and Relationships) and the graphical appearance (containing of Shapes and Connectors) but there is no better way at this time since the DSL Tools can only create connectors from shape to shape.
The first problem I ran into: How to get the shape of the model element in the Connection Builder if the only parameter is the ModelElement?
I used the PresentationViewsSubject.GetPresentation() method and I’m hoping it will always work. The architecture is build in such a way, that one ModelElement
can have multiple shapes, but I didn’t saw such a configuration until now, so I assume there will be only one shape.
After holding the shape in my hand I need to know something about the selected Compartment Entry. This becomes a little bit complicated, too: Sometimes I need the entry right below the mouse cursor (for CanAcceptSource()
) and sometimes the entry that was below the mouse when the user pressed the mouse button. Of course a Compartment Shape doesn’t provide any of that information. Take a look at CompartmentMouseTrack
and ICompartmentMouseActionTrackable
in the CompartmentMapping library and you will see in which way I handle mouse events of the shape to track all these mouse actions for the Compartment Shapes that are under the control of my library.
With all these new information my CompartmentMappingBuilderBase
can decide to allow or permit the creation of a connection. With some virtual
methods, it can delegate more details of this decision to your code (see the advanced options here).
How to trick the routing algorithm of DSL Tools
With the Connection Builder one can create relationships in the domain model but the connector inside the visual representation of your model will still be routed from shape to shape and won’t give you a proper understanding of the entry to entry relationship. So we have to change the routing in a way that the start and endpoints of the connector will be glued near to the entry on one side of the shape.
This will be done with an AddRule (CompartmentMappingAddRuleBase
) every time a new connector will be added to the diagram (and when opening a saved diagram).
After determination the favored points on the shape outline you can set them to the connector with the FromEndPoint and ToEndPoint properties. Don’t forget to change the FixedFrom and FixedTo to VGFixedCode.Caller
as well.
Don’t get tricked by the routing algorithm
If you change the start and endpoints of a connector in the AddRule these values aren’t set forever. The user could collapse and expand the compartment shape or use the "Reroute" command that is visible in the context menu of every connector. He could also move the connector or only the start and endpoints on the shape outline. If he deletes one entry that was shown above an entry with a connection this entry moves closer to the top and the connection should do the same.
With a few lines of code, we can forbid the user to change the routing of a connectors. In the connector class the CanManuallyRoute
property simply have to return false
.
Every time the size of the shape changes (see OnAbsoluteBoundsChanged event) I will recalculate the connection points of all connectors assigned to this shape. This event will handle a bunch of cases for me: insertion and deletion of other entries, expanding and collapsing of the whole shape and singe compartment lists and renaming of entries which can cause reordering of the compartment list.
Delete propagation
The delete propagation is an easy requirement. I just added a DeletingRule that keep track of the deletion of certain Compartment Entries and if one is deleted it looks for Compartment Mapping Relationships and deletes them.
It is a little bit challenging to find the relationships coming from the entry. If you want to know more details take a look at the CompartmentEntryDeletingRuleBase
source code.
Upcoming article
In the last article of this series I will explain the way of removing the "Reroute" command from the connector context menu. The actual code consist only of a few lines, but the way I found the point to do it can be interesting for someone because the "Reroute" command is not proper documented anywhere.
Connectors between compartment shape entries with DSL Tools – part 2
Tags: dotnet, dsl, JaDAL, orcas, visual studio, vsx7 Comments »
[Update (2008-05-21): This code is now hosted at CodePlex as part of JaDAL. And a follow-up article was published.]
Previously on…
This article is part of a series. A table of contents can be found at the end of the first article. In that article you can also find a brief overview.
User guide
If you read this article to this point you will properly use or at least evaluate the CompartmentMapping library. I will provide you with a step-by-step tutorial. There are pretty some steps, but its the shortest way I could imagine to create my solution. Please give it a try. 🙂
- You need TTxGen to generate some code from my templates. See my last article for more information and a workaround for an installation problem.
- I assume you have a DSL solution and at least two Domain Classes mapped to two compartment shapes and two more Domain Classes used as entries of the compartment shapes created. In my example these classes are called
Parent1
,Entry1
,Parent2
andEntry2
.
The entries need a unique identifier. You could use a name property for this, but there will be some difficulties if the user creates two entries in one shape with the same name, so I will use a guid. (Don’t forget to create a new guid in the constructor or another proper place.) - Create a Reference Relationship from
Parent1
toParent2
. This will be automatically namedParent1ReferencesParent2
. We will use this reference to present the relationship from one entry to the other. I would like to create Reference Relationships from this relationship to the entries, but relationships of relationships are not supported. We have to store the guids of the entries in the relationship and add two Domain Properties for this purpose to it. I named themfromEntry
andtoEntry
.
- Set the
Allows Duplicates
property of theParent1ReferencesParent2
relationship totrue
. - Set the
Is Custom
property of the Connection Builder (Parent1ReferencesParent2Builder
) in the DSL Explorer totrue
. - Add a reference to
CompartmentMapping.dll
to both theDsl
andDslPackage
project. - Add a new xml file (in my example
CompartmentMappings.xml
) to your solution and write the following code in it:<?xml version="1.0" encoding="utf-8" ?> <CompartmentConnections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="CompartmentMappings.xsd" namespace="BenjaminSchroeter.CompartmentMappingExample" > <CompartmentConnection> <CompartmentSource> <DomainClass name="Parent1"/> <EntryDomainClass name="Entry1"/> <Shape name="CompartmentShape1" /> </CompartmentSource> <CompartmentTarget> <DomainClass name="Parent2" /> <EntryDomainClass name="Entry2" /> <Shape name="CompartmentShape2" /> </CompartmentTarget> <Connection name="Parent1ReferencesParent2"> <Shape name="Connector" /> </Connection> </CompartmentConnection> </CompartmentConnections>
(If you place the xsd file in the same directory you get IntelliSense and error checking for free.)
- Place the
CompartmentConnections.tt
file in the same directory and right-click on the xml file and choose Generate xGen Template. ACompartmentMappings_xGen.tt
file will be created below the xml file and there you have to add the following lines:<#@ ParentFileInjector processor="TTxGenDirectiveProcessor" requires="fileName='CompartmentMappings.xml'" #> <#@ include file="CompartmentConnections.tt" #>
(Be sure you use the right xml file name here.)
Now press the Transform All Templates Button in the Solution Explorer to generate the the code from this template.
- Now you have to write code by yourself (nearly by yourself, the frames are generated, too). Take a look at the generated cs file
CompartmentConnections_xGen.cs
.At the top you will find commented code. Copy this code to a new cs file and remove the comments. All code you will write for the compartment mappings you will write in this file.
- Complete the
CreateElementLink()
method in this file. Here you have to add code to store the selected compartment entries for source and target in the properties of the relationship (see 3).protected override ElementLink CreateElementLink(Parent1 source, SelectedCompartmentPartType sourcePartType, Entry1 sourceEntry, Parent2 target, SelectedCompartmentPartType targetPartType, Entry2 targetEntry) { Parent1ReferencesParent2 result = new Parent1ReferencesParent2(source, target); result.fromEntry = sourceEntry.Guid; result.toEntry = targetEntry.Guid; return result; }
- In the
CreateElementLink()
method you have stored the source and target entry information somewhere; my library does not know about this location. So we need two more methods to compare an entry with a relationship and answer the question "Is this entry the source of a given relationship?" and the same for the target. These code resists in the same file:public override bool IsEntryConnectionSource (Entry1 entry, Parent1ReferencesParent2 connection) { if (entry == null) return false; return connection.fromEntry.Equals(entry.Guid); } public override bool IsEntryConnectionTarget (Entry2 entry, Parent1ReferencesParent2 connection) { if (entry == null) return false; return connection.toEntry.Equals(entry.Guid); }
Always care for
entry == null
this will be used to check whether the head of the shape is meant. Even if you do not allow the head as source or target this method will be called with anull
parameter from time to time. - Create a
partial class
for your Domain Model (CompartmentMappingExampleDomainModel
) and add the following methods.protected override Type[] GetCustomDomainModelTypes() { return CompartmentMappingUtil.AllCompartmentMappingRules(this); }
(If you have already some custom rules, you can use an overloaded version of the
AllCompartmentMappingRules()
method.) -
In the
DslPackage
project create apartial class
for the generated Command Set and add the following code:protected override IList<MenuCommand> GetMenuCommands() { return CompartmentMappingUtil.RemoveRerouteCommand (base.GetMenuCommands()); }
That’s all. Only 13 steps, a few settings and conventions and 10 lines of C# code to write. I know it is not very easy but I think it is ok. And the best: as you will see it is working.
Now compile and start you solution. You can now create connectors from one entry of the Parent1
shape to an entry of the Parent2
shape. Great – isn’t it?
Take a look at the properties in the Properties window when selecting a connector. You see the guids of the corresponding entries and can use them in your code when working with this connections for code generation or whatever you need to do.
Advanced options
There are some more options besides the basics described above to configure and extend the compartment mappings.
Some of them can easily turned on in the xml file:
- For
CompartmentSource
andCompartmentTarget
you can specify an attribute calledallowHeadAsSource
(andallowHeadAsTarget
respectively) and set it totrue
, to allow the head of the compartment shape as source or target. Remember, if you do this theCreateElementLink()
method will be called withnull
values for the entries and you have to handle this. - On the
Connection
element there are two more optional attributes, too.If you set
allowSelfReference
totrue
the user can create connections from one entry of one shape to another (or the same) entry of the same shape (source = target). This makes only sense if you specifyCompartmentSource
andCompartmentTarget
to be the same Domain Class and the same shape.With the
suppressEntryDeletePropagation
attribute set totrue
you suppress the deletion of the connection when a corresponding compartment entry is deleted. Be careful with this setting: Wrong connections will remain in your model and produce ugly diagrams. - One of source and target can be a regular shape (that is a geometry or image shape and not a compartment shape). Replace either
CompartmentSource
withSource
orCompartmentTarget
withTarget
. For these elements you don’t need to define aEntryDomainClass
element since regular shapes do not have entries.
You can add even more code to the C# file from step 9 to 11. There are two more methods you can override: CanAcceptAsCompartmentSource()
and CanAcceptAsCompartmentSourceAndTarget()
. If you do so, the DSL editor will ask your code while the user moves the mouse over shapes when creating the connection to identify the current shape and entry as a valid source or target in you scenario. It is pretty much the same pattern as used in the DSL Tools itself. But remember, this method will be called very often und should have a very short running time.
Download
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.
Upcoming articles
Part 3 and part 4 of this series will give an in depth view into the internals of the library.
Connectors between compartment shape entries with DSL Tools – part 1
Tags: dotnet, dsl, JaDAL, orcas, visual studio, vsx6 Comments »
[Update (2008-05-21): This code is now hosted at CodePlex as part of JaDAL. And a follow-up article was published.]
This is the first part of an article series to this topic on a library I wrote. At the end of this article you find links to the upcoming articles. The download can be found at part 2 along with a brief "user guide".
Let’s begin…
With DSL Tools you get compartment shapes for your diagrams that look very much like the class-shapes in the graphical class designer of Visual Studio. These compartment shapes are very useful in many model designs and can visualize much information in a compact way. The user can collapse the whole shape (Shape 4) or single compartment lists (Shape 1, Shape 6) to save even more space on his diagram.
Like in the class designer you can only create connections from one shape to another not from one compartment entry to a shape or another compartment entry. The ability to create such connectors can simplify many DSL designs and on the VSX forum a couple of people asked for this feature. But there is a simple answer to this topic:
"This would be a useful feature, but unfortunately it doesn’t fit into our plans for DSL tools V1. It’s something we’ll consider for the next version, though."
[Grayson Myers MSFT]
What should I do? Change my DSL and make it complicated and less useful because of a limitation of the framework? Wait for an upcoming Version without a release date and without knowing that it will work for me? This all sounds like a very bad idea to me. So let’s change or extend the framework!
I wrote a library to bring this feature to the DSL Tools. With this library you can
- Create connections between compartment entries of two different compartment shapes (or even within the same shape, if you like). You can specify the allowed type of the source or target shape or select the same type for source and target. You can also constraint the allowed compartment entries by type if your source (or target) compartment shapes define more then one compartment group.
- The user experience isn’t changed: The user creates this connections with drag and drop as usual in the generated DSL model designer.
- You can add code to write custom accept rules for this connection.
- You can decide how the information of the source and target entry is stored in the generated Domain Relationship.
- If you want, the whole compartment shape can be the source or target of a connection as well. This will be visualized through a connector from or to the head of the shape.
- When the user deletes the compartment entry, the connection will be automatically deleted, too. (optional)
One of source or target can be a regular shape. E.g. you can create a connector where the source is a compartment entry of a certain compartment shape and the target is a regular geometry or image shape.Since the update (see top of article) both source and target can be a regular shape, too or a base class mapped to a regular and compartment shape.- All other properties, extension points, configuration, visualizations and so on will be used from DSL Tools.
Since I can not change the code of the DSL Tools framework and libraries and I want not change the generated code in your DSL project (this will be overwritten each time you generate it) I started to write a library. To use this library in your DSL project you have to design some elements in your DSL as described by me later on. It is curial to set certain properties or it will not work as expected. Then you have to add come code to your DSL project to extend the partial classes generated by the DSL Tools code generator. To minimize the amount of hand written code I created a code generator as well. With this generator you need only to specify the connection you want to use as a compartment entry connection. Then only one class with three methods is left to be filled with code.
Because of the limitations of the DSL Tools framework and the given extension points not everything is working as someone will expect. But at this moment I can only think of one feature you may miss with my solution:
- Obviously the connector have to start on the left or right side of the shape just besides the corresponding compartment entry. Every time you change (add or remove entries; collapse or expand the shape) or move the compartment shape this connection points will be recalculated.
The routing of the connector may change when this happens, but it will always use the routing algorithm you already know within the DSL editors. To ensure a correct visual presentation of the connector the user MUST NOT change the routing! For every connector under the control of my library the user CANNOT change the routing. – In my opinion this is a small price for the features you can add to the DSL framework.
Upcoming articles
- This article showed the basics of what my library can do.
- In the next article I will give you a brief user guide on how to provide this functionality to your DSL developments. The download of the library, the source code and examples will be there as well.
- The third part gives you a deeper look inside of the library and shows you the way it is working
- In the last part I will explain a way of finding and removing some default commands from the DSL editor.
As a professional software developer you will come one day to the point where you want to generate some source code or text files instead of writing it yourself. I don’t speak about dynamic web pages or big frameworks with code generation. But I bet in some of your last projects you could generate some code, too.
Maybe you do this already with a codegenerator of some kind or by using a scripting language like PHP or Python. Maybe you have reasons not to generate it and write it by yourself. Very often these reasons are bad reasons: you don’t know any adequate tool, you don’t want to buy one or you don’t have the time to learn it.
CodeSmith
The first time I had to generate source code we wanted to generate a library to access the data of an external software in a type safe way with real properties and not throwing around with strings. The input was a xml configuration file of this software.
For this aim we used a freeware code generator named CodeSmith. CodeSmith is a templating engine using C# or Visual Basic as embedded language and a syntax that is very similar to ASP. You can use all .Net framework classes and function within your template, load your own or 3rd party libraries into it and use all these classes as parameter of the template.
A few years later we had to generate code again and discovered that CodeSmith became a company that sells an enhanced standard and professional version of the well known tool. They added a very powerful IDE to create and edit your templates and for the professional version Visual Studio integration and an API to use the engine with your templates in your applications.
After buying the professional version we discovered that we cannot use CodeSmith for our needs. We wanted to build a product that generates code for the end-user on his machine but you are not allowed to distribute the CodeSmith libraries that are needed when you use the API. It took a long time for me to imagine a good usecases for an API like this if I cannot give my software away (or every user of my software has to buy a $399 licence of CodeSmith).
Fortunately the old freeware version was capable of this. It can compile your template to C# code that runs without any external reference or library. You can add this code to your application and that’s it. You do not have the fancy IDE (or you can buy a CodeSmith licence and use the IDE but process your template with the freeware version – the syntax is nearly the same) and maybe not all of the features of the new version but it is free and works. The freeware version is still downloadable on their webpage.
T4 – Text Templating Transformation Toolkit
Visual Studio 2008 (aka. Orcas) comes with its own templating engine that is a free part of every Visual Studio Professional or Team System installation (free as in: you paid for it with your Visual Studio licence). This engine is powerful with C# (or even C# 3.0) and Visual Basic as language and access the whole .Net framework and external assemblies as well.
Unfortunately there is no editor integrated in Visual Studio for the TT files. And even worse, you cannot use the engine out of the box. The DSL Tools use it but if you want to do so, you have to write code and access the public interfaces. But for both drawbacks there are solutions.
You can download a beta version of the T4 Editor. It integrates in Visual Studio (the download for the Beta 2 works fine with the final version of Visual Studio for me, too) and has syntax highlighting for template files. It comes also with some Intellisense, but only for the template syntax and not for code you write inside your templates.
Then there is TTxGen that allows you to use every file you want as input for a template in a Visual Studio solution. It adds a template to the input file and generates the output from the input using the template. It’s really that easy. TTxGen is a very lightweight thing (only 45 kByte for the installer) because it delegates most of the work to the T4 stuff from Microsoft and adds only a few menus to Visual Studio.
I had some trouble with the installation of the February 2008 CTP release. You can find my workaround at the discussions over at MSDN Code Gallery.
For one of my DSL Tools add-on libraries I use TTxGen to generate code from a xml file. Later I will post an article with the source code of the template that can be used as an example.
Today my world changed a little bit. My friend Rob asked me to write a fun application: You answer some questions and it tells you something, fairly simple. But he wanted to put it up his blog… oh-oh… Well… I am not much of a network- or server-guy and he runs a WordPress-blog on a hosted linux maschine, so .NET wouldn’t work. I started thinking about HTML-forms and java-script stuff but I don’t think it works too well with blog-posts and – by the way – I hate that sh*t, so I dropped it after one second.
Flash or Silverlight?
Then I thought about Flash and I guess that’s what you would use for a little game like that. But I don’t have it, I don’t want to buy it (is there a free version?) and I don’t have the time to learn it; although I think it’s pretty cool.
But… wait a moment? What about Silverlight? Isn’t it similar to Flash? I checked it out and I tell you: No, it’s not. Or nor yet. I downloaded the SDK, played a bit, read some pages. But… it’s just not the way you want it to be. Works only with java-script, isn’t easy, needs a lot of learning, doesn’t give you any moments of success. It’s just no fun.
Here comes GOA
So I complained in a chat with Benjamin. "Do I have to write a .NET-to-Flash-converter first?" He said: "There is one." – "WHAT?" – "Yes, I heard about something like that." and a minute later he produced a link to a product called "Goa Winforms". I could hardly believe my eyes. They claim to have an engine to compile .NET-Winform-Code to Flash (.swf) applications.
The basic version is free and comes as a 3MB installer, which integrates the tool smoothly into VS2005. After installing VS comes up with new project-types.
I choose the GOA Application and VS prepares a solution with a new project and a file that looks like a regular C#-file (the extension is .ccs by the way). Okay, let’s start it… Wow! A real Flash-application comes up. So far it’s just an empty form with one button – but it runs. It runs!!!
I think they implemented most of the common gui-components for flash and engineered a compiler that somehow converts C#-Code into an flash-interpretable format. Cool achievement!
Now, let’s design a fancy gui… but… were is the designer? In brief: There is none. You can’t design the gui as you usually would. Ouch. Okay, there is a solution to this ‘issue’, but it hurts and can’t be more than an temporary workaround: Add a Windows-application project to your solution, design your gui and copy the generated code to the GOA-application file.
Done? No. When you try to compile it, you get a bunch of errors and you have to manually delete any usages of properties that have been generated but are not supported by the GOA-version of the corresponding controls.
Now it works and you can continue to write your logic… but that takes us to another annoying point: The integration to Visual Studio could be improved a lot. Syntax-highlighting and intellisense work, but that’s it. Auto-re-indentation doesn’t work, errors and warnings do not all occur in the "Error-List"-Window, and my loved ReSharper didn’t recognize the code; but maybe that’s just because of the extension .css – is there a way to make ReSharper recognize those files as regular C#-files? Please let me know.
That’s was a lot of bleating, but lord don’t let me be misunderstood: It’s works and you get a lot for your effort; I had the fun application for Rob done in three hours (including learning, fighting, reading) and I am happy with it.
There is a lot more to play around with – like dymanic creation of controls – but it should be fine for the most purposes.
GOA vs. Silverlight
As soon as the GOA guys can get rid of the drawbacks, I think this approach has great potential. As long as Silverlight is ugly, "GOA Winforms" is something to really keep an eye on. And Silverlight 2.0 hasn’t proven yet that it can beat Flash. So, let’s see if Microsoft will take
the challenge.
Bonustrack
By the way.. here is the "application". It’s in German and doesn’t do much… but blame Rob for that 🙂
[kml_flashembed movie=”http://www.ticklishtechs.net/wp-content/uploads/2008/02/goaapplication1.swf” height=”270″ width=”350″ /]
Adding properties to Domain Classes at runtime with DSL Tools
Tags: dotnet, dsl, JaDAL, visual studio, vsx3 Comments »
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.
Workaround for Known Issue with TypeConverters in DSL Tools for Visual Studio
Tags: dotnet, dsl, orcas, visual studio, vsx6 Comments »
When I tried to add a Domain Property with a custom TypeConverter to my Domain Class I ran into serious problems. Sometimes it worked and this property was shown correctly in the properties window using the custom TypeConverter, but sometimes not. I was desperately searching for a bug in my code for hours but then I figured out: Each first build after a solution cleanup was working and all other builds not. Even when I start the working build a second time without rebuilding it, the custom TypeConverter was not used.
This looks very much like the number 1.10 (the second 1.10 😉 ) in the known issues list posted on the VSX Team blog:
1.10 TypeConverters and TypeDescriptors are not picked up during the build process or during toolbox initialization.
When adding a custom TypeConverter or TypeDescriptor and then building the DSL, the TypeConvertor or TypeDescriptor is not picked up. The workaround is to rebuild the solution with a clean build.
And as you see: I discovered this workaround for myself, too. But I also found another workaround. I’m not sure if it is working for all situations with the described known issue but if you have the same problem you might want to try it. If it works or not, please comment some feedback here.
I simply used another constructor of the TypeConverterAttribute
. Instead of providing the type information I used the constructor with a string:
// this one does not work:
[TypeConverter(typeof(DynamicPropertiesTypeConverter))]
// this one is not nice, but works:
[TypeConverter(“BenjaminSchroeter.DynamicPropertiesTypeConverter”)]
public class DynamicProperties : ICustomTypeDescriptor
CopySourceAsHtml (CSAH) is a small and nice add-in for Visual Studio 2005 to copy the selected source code html-formatted to the clipboard. This is very useful if you want to paste this code into your blog (as I do here sometimes).
Unfortunately the current version 2.0.0 does not work with Visual Studio 2008 out of the box, but it is very simple to get it running.
The add-ins are located in below documents-folder in a path like this: C:\Users \Benjamin \Documents \Visual Studio 2005 \Addins
. Just copy all files beginning with CopySourceAsHtml*.*
to the corresponding folder for Visual Studio 2008: C:\Users \Benjamin \Documents\ Visual Studio 2008 \Addins
.
Now you have to edit the CopySourceAsHtml.AddIn
file with a text editor: only change at two positions in this short xml-file the Version
value from 8.0
to 9.0
.
After a restart of Visual Studio 2008 you should find the CopySourceAsHtml add-in in the Tools / Add-in menu and of cause in the context menu of the code editor.
Recent Comments