How to add a custom rule that listens on "delete" events?
How can I add a custom rule (or similar mechanism) that fires custom logic in case I delete a class in the domain model?In my example I have a domain model from which I create various ASPs and C# classes (for each domain class various implementation artifacts are being generated). For consistency reasons, I would like to add a “delete” event listener in the domain model. So, whenever a domain class is deleted, I would like to delete the derived implementation artifacts. Traceability is not the issue, only having a listener to a “delete” event. How would I do that?
Thanks,
Alex
[930 byte] By [
AlexWied] at [2007-12-16]
Hi Alex,
First, our modeling APIs make a distinction between rules and events. Rules are intended for cases where you want to change state stored in the model in response to a change in some other part of the model; one example of this is creating shapes which appear on the design surface when corresponding elements from the domain model are created (note that in addition to the domain model, our tools generate a diagram model for your DSL, and diagram state is persisted in this model). One key feature of rules is that they do not fire during undo/redo; this makes sense since they're only intended to change the state of the model, and all model changes are automatically tracked by the undo/redo infrastructure. Also, rules fire before a transaction is commited to the model.
Events, on the other hand, are intended to change state external to the model (this sounds like your case). They are raised during undo/redo, and they occur only after successful transaction commit. Here's a code snippet showing how to listen for delete of a domain class called WizardPage:
...
Store store = <obtain store reference based on context you're in, might be from the current selection, or it could be a cached value>
store.EventManagerDirectory.ElementRemoved.Add(store.MetaDataDirectory.FindMetaClass(WizardPage.MetaClassGuid),
new ElementRemovedEventHandler(OnWizardPageRemoved));...
private
void OnWizardPageRemoved(Object sender, ElementRemovedEventArgs e)
{
// you can get access to the element that was removed via the event args,
// and do whatever you need to do with it here.
}
You can listen to other events such as add and property change in a similar manner. One thing to be aware of when using remove events is that by the time your event handler is been called, you won't be able to traverse any links in the model which were connected to the removed element. There are some strategies for getting around this, such as listening for removal of the relationship class you're interested in rather than the domain class. I won't go into details, but if your scenario requires it, let me know.
Thanks,
Grayson
Thanks, Grayson. While the approach is clear, where in the generated code (e.g. sample ConceptA/ConceptB project) would I inject my event handler and at which point would I need to initialize it?
A good spot might be in an override of the XXXDocData.OnDocumentLoaded method. There is one XXXDocData instance per file you have open in your designer, and OnDocumentLoaded is called when you first open the file, after the Store has been created and all data loaded into it. So you would make the code above part of your derived DocData class. Code for this lives in the Designer project, in the Shell subfolder. The override might look like this:
protected
override void OnDocumentLoaded(EventArgs e)
{
base.OnDocumentLoaded(e);
Store store = this.Store;
// attach event handler using code as above.
}The one tricky part is that you'll want to make the DocData class partial, so you can put this definition in another partial class in a separate file, so it doesn't get overwritten by further code generation. To do that, you currently have to modify the DocData code generation template. If you open XXXDocData.dslddt in your Designer project, you'll notice it references an include file, DocData.dslddi. You can find this file under Program Files\Microsoft Visual Studio 8\Common7\IDE\DSLTools\TextTemplates\Designer\Shell\DocData.dslddi. You can either change the class definition directly in the include template, or copy the contents of the template into XXXDocData.dslddt in your Designer project (and remove the DocData.dslddi include line).
In future releases we're planning to partialize our generated classes by default so you don't have to do this.
Thanks,
Grayson
I'm once again impressed about the beta release and how well architected everything is. Brilliant.
This works and is exactly what I was looking for.
Thanks for the follow-up. Much appreciated.
Alex
How can i do this in new dsl tools version?
I rule gets fired after modelelement/connector is deleted, is there anyway to capture it before it is deleted and get all the properties of that modelelement? something to set to fire before delete?
thanks
There is a base class DeletingRule to capture ModelElements, before they are actually deleted. I'd use that for the purpose. Maybe even with TimeToFire.Inline to capture it even before the Transaction commits. Your Rule must derive from DeletingRule, set the RuleOn Attribute appropriately and publish the Rule in GetCustomDomainModelTypes.
Michael
great, that worked. Thanks for your help. Do you know if there is way to cancel delete, that was inititated by user but still programatically allow it.
You can do that yourself. Just add a boolean (domain) property to the element in question and set it to true before you programmatically delete it. Set the Browsable to false and UI Read Only to true. In your Rule you can check if the boolean property is set and if not throw an InvalidOperationException to prevent the delete operation.