Using “AddRule” problem: how we can distinguish between diagram loading situation and user d
Hello!
How we can define aAddRuleto fire only when user drag & drop a domain class into diagram via toolbar?
I’m trying to simplify my DSL’s developer tasks while he drags a domain class into diagram (e.g. add related domain classes, …) , for this propose the suggestion is to using “AddRule”, but I have a simple problem with AddRule: when I use this technique, my AddRule class is called whenever the file (diagram) open and load! How can I bypass my AddRule when diagram is opened?
Is there any way to check is current ModelElemet new or exist (in “ElementAddedEventArgs” of “ElementAdded” at “AddRule”) ?
Thanks,
H. Kavousi
Check Transaction.IsSerializing.
public override void ElementAdded(ElementAddedEventArgs e)
{
base.ElementAdded(e);
ModelClassProperty property = e.ModelElement as ModelClassProperty;
if (property != null)
{
Transaction transaction = property.Store.TransactionManager.CurrentTransaction;
if (transaction != null && !transaction.IsSerializing)
//Handle
}
}
Hi;
Great, and a clever approach, tanks a lot Gokhan; it worked properly J
But yet I have a problem with “AddRule”: this rule is called in recursive style! Means when I add an element in AddRule handler, this cause the AddRule call again, and code fall into an infinite loop! I can’t stop and or bring to an end this loop, because there is no method for that. Is there any way to handle this problem?
And also tanks again for any reply.
Hi,
The transaction context object has a property bag that you can add custom data to, so you could use this to keep track of the model elements you have created in code and not do anything when the add rule fires for these elements. The following code shows how you could do this.
///
<summary>///
Rule to add a second comment whenever a comment is added///
to a model///
</summary>[
RuleOn(typeof(Comment), FireTime=TimeToFire.TopLevelCommit)]internal
sealed class AddCommentRule : AddRule{
public override void ElementAdded(ElementAddedEventArgs e){
// Get a ref to the top-level transactionTransaction outerTransaction = e.ModelElement.Store.TransactionManager.CurrentTransaction.TopLevelTransaction;// Don't do anything on deserializationif (outerTransaction.IsSerializing) { return; }// Transactions have a ContextInfo dictionary to which you can // add your own data items. We will add a list that contains the
// items that we have manually created that we don't want// to run the add rule for.// Name of our data itemconst string createdItemsKeyName = "createdItems";// List of items that we created manuallyList<ModelElement> createdItems;// Get the list of added items for this transaction, or create it// if it does not exist.if (outerTransaction.Context.ContextInfo.ContainsKey(createdItemsKeyName)){
createdItems = (
List<ModelElement>)outerTransaction.Context.ContextInfo[createdItemsKeyName];}
else{
createdItems =
new List<ModelElement>();outerTransaction.Context.ContextInfo.Add(createdItemsKeyName, createdItems);
}
// Check whether the current model element is one we created in codeif (createdItems.Contains(e.ModelElement)){
// Comment was created in code - do nothing}
else{
// Comment not created in code - created anotherComment newComment = new Comment(e.ModelElement.Store);newComment.Text =
"Created comment";// Associate the new comment with the model9Comment comment = e.ModelElement as Comment;comment.ModelRoot.Comments.Add(newComment);
// Store a ref to the new model element in the // transaction contextcreatedItems.Add(newComment);
}
}
}
However, because this is a rule, it will fire regardless of how the new items are added to the model; via the toolbox, the model explorer, or via code. This may or may not be the behaviour you want.
If you just wanted to add multiple items when dragging from the toolbox, you could override the "CreateElementToolPrototype" method of the model element instead. This method returns a blueprint of the items that will be merged into the model when dragging from the toolbox. The default generated version of the method just adds a single item, but you could change this to add multiple items.
If you want to add two items just when dragging from the toolbox or when clicking "Add" in the model explorer, then you could override the "MergeConfigure" of the model element to create the new item and link it to the model.
Duncan
Hi Duncan and Everyone,
Duncan said:
However, because this is a rule, it will fire regardless of how the new items are added to the model; via the toolbox, the model explorer, or via code. This may or may not be the behaviour you want.
How can I then apply this rule only if the element is added from toolbox not from the code? If this question is a repost, please point me to the related link. TIA.
NB: I changed the Subject Title a little bit, so it fits my problem - if you don't mind.
Regards,
Herru,
The example rule above does what you want: the rule always fires, but it looks for extra information stored in the context of the transaction to detect model elements that it should ignore.
If you add the above rule to a new language based on the Class Diagram, then every time you add a new comment (either from the toolbox or through the model explorer), the rule will add a second comment. The following code snippet shows how you would add a new comment in code that the rule would ignore.
Code Snippet
// Code assumes that it is called from the ClassDiagram domain class
private void AddCommentInCode()
{
using (Transaction t = this.Store.TransactionManager.BeginTransaction("Add comment in code"))
{
// Create a new comment and link it to the model
Comment newComment = new Comment(this.Store);
newComment.Text = "Comment added in code";
((ModelRoot)this.ModelElement).Comments.Add(newComment);
// Record the fact that we created this comment in
// code
List<ModelElement> createdItems = new List<ModelElement>();
t.Context.ContextInfo.Add("createdItems", createdItems);
createdItems.Add(newComment);
t.Commit();
}
}
Duncan
The Propertybag in the TransactoinContext is fine. However I wanted to do better and did this:
Code Snippet
internal class DragDropTransactionContext : DslModeling.TransactionContext
{
public DragDropTransactionContext(DiagramDragEventArgs dragArgs)
{
this.dragArgs = dragArgs;
}
public DiagramDragEventArgs dragArgs;
}
public
partial class MyDiagram
{
public override void OnDragDrop(DiagramDragEventArgs e)
{
using (DslModeling::Transaction tx = Store.TransactionManager.BeginTransaction("Drag & Drop", false, new DragDropTransactionContext(e))) // the typesafe and nice way to convey the dragargs into the transaction
In words, I derived from TransactionContext and passed it to BeginTransaction(....., TransactionContext c);
Then, in the rule I wanted to do this:
Code Snippet
if
(Store.TransactionManager.CurrentTransaction.Context
is DragDropTransactionContext)
{ ....
Oh sheer beauty of my code, but this does not work. I found out why: BeginTransaction COPIES the bag from my DragDropTransactionContext instance into its own created context. I think this implementation is unlucky and unexpected. So my suggestion and wish is:
TransactionManager.BeginTransaction( , , , TransactionContext)
should be changed such that a given context is used as the context of the transaction.
Love to hear comments.
And my honest respect for the dsl tools team, I just love it.
(waiting for the book to ship to my office)