Diagrammer .NET Tips and Tricks Explained Through the “UML Class Diagram” Sample - Part 2
In the previous post, we explained how to assemble built-in ILOG Diagrammer for .NET objects to build a complex UML Type symbol. The first post described how to define the appearance of the symbol, now we will define its behavior. To do this, we will need to do a bit of C# coding.
If you want to look at the code in Visual Studio, download the second version of the source code and open the UMLClassDiagram.sln solution. To see the code of the UMLType symbol, right-click the UMLType.cs item in the Solution Explorer and choose View Code.
Making the Symbol Functional: Adding Properties
Let’s start with an easy one: the TypeName property. We want the UML type name to be displayed in the title of our UML Type symbol. The code for the TypeName property looks like this:
[Description("The name of the UML type")]
[Category("UML")]
public string TypeName
{
get
{
return nameRect.Text;
}
set
{
nameRect.Text = value;
}
}
What we did here is add a C# property to our user symbol. The property just gets/sets the Text property of the title rectangle of the symbol. This is a frequently used technique: users of your symbol do not have access to its inner elements, so you must selectively expose some of the properties of the inner elements to the outside world if you want users to be able to customize these properties.
Note the [Description(...)] and [Category(...)] attributes: you have probably used these attributes before if you designed .NET components. These attributes can (and should) be used on ILOG Diagrammer .NET user symbols as well.
To try the new property, build the solution (Build > Build Solution), then double-click the Diagram1.cs item in the Solution Explorer. If you select a UMLType instance, you will see the new TypeName property in the property grid, and you can change its value to verify that it works correctly.
(Note: Remember to always rebuild your solution when you change a symbol: the diagrams or the other symbols that use it will be automatically updated.)
Let’s continue with the SplitterSposition property now. This property controls the vertical position of the splitter, and also the sizes of the upper and lower content areas that will contain the attributes and the methods respectively.
private float splitterPosition = 0.5f;
[Description("The vertical position of the splitter (between 0 and 1).")]
[Category("UML")]
[DefaultValue(0.5f)]
public float SplitterPosition
{
get
{
return splitterPosition;
}
set
{
if (value != splitterPosition)
{
splitterPosition = value;
grid.Rows[0].Size = 100 * splitterPosition;
grid.Rows[2].Size = 100 - (100 * splitterPosition);
}
}
}
As you can see, the SplitterPosition property adjusts the sizes of the top and bottom rows of the GridPanel so that the splitter is at the desired position. The position is a floating-point value between 0 and 1 (0 = top, 1 = bottom). Note the [DefaultValue(0.5f)] attribute which makes sure that the property will not be serialized if it has its default value.
As before, you can try the new property: in the Diagram1 designer view, change the SplitterPosition property of a UMLType instance and see how the splitter position changes.
Let’s continue with something a little bit more tricky: the Attributes and Operations properties. We will explain only the Attributes property since the Operations property is almost identical. First, we have to create a simple class that describes a UML attribute.
public class UMLAttribute
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
Our UML Attribute class is really simple, it has only a Name property. We can add more properties if we want (like the attribute type, scope, etc) but the Name property is enough for our explanations, so let’s keep it simple.
The next step is to define a collection of attributes. The collection will be a specialized subclass of the generic System.Collections.ObjectModel.Collection class. What we want to achieve is the following: when the user adds a UMLAttribute to the collection, a graphical item will be added in the corresponding list of our UML Type symbol. So, the collection must be able to notify its UML Type owner when an element is added to it. Here is the code of the collection.
internal class UMLAttributeCollection : Collection<UMLAttribute>
{
private UMLType owner;
internal UMLAttributeCollection(UMLType owner)
{
this.owner = owner;
}
protected override void InsertItem(int index, UMLAttribute item)
{
base.InsertItem(index, item);
owner.AttributeAdded(index, item);
}
protected override void RemoveItem(int index)
{
base.RemoveItem(index);
owner.AttributeRemoved(index);
}
protected override void SetItem(int index, UMLAttribute item)
{
base.SetItem(index, item);
owner.AttributeRemoved(index);
owner.AttributeAdded(index, item);
}
protected override void ClearItems()
{
for (int i = 0; i < Count; i++)
owner.AttributeRemoved(0);
base.ClearItems();
}
}
We rely on the base class Collection for the implementation of the collection, but we override the InsertItem, RemoveItem, SetItem and ClearItems methods to notify the “owner” of the collection, that is, the UML Type. When the content of the collection is modified, we call the AttributeAdded or AttributeRemoved method. These methods will be responsible for creating and deleting the actual graphical items displayed in our symbol:
private void AttributeAdded(int index, UMLAttribute attribute)
{
AttributeSymbol symbol = new AttributeSymbol();
symbol.AttributeName = attribute.Name;
attributesList.Items.Insert(index, symbol);
}
private void AttributeRemoved(int index)
{
UMLAttribute attribute = attributes[index];
attributesList.Items.RemoveAt(index);
}
The graphical items that represent the UML attributes are ILOG Diagrammer .NET user symbols (the AttributeSymbol class). This is a really simple user symbol that contains a “blue square” icon and a text object, and that has an AttributeName property to change the text. We will not describe this sub-symbol in details, you can look at it by double-clicking the AttributeSymbol.cs item in the Solution Explorer (or right-click > View Code to see the code).
(Note: do not confuse the UMLAttribute class with the AttributeSymbol class. The UMLAttribute class is the logical representation used to define and persist a UML attribute, whereas the AttributeSymbol class is the graphical representation of a UML attribute.)
OK, now we finally have all the pieces to create our Attributes property:
private UMLAttributeCollection attributes;
public UMLType()
{
...
attributes = new UMLAttributeCollection(this);
}
[Description("The attributes of the UML type.")]
[Category("UML")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public Collection<UMLAttribute> Attributes
{
get
{
return attributes;
}
}
The attributes member variable will hold the actual collection that backs the property. This collection is created in the constructor of the UML Type class. Finally, we define the Attribute property. The property is read-only, it just returns the collection. To add a UML attribute, users will add items to the collection like this:
UMLAttribute attribute = new UMLAttribute();
attribute.Name = "Attribute1";
umlType1.Attributes.Add(attribute);
One thing to note is the DesignerSerializationVisibility attribute. The Attributes property is read-only, and by default the .NET framework does not serialize read-only properties, so we need to explicitly tell the .NET framework that the property must be serialized. The DesignerSerializationVisibility.Content value tells the framework that the property value is created automatically by the class, and that only its content (that is, the UMLAttribute values contained in it) must be serialized/deserialized.
You can now try the new Attributes property in our sample diagram. Select a UML Type instance and click the “…” button in the Attributes row of the property grid. You get a dialog that lets you add, remove and modify the UMLAttribute instances of the collection. Once you click OK, the UML Type symbol is updated to show the new UML attributes.
Tags: Diagram, properties, UML class diagram, user symbol







