Creating class diagrams with G2

Wherein I explain how G2 was used to create class diagrams. Some notes on XML (de)serialization of G2 diagrams and the upcoming workflow designer.

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

<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>
<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.

< ?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>
Tagged with:
 

3 Responses to “Creating class diagrams with G2”

  1. ThomasS says:

    What do you do if the content of the TreeView gets to big. Do you show a scroll bar? I had this problem when i did some kind of Database Explorer. I restyled the scroll bar of my Listbox but it still looked a bit odd.

  2. By setting the Width to ‘Auto’ and the HorizontalAlignment of the parent Border the whole shape gets resized whenever the content gets bigger/smaller, the MinWidth/MaxWidth enforcing a total collapse when nothing is present. In addition, when the content of some text gets too big the text-trimming makes sure that an ellipsis is shown. So, in practice there is no need for scrolling but one has, of course situations where things go wrong but these pathologies are usually due to the exotic content shown rather than the failure of the encapsulating shape.

  3. Dutouya.D says:

    Hi !

    If the header’s value is too long , the treeview shape don’t display all the header’s content.

    For the treeview it seams that there’s an auto resize content , why it’s not the same thing for the header? Is it possible to adjust max header’s size ?

    Best regards

Leave a Reply