Preventing model elements from being deleted
- Is it possible to stop model elements from being deleted by a user? For example, I have a language model in which I want there to always be one and only one instance of ConceptA. I can ensure that all new diagrams will have an instance of the element when they are created, but how can I then stop the user from deleting the element?
Throwing an exception from withing "OnRemoving" appears to do the trick, but this is very crude, and causes problems since there appear to be times when the designer framework creates and removes elements behind the scenes (e.g. when initializing the toolbox).
Duncan
Odpovědi
- Hi Duncan,
Sorry it's taken a while to get a response on this one, I've been chewing it over for a bit. What it comes down to is that there are (or will be) multiple ways to accomplish this goal, depending on how strictly you want to enforce this in your model, and what you want the user experience to be. I'll outline three approaches below:
1. Strict enforcement in the model. You were on the right track here with the OnRemoving override, but a better place to do this is actually on the relationship which embeds the element you want to prevent removal of in the model. So, if you had an embedding relationship ConceptBHasConceptC connecting ConceptB (parent) and ConceptC (child), you could write the following code in a partial class:
partial class ConceptBHasConceptC
{
public override void OnRemoving()
{
base.OnRemoving();
if (this.ConceptB != null && !this.ConceptB.IsRemoving)
{
throw new InvalidOperationException("Cannot remove Concept C"); // really you'd want to retrieve this message from resources.
}
}
}
This would prevent the ConceptC participating in this relationship from being removed, unless it's parent ConceptB was also being removed (this should alleviate the problems you were seeing before with the designer framework removing elements).
2. Enforcement in the model through constraints. In upcoming releases, we'll be enhancing and providing examples demonstrating use of our constraint framework. This gives you a way to write model constraints in C#, and have them run at a time specified by the DSL author, such as load, save, or when the user chooses a "Validate" menu item on your designer. Constraints which are violated appear as errors or warnings in the error list. This is not as strict as option #1.
3. Enforcement in the Designer UI. Overriding commands using the APIs we have today is tricky, but we are improving that, so in the future you'll be able to implement your own command status handler for the delete command easily, which will allow you to disable the delete command when certain shapes or model elements are selected in the designer (and delegate to the base implementation for everything else).
Hope this is helpful. If you try to implement option #1, let us know if you run into any difficulties. Designers we've written in the past (such as Whitehorse) have generally used strategies #2 and #3, sometimes in combination.
Thanks,
Grayson
Všechny reakce
- Hi Duncan,
Sorry it's taken a while to get a response on this one, I've been chewing it over for a bit. What it comes down to is that there are (or will be) multiple ways to accomplish this goal, depending on how strictly you want to enforce this in your model, and what you want the user experience to be. I'll outline three approaches below:
1. Strict enforcement in the model. You were on the right track here with the OnRemoving override, but a better place to do this is actually on the relationship which embeds the element you want to prevent removal of in the model. So, if you had an embedding relationship ConceptBHasConceptC connecting ConceptB (parent) and ConceptC (child), you could write the following code in a partial class:
partial class ConceptBHasConceptC
{
public override void OnRemoving()
{
base.OnRemoving();
if (this.ConceptB != null && !this.ConceptB.IsRemoving)
{
throw new InvalidOperationException("Cannot remove Concept C"); // really you'd want to retrieve this message from resources.
}
}
}
This would prevent the ConceptC participating in this relationship from being removed, unless it's parent ConceptB was also being removed (this should alleviate the problems you were seeing before with the designer framework removing elements).
2. Enforcement in the model through constraints. In upcoming releases, we'll be enhancing and providing examples demonstrating use of our constraint framework. This gives you a way to write model constraints in C#, and have them run at a time specified by the DSL author, such as load, save, or when the user chooses a "Validate" menu item on your designer. Constraints which are violated appear as errors or warnings in the error list. This is not as strict as option #1.
3. Enforcement in the Designer UI. Overriding commands using the APIs we have today is tricky, but we are improving that, so in the future you'll be able to implement your own command status handler for the delete command easily, which will allow you to disable the delete command when certain shapes or model elements are selected in the designer (and delegate to the base implementation for everything else).
Hope this is helpful. If you try to implement option #1, let us know if you run into any difficulties. Designers we've written in the past (such as Whitehorse) have generally used strategies #2 and #3, sometimes in combination.
Thanks,
Grayson - Thanks Grayson, that's very helpful.
So far I've been using a combination of #1 and #3, with limited success. I'll try option #1 based on the relationship rather than on the element directly.
Being able to specify a custom command status handler for delete would certainly be useful. I'd expect that customising the delete menu behaviour would be a pretty common task, so much so that I was wondering whether the default implementation could be a call to a "bool CanRemove()" method on the shape / element to decide whether or not to display the "delete" menu option.
This might give a neater way for DSL developers to implement domain-specific logic (i.e. in the appropriate model element) that would still give a better user experience by preventing the user from doing an inappropriate action, rather than showing them an error message afterwards. The custom domain logic could be as simple as always returning false for some element types, to complex rules based on the current state of the model.
Thanks again,
Duncan - Hi Duncan,
Probably this is a bit an old issue, but I can't figure out to prevent the element being deleted. I tried Grayson's solution and I also tried deleting rule and throw InvalidOperationException. But the element is still deleted. I also tried RollBack the transaction, but it didn't work either. Can you please give me a clue on this? TIA.
Regards, Hi Herru,
Both adding a deleting rule and overriding the "OnDeleting" method should work (the method name changed from "OnRemoving" to "OnDeleting").
The following two snippents of code both worked in my test language, based on the the Class Diagram template:
Method (1): Override OnRemoving
Code Snippetpartial class ModelRootHasComments
{
protected override void OnDeleting()
{
base.OnDeleting();// Allow the deletion if the parent (i.e. the model root)
// is being deleted.
if (!this.ModelRoot.IsDeleting && this.Comment != null)
{
throw new InvalidOperationException("Cannot delete comments");
}
}
}Method (2): Adding a Deleting Rule
NB rule needs to be registered as usual.
Code Snippet[RuleOn(typeof(ModelRootHasComments), FireTime=TimeToFire.TopLevelCommit)]
public class StopCommentDeletionRule : DeletingRule
{
public override void ElementDeleting(ElementDeletingEventArgs e)
{
if (!e.ModelElement.Store.TransactionManager.CurrentTransaction.IsSerializing)
{
throw new ArgumentException("Cannot delete comments");
}}
}FYI There is also a (rather long) post on how to customize the explorer and diagram context menus to remove the "Delete" command at http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=780425&SiteID=1.
I'm not sure why these aren't working for you: is your rule being fired at all?
Duncan
- Thanks, Duncan.
Your Method (2) works perfectly to me now. My previous rule was registered, and fired properly.
For Method (1), I did basically the same as you wrote there. I was aware with the changes. Here is my code for the OnDeleting():
public partial class ModuleHasPorts
{
protected override void OnDeleting()
{
base.OnDeleting();
try
{
if (this.Module is CompositeModule)
throw new InvalidOperationException("Cannot delete.");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
It showed the message, but the port was still deleted after. Thanks for the response,
Regards, No problem.
The problem with your method (1) is that you are immediately catching your own exception, so it isn't propagating up the call stack and causing the transaction to be aborted. If you just take out the try-catch block, it should work. You don't need to explicitly display the error message using MessageBox.Show - this will be done for you.
Regards,
Duncan
- Oh, I see....
First I did not write the try-catch block. But it gave me warning that my exception is unhandled. and I thought this could be a problem. Anyway, many thanks Duncan!
Regards, I wrote a small “library” to control the deletion of Model Elements by a function you can implement. The Delete command of the graphical DSL editor and the Model Explorer will be removed if the user is not allowed to delete the Model Element. See http://www.ticklishtechs.net/2008/05/15/preventing-model-elements-from-being-deleted/ for more information.
Benjamin

