Hi,
[Background]
I'm working on a DSL called XmlBusinessObjects that functions similar to the strongly typed DataSet designer (i.e. it displays tables with fields and relationships between them) and generates code for a very specialized data access layer. I also want the DSL to be able to work with the Server Explorer so new tables can just be dropped onto the diagram, like with the DataSet designer.
From exploring the modeling assemblies provided with the DSL tools, I've found that the "design surface" is the DiagramClientView control. Although you appear to have additional Drag/Drop support built on top of the diagramming architecture, I used the standard OLE drag/drop wrapped by Windows Forms with the DiagramClientView.DragOver and DiagramClientView.DragDrop events. (Aside: Is not calling "base.OnDragEnter();" in DiagramClientView.OnDragEnter an intentional decision to prevent people from dragging custom objects onto the diagram ) I've found wiring these events in my diagram's OnAssociated method works well, although there may be a more appropriate location.
The DSRefNavigator class then interprets the Server Explorer's selection using the "DSRef Architecture," as vaguely alluded to in the documentation, and creates similar ModelElements while positioning the corresponding Shapes. This is where things get tricky. If I just create the ModelElements and commit the transaction, they all appear stacked on top of each other in the upper left corner. It's definitely awkward if the user intentionally dragged their selection to a blank area of the diagram. Therefore I want to both add the elements and position their shapes accordingly.
[Question]
How do I get a new ModelElement's corresponding Shape
[My (problematic) approach]
As you can see in the OnDragDrop method, I'm iterating over the NestedChildShapes collection of my diagram to find a Shape with a matching ModelElement. From what I gather, this corresponding shape is not added to NestedChildShapes until the top level transaction has committed. I would like creating the elements and positioning their shapes to be handled as a single transaction so they can be Undone together (and my attempt to wrap both of them in another transaction was unsuccessful, leading to my "until the top level transaction has committed" observation). Is this possible
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.VisualStudio.Data.Interop;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using CompanyName.BusinessObjects.DSL.DomainModel;
using CompanyName.BusinessObjects.DSL.Designer.Diagram;
namespace CompanyName.BusinessObjects.DSL.Designer
{
/// <summary>
/// Add drag/drop support for data sources from the Server Explorer (using
/// the DSRef architecture via interop) to the XML Business Objects Diagram
/// </summary>
public partial class XmlBusinessObjectsDiagram
{
/// <summary>
/// Initialize the drag/drop when the diagram is associated with a view (i.e. when
/// a model is loaded from a file)
/// </summary>
protected override void OnAssociated(DiagramAssociationEventArgs e)
{
base.OnAssociated(e);
// Ensure we have a view
if (e.DiagramView == null || e.DiagramView.DiagramClientView == null)
return;
// Wireup the drag/drop support
Control ctrl = e.DiagramView.DiagramClientView;
ctrl.AllowDrop = true;
ctrl.DragOver += new DragEventHandler(OnDragOver);
ctrl.DragDrop += new DragEventHandler(OnDragDrop);
}
/// <summary>
/// OnDragOver is used to inform OLE that the DiagramClientView associated with
/// the current diagram is willing to accept the DSRef data format. Additionally,
/// we enforce that only tables can be copied onto the diagram. We would
/// normally do this in OnDragEnter as it's the appropriate event, but the
/// DiagramClientView does not properly implement OnDragEnter by calling its
/// base method (i.e. any event we wire up to DragEnter would never fire)
/// </summary>
private void OnDragOver(object sender, DragEventArgs e)
{
// Check if the data present is in the DSRef format
if (e.Data.GetDataPresent(DSRefNavigator.DataSourceReferenceFormat))
{
try
{
// Create a navigator for the DSRef Consumer (and dispose it when finished)
using (DSRefNavigator navigator = new DSRefNavigator(e.Data.GetData(
DSRefNavigator.DataSourceReferenceFormat) as Stream))
// Only allow a copy if they selected table leaf nodes (we of
// course don't set the Effect to None if not as the base classes
// do their own drag/drop manipulation on other data formats)
if (navigator.ContainsOnlyTables)
e.Effect = DragDropEffects.Copy;
}
catch { }
}
}
/// <summary>
/// OnDragDrop is used to create tables corresponding to the selection dragged
/// from the Server Explorer
/// </summary>
private void OnDragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
// Check if the data present is in the DSRef format
if (e.Data.GetDataPresent(DSRefNavigator.DataSourceReferenceFormat))
{
try
{
// Create a navigator for the DSRef Consumer (and dispose it when finished)
using (DSRefNavigator navigator = new DSRefNavigator(e.Data.GetData(
DSRefNavigator.DataSourceReferenceFormat) as Stream))
{
// Get the root element where we'll add the tables
BusinessObjectModel model =
Utility.GetBusinessObjectModel(ElementDirectory);
if (model == null)
return;
// Get the location the user intended to drop the tables
DiagramClientView control = sender as DiagramClientView;
PointD location = control.DeviceToWorld(control.PointToClient(
new Point(e.X, e.Y)));
// Collection to track added tables so we can position them later
List<Table> addedTables = new List<Table>();
// Create a transaction to add the tables (note that
// we create seperate transactions for adding the table
// and setting its position. It seems that we can't
// find the Table's corresponding TableShape in the
// NestedChildShapes collection until after the
// transaction has committed)
using (Transaction txAdd =
model.Store.TransactionManager.BeginTransaction("Add tables"))
{
// Get the table names from the Server Explorer selection
foreach (string name in navigator.Tables)
{
// Create the table and add it to the model
Table newTable = Table.CreateTable(model.Store);
newTable.Name = name;
newTable.BusinessObjectModel = model;
addedTables.Add(newTable);
}
// Commit the transaction and add the tables to the model
txAdd.Commit();
}
// Create a transaction to move the newly created tables
// to the location the user selected
using (Transaction txMove =
model.Store.TransactionManager.BeginTransaction("Move items"))
{
// Height offset used to appropriately stack the
// selected tables in a line descending from the
// original drop location
double offset = 0;
// Loop through the tables we just added
foreach (Table newTable in addedTables)
{
// Look in the NestedChildShapes collection
// for the new table's corresponding TableShape
// by checking the model element
foreach (object obj in NestedChildShapes)
{
TableShape shape = obj as TableShape;
if (shape != null && shape.ModelElement == newTable)
{
// Expand the shape and move it to its new position
shape.IsExpanded = true;
shape.AbsoluteBounds = new RectangleD(new PointD(location.X,
location.Y + offset), shape.Size);
// Adjust the height offset for the size of the shape
// (I'm assuming that the DefaultContainerMargin
// provides for a decent spacing between the shapes)
offset += shape.Size.Height + DefaultContainerMargin.Height;
}
}
}
// Commit the transaction
txMove.Commit();
}
}
}
catch { }
}
}
/// <summary>
/// The DSRefNavigator is used to enumerate the DSRef Consumer data source
/// and provide details about the Server Explorer's selection.
/// </summary>
private class DSRefNavigator : IDisposable
{
/// <summary>
/// Clipboard format of the data source reference objects in the
/// Server Explorer window of Visual Studio
/// </summary>
public static readonly string DataSourceReferenceFormat = "CF_DSREF";
/// <summary>
/// Root node of the DSRef Consumer selection result tree
/// </summary>
private static readonly IntPtr RootNode = IntPtr.Zero;
/// <summary>
/// Create a COM stream from a pointer in unmanaged memory
/// </summary>
[DllImport("ole32.dll")]
private static extern int CreateStreamOnHGlobal(IntPtr ptr, bool delete,
ref IntPtr stream);
/// <summary>
/// Load an OLE object from a COM stream
/// </summary>
[DllImport("ole32.dll")]
private static extern int OleLoadFromStream(IntPtr stream, byte[] iid,
ref IntPtr obj);
/// <summary>
/// DSRef Consumer that provides a tree-like structure of the selected items
/// </summary>
private IDSRefConsumer _consumer;
/// <summary>
/// Constructor to create the DSRef navigator from a stream
/// </summary>
/// <param name="data">Stream containing the DSRef consumer</param>
public DSRefNavigator(Stream data)
{
_consumer = null;
// Pointers to unmanaged resources
IntPtr ptr = IntPtr.Zero;
IntPtr stream = IntPtr.Zero;
IntPtr native = IntPtr.Zero;
// Get a reference to the DSRef Consumer from the stream
try
{
// Read the stream containing the DSRef Consumer
byte[] buffer = new byte[data.Length];
data.Seek(0, SeekOrigin.Begin);
data.Read(buffer, 0, buffer.Length);
// Copy the DSRef Consumer to native memory
native = Marshal.AllocHGlobal(buffer.Length);
Marshal.Copy(buffer, 0, native, buffer.Length);
// Create a COM stream from the memory
int result = CreateStreamOnHGlobal(native, false, ref stream);
Marshal.ThrowExceptionForHR(result);
// Load the DSRef Consumer from the COM stream
result = OleLoadFromStream(stream,
typeof(IDSRefConsumer).GUID.ToByteArray(), ref ptr);
Marshal.ThrowExceptionForHR(result);
// Get the DSRef consumer
_consumer = Marshal.GetObjectForIUnknown(ptr) as IDSRefConsumer;
}
finally
{
// Release all of the unmanaged resources
Marshal.Release(ptr);
Marshal.Release(stream);
Marshal.Release(native);
}
}
/// <summary>
/// Dispose resources used by the navigator
/// </summary>
public void Dispose()
{
// Release the reference to the DSRef Consumer
if (_consumer != null)
Marshal.ReleaseComObject(_consumer);
}
/// <summary>
/// Indicate whether or not the selection represented by this DSRef Consumer
/// contains only tables as leaf nodes
/// </summary>
public bool ContainsOnlyTables
{
get
{
if (_consumer != null)
{
// Determine if one of the leaf nodes has a table by querying
// the type of the root node
try
{
return (_consumer.GetType(RootNode) &
__DSREFTYPE.DSREFTYPE_TABLE) == __DSREFTYPE.DSREFTYPE_TABLE;
}
catch { }
}
return false;
}
}
/// <summary>
/// Enumerate the names of the tables in the selection represented by
/// the DSRef Consumer
/// </summary>
public IEnumerable<string> Tables
{
get
{
// TODO: Include data source information at some point
foreach (IntPtr child in ChildNodes)
{
__DSREFTYPE type = _consumer.GetType(child);
if ((type & __DSREFTYPE.DSREFTYPE_TABLE) ==
__DSREFTYPE.DSREFTYPE_TABLE &&
(type & __DSREFTYPE.DSREFTYPE_HASNAME) ==
__DSREFTYPE.DSREFTYPE_HASNAME)
yield return _consumer.GetName(child);
}
}
}
/// <summary>
/// Enumerate all of the child nodes in the selection
/// </summary>
/// <returns>Iterator for the child nodes in the selection</returns>
private IEnumerable<IntPtr> ChildNodes
{
get
{
// Instead of recursing, we'll just continually iterate over the queue
Queue<IntPtr> parents = new Queue<IntPtr>();
parents.Enqueue(RootNode);
while (parents.Count > 0)
{
IntPtr parent = parents.Dequeue();
IntPtr child = _consumer.GetFirstChildNode(parent);
while (child != IntPtr.Zero)
{
yield return child;
parents.Enqueue(child);
child = _consumer.GetNextSiblingNode(child);
}
}
}
}
/// <summary>
/// Display the Server Explorer selection represented by the DSRef
/// Consumer in a non-modal form. This should be implemented as a
/// Debugger Visualizer but I'm too lazy to handle serializing the
/// COM object.
/// </summary>
[Conditional("DEBUG")]
public void ShowVisualizer()
{
(new DSRefNavigatorVisualizer(_consumer)).Show();
}
/// <summary>
/// Display the selection from the Server Explorer represented by the
/// DSRef Consumer in a TreeView. We include both the name of the node
/// as well as type and structural information.
/// </summary>
private class DSRefNavigatorVisualizer : Form
{
/// <summary>
/// Constructor to initialize and display the form
/// </summary>
/// <param name="consumer">DSRef Consumer with selection to display</param>
public DSRefNavigatorVisualizer(IDSRefConsumer consumer)
{
// Add the tree to the form
Text = "DSRef Consumer Selection Debug Display";
TreeView tree = new TreeView();
tree.Dock = DockStyle.Fill;
Controls.Add(tree);
tree.BeginUpdate();
// Create the root node and build the tree
TreeNode root = new TreeNode();
BuildTree(consumer, DSRefNavigator.RootNode, root);
// Display the debuging form
tree.Nodes.Add(root);
tree.ExpandAll();
tree.EndUpdate();
}
/// <summary>
/// Build a tree of the DSRef Consumer's selection starting with the provided
/// node in the DSRef Consumer and node in the TreeView
/// </summary>
/// <param name="consumer">DSRef Consumer containing the selection</param>
/// <param name="node">Current node in the DSRef Consumer</param>
/// <param name="treeNode">Node in the tree</param>
private void BuildTree(IDSRefConsumer consumer, IntPtr node,
TreeNode treeNode)
{
// Copy the properties for the current node given its type
treeNode.Text = "";
__DSREFTYPE type = consumer.GetType(node);
if ((type & __DSREFTYPE.DSREFTYPE_HASNAME) ==
__DSREFTYPE.DSREFTYPE_HASNAME)
treeNode.Text = string.Format("{0} ", consumer.GetName(node));
treeNode.Text += string.Format("(Type: {0}", GetNodeTypes(type));
string structure = GetNodeStructure(type);
if (!string.IsNullOrEmpty(structure))
treeNode.Text += string.Format(" || Structure: {0}", structure);
if ((type & __DSREFTYPE.DSREFTYPE_HASOWNER) ==
__DSREFTYPE.DSREFTYPE_HASOWNER)
treeNode.Text += string.Format(" || Owner: {0}", consumer.GetOwner(node));
treeNode.Text += ")";
// Iterate over the children
IntPtr child = consumer.GetFirstChildNode(node);
while (child != IntPtr.Zero)
{
// Create a tree node for the child and set its properties
TreeNode next = new TreeNode();
treeNode.Nodes.Add(next);
BuildTree(consumer, child, next);
// Move to the next child
child = consumer.GetNextSiblingNode(child);
}
}
/// <summary>
/// Convert the type of a node to a string
/// </summary>
/// <param name="type">Type of a DSRef node</param>
/// <returns>String representation of the node type</returns>
private string GetNodeTypes(__DSREFTYPE type)
{
// Convert the flag enumeration to a set of legible strings
List<string> names = new List<string>();
if ((type & __DSREFTYPE.DSREFTYPE_COLLECTION) ==
__DSREFTYPE.DSREFTYPE_COLLECTION)
names.Add("Collection");
if ((type & __DSREFTYPE.DSREFTYPE_MULTIPLE) ==
__DSREFTYPE.DSREFTYPE_MULTIPLE)
names.Add("Multiple Selection");
if ((type & __DSREFTYPE.DSREFTYPE_MIXED) ==
_DSREFTYPE.DSREFTYPE_MIXED)
names.Add("Collecion of Multiple Selections");
if ((type & __DSREFTYPE.DSREFTYPE_DATABASE) ==
_DSREFTYPE.DSREFTYPE_DATABASE)
names.Add("Database");
if ((type & __DSREFTYPE.DSREFTYPE_DATASOURCEROOT) ==
_DSREFTYPE.DSREFTYPE_DATASOURCEROOT)
names.Add("Data Source Root");
if ((type & __DSREFTYPE.DSREFTYPE_EXTENDED) ==
_DSREFTYPE.DSREFTYPE_EXTENDED)
names.Add("[Extended]");
if ((type & __DSREFTYPE.DSREFTYPE_FIELD) ==
_DSREFTYPE.DSREFTYPE_FIELD)
names.Add("Field");
if ((type & __DSREFTYPE.DSREFTYPE_FUNCTION) ==
_DSREFTYPE.DSREFTYPE_FUNCTION)
names.Add("Function");
if ((type & __DSREFTYPE.DSREFTYPE_INDEX) ==
_DSREFTYPE.DSREFTYPE_INDEX)
names.Add("Index");
if ((type & __DSREFTYPE.DSREFTYPE_PACKAGE) ==
_DSREFTYPE.DSREFTYPE_PACKAGE)
names.Add("Package");
if ((type & __DSREFTYPE.DSREFTYPE_PACKAGEBODY) ==
_DSREFTYPE.DSREFTYPE_PACKAGEBODY)
names.Add("Package Body");
if ((type & __DSREFTYPE.DSREFTYPE_QUERY) ==
_DSREFTYPE.DSREFTYPE_QUERY)
names.Add("Query");
if ((type & __DSREFTYPE.DSREFTYPE_RELATIONSHIP) ==
_DSREFTYPE.DSREFTYPE_RELATIONSHIP)
names.Add("Relationship");
if ((type & __DSREFTYPE.DSREFTYPE_SCHEMADIAGRAM) ==
_DSREFTYPE.DSREFTYPE_SCHEMADIAGRAM)
names.Add("Schema Diagram");
if ((type & __DSREFTYPE.DSREFTYPE_STOREDPROCEDURE) ==
_DSREFTYPE.DSREFTYPE_STOREDPROCEDURE)
names.Add("Stored Procedure");
if ((type & __DSREFTYPE.DSREFTYPE_SYNONYM) ==
_DSREFTYPE.DSREFTYPE_SYNONYM)
names.Add("Synonym");
if ((type & __DSREFTYPE.DSREFTYPE_TABLE) ==
_DSREFTYPE.DSREFTYPE_TABLE)
names.Add("Table");
if ((type & __DSREFTYPE.DSREFTYPE_TRIGGER) ==
_DSREFTYPE.DSREFTYPE_TRIGGER)
names.Add("Trigger");
if ((type & __DSREFTYPE.DSREFTYPE_USERDEFINEDTYPE) ==
_DSREFTYPE.DSREFTYPE_USERDEFINEDTYPE)
names.Add("User Defined Type");
if ((type & __DSREFTYPE.DSREFTYPE_VIEW) ==
_DSREFTYPE.DSREFTYPE_VIEW)
names.Add("View");
if ((type & __DSREFTYPE.DSREFTYPE_VIEWINDEX) ==
_DSREFTYPE.DSREFTYPE_VIEWINDEX)
names.Add("View Index");
if ((type & __DSREFTYPE.DSREFTYPE_VIEWTRIGGER) ==
_DSREFTYPE.DSREFTYPE_VIEWTRIGGER)
names.Add("View Trigger");
return string.Join(", ", names.ToArray());
}
/// <summary>
/// Convert the node structure to a string. Note that these properties are
/// not intended to be used by a normal application and are not necessarily
/// stable. They are only provided to aid in debugging.
/// </summary>
/// <param name="type">DSRef node type</param>
/// <returns>Description of the structure</returns>
private string GetNodeStructure(__DSREFTYPE type)
{
// Convert the flag enumeration to a set of legible strings
List<string> structure = new List<string>();
if ((type & __DSREFTYPE.DSREFTYPE_NODE) ==
__DSREFTYPE.DSREFTYPE_NODE)
structure.Add("Node");
if ((type & __DSREFTYPE.DSREFTYPE_HASNAME) ==
_DSREFTYPE.DSREFTYPE_HASNAME)
structure.Add("Name");
if ((type & __DSREFTYPE.DSREFTYPE_HASMONIKER) ==
_DSREFTYPE.DSREFTYPE_HASMONIKER)
structure.Add("Moniker");
if ((type & __DSREFTYPE.DSREFTYPE_HASOWNER) ==
_DSREFTYPE.DSREFTYPE_HASOWNER)
structure.Add("Owner");
if ((type & __DSREFTYPE.DSREFTYPE_HASPROP) ==
__DSREFTYPE.DSREFTYPE_HASPROP)
structure.Add("Additional Properties");
if ((type & __DSREFTYPE.DSREFTYPE_HASFIRSTCHILD) ==
__DSREFTYPE.DSREFTYPE_HASFIRSTCHILD)
structure.Add("Children");
if ((type & __DSREFTYPE.DSREFTYPE_HASNEXTSIBLING) ==
__DSREFTYPE.DSREFTYPE_HASNEXTSIBLING)
structure.Add("Sibling");
return string.Join(", ", structure.ToArray());
}
}
}
}
}
Thanks,
Ted Glaza

How do I get a new ModelElement's corresponding Shape?
vdh
Ted,
I don't have any definitive answers, but I can give you a couple of pointers:
1) the "modelElement.AssociatedPresentationElements" property gives a list of all of the shapes for a particular model element.
2) adding and positioning a shape in a single transaction: I had exactly the same problem. The way I got round it is ugly but works - explicitly create the new shape element and associate it with the newly-created model element. You can then position the shape inside the same transaction.
e.g.
// NB must be in a transaction
// Explicitly create a new shape
MyShape newShape = MyShape.CreateMyShape(newModelElement.Store);
// Associate the shape with the model element
newShape.Associated(newModelElement);
// Add the new shape to the diagram
currentDiagram.NestedChildShapes.Add(newShape);
// Position the new shape
newShape.AbsoluteBounds = ... etc
An alternative might be:
ShapeElement newShape = currentDiagram.FixupChildShapes(newModelElement);
This appears to create the new shape element and associate it correctly. The downside is that the "OnChildConfiguring" method then appears to called twice for the new shape which can cause an exception, so you'd need to find a way round this (or a better way of hooking into the view fixup process).
Cheers, Duncan
Dave in Colorado
Hi,
Thanks for the quick replies.
Duncan - your first approach works perfectly for the scenario I had in mind. For whatever reason, I thought there would be problems trying to create the shapes myself as it happens behind the scenes.
Pedro - I didn't know you can apply rules to things other than model elements. This looks like a powerful way to handle all layout in the diagram. I think it might be a good answer to the post about fixing the layout as well (http://forums.microsoft.com/MSDN/ShowPost.aspx PostID=259315).
Thanks,
Ted
BSure
Yes, I think you're right. I'll cross link a response there.
And, you are right -- rules only work on ModelElements. This rule isn't actually on a shape, it's actually on the creation of a relationship (shape -> diagram), which is an ElementLink and derives from ModelElement.
However, the Shape class also derives from ModelElement (way down the line), so you can have rules on them as well. Pretty sneaky, isn't it
And, it does allow you to do powerful things with shapes, that's the way much of the internal implementation of our diagramming system is handled. You just have to be careful developing rules because, since they can change the model, they may have unintended consequences and result in other rules firing.
JargonBuster
Hi Ted,
The type of custom placement that you're asking for is possible to do in our diagrams. Our modeling system has a feature called Rules that are fired within or at the end of a transaction (but before it's complete), that allows you to respond to changes in the model. You need to create a rule that provides that placement that you're looking for.
A rule is a class that derives from one of the rules defined in the modeling system (in this case the AddRule), and overrides one or more of its methods (ElementAdded). It also needs a RuleOn attribute placed ont he class. The attribute allows you to say what type it works one and when the rule should fire (inline, local commit, and top level commit). Here's some code for such a rule:
/// <summary>
/// Rule to call programmatically set the position of a Shape when
/// it's added to the diagram (directly or indirectly). This rule
/// gets called when the transaction's top level commit is called.
/// </summary>
[RuleOn(typeof(ParentShapeContainsNestedChildShapes), FireTime = TimeToFire.TopLevelCommit)]
public class ShapeAddedToDiagramRule : AddRule
{
private double offset = 0.25;
private PointD location = new PointD(0.25, 0.25);
public override void ElementAdded(ElementAddedEventArgs e)
{
Shape shape = null;
ParentShapeContainsNestedChildShapes nestedLink = e.ModelElement as ParentShapeContainsNestedChildShapes;
if (nestedLink != null)
{
shape = nestedLink.NestedChildShapes as Shape;
}
if (shape != null && shape.Diagram != null)
{
// Expand the shape and move it to its new position
shape.IsExpanded = true;
shape.Location = new PointD(location.X, location.Y + offset);
// Adjust the height offset for the size of the shape
// (I'm assuming that the DefaultContainerMargin
// provides for a decent spacing between the shapes)
offset += shape.Size.Height + shape.Diagram.DefaultContainerMargin.Height;
}
}
}
The above code starts at the top-left of the diagram and moves additional shapes down the diagram. You'll need to specify the starting offset and location (and probably reset it), because this code just always assumes the top left of the diagram, but it gives an idea of how to get this working. Also, this rule is pretty general because is works on anything derived from Shape (which is basically everything on your diagram but Connectors), you can make this more targeted by trying to cast to a specific shape type, if you need that.
You also need to the the modeling system know that this rule exists. Rules we generate in code automatically get added to the modeling system, but not custom written ones. To do this, you need to let the modeling system know to add your new rule, like this:
namespace MicrosoftCorporation.Language1.Designer
{
internal static partial class GeneratedMetaModelTypes
{
// If you have hand-written rules, you define a
// partial GeneratedMetaModelTypes and put the types
// of the rules as internal static fields of the class.
// For example:
// internal static Type MyRuleType = typeof(MyRule);
internal static Type ShapeAddedToDiagramRuleType = typeof(ShapeAddedToDiagramRule);
}
}
Define an internal field for your rule type, make sure the namespace points to the one for your Designer (this class is defined as partial in the DesignerMetaModelTypes.cs file in your project), and the model system goes through all of the fields on this class and adds to the notification list.
With all of this custom code, it's best that you put it into a separate code file, so that it doesn't get deleted whenever you transform all of the templates again.
Thanks.