Creating class diagrams with G2

As a proof of concept for some customer I created recently some class diagrams in G2. The purpose was the following; to be able to drag-drop assembly items on a diagram surface much like one does inside Visual Studio and to have the same kind of coloring and auto-connecting of inter-related classes (i.e. inheritance). As a to-be-visualized assembly I took the G2 assembly itself. So, in a way G2 is reflecting itself in this application.

Part of the work consisted in creating some smart reflection code. Fortunately, nowadays one can do things in a flash through LINQ, so this was done in no time.

G2/O2 with class diagrams.

Another part consisted in customizing my O2 presentation environment which has the look-and-feel of Microsoft Blend and a bit of Visual Studio as well. The code is largely based on the AvalonDock project and on Denis Vuyka’s property grid. The big challenge here was to merge and harmonize the code into one smooth framework. At some point I considered to merge some WPF composition ideas as well but due to the horrendous learning curve and the P&P’s complicated ideas around multi-targetting their framework I set the idea aside. Still, I believe that as it stands now the O2 code is well suited to develop within it a variety of (commercial) applications. See the screenshots and note, by the way, how nicely the browser and The Orbifold’s website blends into the application.

Browser and site integration in G2/O2

Finally, the biggest part of my efforts went into creating the ‘class shape’  or, as it’s called internally, the TreeviewShape. Let me highlight a bit the anatomy of the shape and how the serialization of it is implemented.

The shape is organized more or less like this (see also the XAML template below):

  • The head consists of the colored (rounded) rectangle inside which multiple headers can be place, all of which can have a distinct icon. The icon which allows you to collapse/expand the shape is in fact a templated button. The whole head (see the TranslationHeader style below) can catch the mouse in order to move the shape around and is in fact itself a templated control.
  • The body is really just a standard WPF TreeView. However, it took me an eternity to make it look OK and to position it inside the template. The reason being that the order in which one places the various XAML tags is important and there are some quirks in the WPF framework (no mouse recognition if there is no background, for instance). This said, I should underline that all of this is nothing in comparison to what it once required before WPF was among us (cfr. the class-shape in Netron in this context).

The most time-consuming part of the work is in fact the little details and the parametrization of the various XAML attributes in order to make sure that the layout works for all possible situations.

The TreeViewShape

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<style TargetType="{x:Type Core:TreeviewShape}">
  <setter Property="MinWidth" Value="125"/>
  <setter Property="MinHeight" Value="28"/>
  <setter Property="SnapsToDevicePixels" Value="True"/>
  <setter Property="Template">
    </setter><setter .Value>
      <controltemplate TargetType="{x:Type Core:TreeviewShape}">
        <border BorderBrush="White"   CornerRadius="10"  Width="Auto" Height="Auto" BorderThickness="1,1,1,2" Background="White">
          <grid x:Name="PART_maingrid" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
            <stackpanel Orientation="Vertical">
              <!-- PART_TranslationOverlay -->
              <core :TranslationOverlay Style="{StaticResource TranslationHeader}" x:Name="PART_TranslationOverlay" Cursor="SizeAll" />
              <treeview Margin="0,0,0,5" Style="{StaticResource xxx}" Width="Auto" Height="Auto"  x:Name="tree" />
            </stackpanel>
            <core :ConnectorDecorator x:Name="PART_ConnectorDecorator"
                         Visibility="Hidden"
                         Template="{StaticResource ConnectorDecoratorTemplate}"/>
          </grid>
        </border>
        </controltemplate><controltemplate .Triggers>
          <!--<dataTrigger Value="True" Binding="{Binding RelativeSource={RelativeSource Self},Path=IsSelected}">
                <setter TargetName="PART_ResizeDecorator" Property="Visibility" Value="Visible"/>
              -->
          <trigger Property="IsMouseOver" Value="true">
            <setter TargetName="PART_ConnectorDecorator" Property="Visibility" Value="Visible"/>
          </trigger>
          <datatrigger Value="True" Binding="{Binding RelativeSource={RelativeSource Self},Path=IsDragConnectionOver}">
            <setter TargetName="PART_ConnectorDecorator" Property="Visibility" Value="Visible"/>
          </datatrigger>
        </controltemplate>
 
    </setter>
 
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<style x:Key="TranslationHeader" TargetType="{x:Type Core:TranslationOverlay}">
  <setter Property="Background">
    </setter><setter .Value>
      <lineargradientbrush EndPoint="1,0" StartPoint="0,0">
        <gradientstop Color="#9FB6CD" Offset="0.50"/>
        <gradientstop Color="#FFF" Offset="1.0"/>
      </lineargradientbrush>
    </setter>
 
  <setter Property="Template">
    </setter><setter .Value>
      <controltemplate TargetType="{x:Type Core:TranslationOverlay}">
        <border x:Name="PART_HeaderBorder" Background="{TemplateBinding Background}" CornerRadius="10,10,0,0">
          <grid x:Name="PART_HeaderGrid" MinHeight="60" Width="184" Height="60">
            </grid><grid .ColumnDefinitions>
              <columndefinition MinWidth="50" Width="140*"/>
              <columndefinition Width="19*" MinWidth="19"/>
            </grid>
            <grid .RowDefinitions>
              <rowdefinition Height="25*"/>
              <rowdefinition Height="25*"/>
              <rowdefinition Height="25*"/>
            </grid>
            <stackpanel Grid.ColumnSpan="1" Orientation="Horizontal" Margin="0,2,0,0">
              <image x:Name="PART_HeaderIcon" Width="Auto" Height="Auto" MaxHeight="10" MaxWidth="10" Margin="5,0,0,0"/>
              <textblock x:Name="PART_Header"    Text="BezierConnection"    Foreground="#FF000000" TextTrimming="CharacterEllipsis" TextWrapping="NoWrap" FontSize="12" FontWeight="Bold" Width="Auto" MaxWidth="120" Height="20" Margin="5,0,0,0" FontFamily="Tahoma" />
            </stackpanel>
            <button Style="{StaticResource SwitchButton}"  HorizontalAlignment="Left" Grid.Column="2" Margin="0,0,2,0" x:Name="PART_ExpandButton" VerticalAlignment="Top" Width="Auto" Height="Auto"/>
            <stackpanel Margin="2,0,5,0" Grid.ColumnSpan="3" Grid.Row="1" Orientation="Horizontal">
              <image x:Name="PART_SubHeaderIcon" Width="Auto" Height="Auto" MaxHeight="10" MaxWidth="10" Margin="3,0,0,0"/>
              <textblock x:Name="PART_SubHeader"  Width="Auto" Height="Auto" Foreground="#FFFFFFFF"  TextWrapping="Wrap" FontSize="11" Margin="5,0,0,0" FontFamily="Tahoma"/>
            </stackpanel>
            <stackpanel Margin="2,0,5,0" Grid.ColumnSpan="2" Grid.Row="2" Orientation="Horizontal">
              <image x:Name="PART_SubSubHeaderIcon" Width="Auto" Height="Auto" MaxHeight="10" MaxWidth="10" Margin="3,0,0,0"/>
              <textblock x:Name="PART_SubSubHeader" Width="Auto" Height="Auto" Foreground="#FFFFFFFF"  TextWrapping="Wrap"  FontSize="11" Margin="5,0,0,0" FontFamily="Tahoma"/>
            </stackpanel>
 
        </border>
      </controltemplate>
    </setter>
 
</style>

Finally, the serialization part. This is really where G2 shines, I think. I probably invested half a year into the data-exchange pipeline of G2 but looking now how much the development benefits from this I’m really happy I went through this difficult development period.
The data exchange pipeline (called internally G2D) is a rather complex piece of software which to some extend is explained in my series of articles on Unity and Unity extensions. It basically consists of ‘actions’ which sit inside the Unity container and are orchestrated into one ‘transaction’ to achieve various things like binary serialization, XML serialization, copy/paste and the creation of undo/redo units of (diagramming) work. It might come as a suprise but probably half of the code of G2 has to do with serialization and data exchange and only a minor part is related to visualization and layout.
Anyway, the data-exchange code (note that this entails more than just serialization) of the TreeViewShape consists of three parts:

  • the data-bucket or the serialization counterpart of the shape. This is a true data-entity much like one has in data access layers of a N-tiered database application. It contains the presentation-independent stuff of the shape and can be serialized to any repository (database, WCF, XML, binary files).
  • The converter between the shape and the data-entity. This can be compared to a mapping layer. Actually, the whole G2D pipeline is much like the Entity Framework as far as functionality is concerned. One could really take the G2D code and generalize it with little efforts to a generic data mapping layer.
  • a lot of XML fine-tuning in order to make WPF things XML-readable. Some would use here the out-of-the-box XAML-writer but I discovered after many failures that the XAML reader/writer of WPF has some limitations and is not suitable if you wish to have a bit of control on what is being outputted.

I will discuss in a separate article the beautiful XML features of G2 but let me briefly here show how easy one can create or modify diagrams through XML. Below is a snippet of G2 XML which serializes a single G2 page with a single layer and in this layer two treeview shapes. Now, it doesn’t require a PhD to see what it all means and to modify things; connections, layers, pages and other shapes are added in a snap. Don’t get confused by the many GUID’s, they are internally generated and can be replaced by your own ID’s. This means in fact that these ID’s could have some meaning inside your applications (database record ID’s for example) and that the XML could be generated outside G2. In addition, G2 allows you to import parts of diagramming documents like pages or bundles of shapes and connections. This then, in effect, allows you to import parts of diagrams from multiple source which then merge into one document. For example, it’s perfectly possible to use multiple WCF services (the data buckets are WCF-ready and serializable through WCF) to feed one diagram.

To conclude, somewhere soon I’ll also demonstrate how G2 can be used to create workflow diagrams in the same fashion. I also intend to create a XOML importer which would enable one to model WF models outside Visual Studio, but considering the upcoming WF 4.0 and its many new features I think I’d better wait until .Net 4.0 is released.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
< ?xml version="1.0" encoding="utf-8" standalone="yes"?>
<gradium Version="1.0">
  <document>
    <metadata>
      <creationdate>2009-01-05T19:35:12.4154+01:00</creationdate>
    </metadata>
    <pages>
      <page AutoLayout="false" AutoConnect="false" IsActivePage="true" PageType="ConceptMap" UID="e5e78121-55c3-422c-a423-39b681fca3c8" Title="Concept map">
        <connections />
        <layers>
          <layer Visible="false" IsDefaultLayer="true" Title="Default layer" UID="bba7b83a-718c-4305-a6a8-5274d4174351">
            <shapes>
              <treeviewshape UID="365b6faf-44e6-4dce-9b28-975990da3ced" Size="NaN (Niet-een-getal);NaN (Niet-een-getal)" Location="1013;546,04" Header="BezierConnection" SubHeader="Class" SubSubHeader="ConnectionBase">
                <connector UID="b9137087-095e-45f5-b92a-04a640b9bc83" Layer="Left" Orientation="Left" Position="1014;599,5" />
                <connector UID="98075af4-77c0-45fa-a407-cc0d7d272643" Layer="Top" Orientation="Top" Position="1106;547,04" />
                <connector UID="e1b496f0-f393-4556-a0db-4b6935831a4f" Layer="Right" Orientation="Right" Position="1198;599,5" />
                <connector UID="e1c800dc-7014-41d1-9930-280a6c317f3c" Layer="Bottom" Orientation="Bottom" Position="1106;651,96" />
                <treeitems>
                  <treeviewitem Header="Methods">
                    <treeviewitem Header="OnApplyTemplate" />
                  </treeviewitem>
                </treeitems>
              </treeviewshape>
              <treeviewshape UID="bfcbbeef-fa17-4ea5-8f66-32e5aff6572b" Size="NaN (Niet-een-getal);NaN (Niet-een-getal)" Location="223;114,04" Header="ActivityBase" SubHeader="Class" SubSubHeader="Object">
                <connector UID="c7c5d368-1773-4267-8f2d-e2f82b42bc29" Layer="Left" Orientation="Left" Position="224;231,34" />
                <connector UID="45e7cf57-1cc6-4529-a03f-dae3f483b231" Layer="Top" Orientation="Top" Position="316;115,04" />
                <connector UID="96547264-8961-4909-a4a6-6b5b1c307e6d" Layer="Right" Orientation="Right" Position="408;231,34" />
                <connector UID="8162149f-1304-40b9-8b1b-9f94c46f8b42" Layer="Bottom" Orientation="Bottom" Position="316;347,64" />
                <treeitems>
                  <treeviewitem Header="Properties">
                    <treeviewitem Header="Name" />
                    <treeviewitem Header="Enabled" />
                    <treeviewitem Header="StepTime" />
                    <treeviewitem Header="StartTime" />
                    <treeviewitem Header="Duration" />
                  </treeviewitem>
                  <treeviewitem Header="Methods">
                    <treeviewitem Header="Run" />
                    <treeviewitem Header="RunAfter" />
                    <treeviewitem Header="Stop" />
                  </treeviewitem>
                </treeitems>
              </treeviewshape>
            </shapes>
          </layer>
        </layers>
      </page>
    </pages>
  </document>
</gradium>

3 Responses to Creating class diagrams with G2

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

top