In the DSL Tools Customization Samples & Guide there’s some examples that use MDF rules to spot a change and react to it. (1) In the class diagram sample, attributes are displayed as a string like “aName : SomeType”, and in the properties grid, the name and type are in separate properties. All three forms are accessible to code, for example in any generator template. A rule fires when any of the properties changes, and propagates the change to the others. (2) In the activity diagram sample, rules are used to snap the Sync Bar shape always to be a constant width or height, no matter how the user pulls the corners. (3) There is a (rather primitive!) container shape in the activities and use-case samples, vaguely reminiscent of a swimlane and a system boundary respectively. The shape manages the trick of always remaining behind other shapes; and when it moves, so do any shapes within its boundaries.
In all these cases, you’ll notice that if you undo a change, the whole thing undoes in one go: you don’t, for example, have to first undo the readjustment to the shape, and then another undo for the original change. This is because the rule framework is part of the MDF transaction system. (MDF is the framework underlying the DSL Tools and other graphical designers in Visual Studio.) All the properties of an object – size and color of a shape, the value properties you’ve specified in your language, etc – are accessed through a framework that knows about the rules, and schedules firings as appropriate. Changes are always made within a transaction, and when the transaction is committed, the rules fire. All the firings are counted as being part of the transaction, which is the unit of undo/redo.
As you can see from the samples, rules are attached to specific types (such as SyncBarShape), using a class attribute. Each rule is a class inheriting from one of a few abstract parents. The most useful of these are ChangeRule, which monitors property changes, AddRule, RemovingRule (about to delete) and RemovedRule (after deletion). The main thing to be careful of is infinite reiterations: if a rule makes a change, it the appropriate rule will be scheduled on the firing list. However, ChangeRules only fire if there is an actual change of value – a set operation by itself doesn’t fire. So in a rule that re-adjusts the same property that caused the firing, you just have to make sure things always converge rapidly on a stable value that gets adjusted to itself.
- (If you’re fascinated by scrolling through that long list you get after typing “override” in one of your language classes, you’ll notice some methods called “On…Changed”. You can override these to get an effect like the rules, but it doesn’t work so well. The On… method is called after the end of the transaction, so you have to open another transaction to make your adjustments. Then of course, the user gets a horrible two-stage undo (which might sometimes be what you want, I suppose). And the biggest pitfall is that you mustn’t make changes if the original change is part of an undo or redo – so if you decide you really must use this method, bracket the code with “if (!Store.InUndoRedoOrRollback)…” But it’s really not recommended, and may not work in future versions.)