Displaying a Microsoft Project XML file using Diagrammer for .NET, WPF and LinQ (Part 2)

In the part 1 of this post, we have seen how to use ILOG Diagrammer for .NET, WPF and Linq to display the content of a Microsoft Project XML file into a graph showing the tasks and the constraints between them, we are now going to see how to use more WPF features to display the critical path of the project.
The critical path of the project is simply the set of tasks that are critical. The task is considered as critical if a delay in the task will delay the end of the project. This set of tasks and the constraints between them usually creates a path in the network, called the critical path although sometime you might see several paths.
For our example, we will fist try to display the critical tasks with a red background and give some information on different type of tasks.

We add 3 new boolean properties to our Task class, a Milestone property for tasks that are milestones, a Summary property for tasks that represents a group of tasks, and a Critical property for critical tasks.

 public bool Milestone { get; set; }
 public bool Summary { get; set; }
 public bool Critical { get; set; }

and we change the Linq query to fill these properties from the XML file:

project.Tasks
      = (from task in document.Descendants("{http://schemas.microsoft.com/project}Task")
          where (int)task.Element("{http://schemas.microsoft.com/project}ID") != 0
          select new Task()
           {
               Project = project,
               UID = (int)task.Element("{http://schemas.microsoft.com/project}UID"),
               Name = (string)task.Element("{http://schemas.microsoft.com/project}Name"),
               Start = (DateTime)task.Element("{http://schemas.microsoft.com/project}Start"),
               Finish = (DateTime)task.Element("{http://schemas.microsoft.com/project}Finish"),
               Milestone = (bool)task.Element(”{http://schemas.microsoft.com/project}Milestone”),
               Critical = (bool)task.Element(”{http://schemas.microsoft.com/project}Critical”),
               Summary = (bool)task.Element(”{http://schemas.microsoft.com/project}Summary”),
               PredecessorLinks =
                         (from link in task.Descendants(”{http://schemas.microsoft.com/project}PredecessorLink”)
                         select
                           new PredecessorLink
                           {
                               PredecessorUID = (int)link.Element(”{http://schemas.microsoft.com/project}PredecessorUID”)
                           }).ToList() 

              }).ToList();

Now that our Task has 3 new properties we can modify the style of nodes with data triggers to take advantage of this. The data trigger in a style will allow changing the style depending on data. In our case, milestones will be white, summary tasks in blue and all critical tasks in red.
Here is the new style for nodes with 3 data triggers.

 <Style TargetType="{x:Type ilog:Node}" >
     <Setter Property="Background" Value="#FFF0A5"/>
     <Setter Property="BorderBrush" Value="#468966"/>
     <Setter Property="BorderThickness" Value="1"/> 

     <Style.Triggers> 

        <DataTrigger Binding=”{Binding Summary}” Value=”true”>
           <Setter Property=”Background” Value=”blue”/>
        </DataTrigger> 

        <DataTrigger Binding=”{Binding Milestone}” Value=”true”>
           <Setter Property=”Background” Value=”white”/>
           <Setter Property=”CornerRadius” Value=”10″/>
        </DataTrigger> 

        <DataTrigger Binding=”{Binding Critical}” Value=”true”>
           <Setter Property=”Background” Value=”red”/>
        </DataTrigger> 

      </Style.Triggers>
 </Style>

The definition of data triggers is very straight forward. The data trigger evaluates the value specified by the Binding (for example {Binding Summary}) and see if it matches the value specified in the Value property (in our case true), if the two things match, then the setters are applied, in our case the background color is changed.

Two things to understand here, first the binding specified in the data trigger is evaluated on the data context of the Node. In the Diagram control the data context for node is the item in the data source, in our case a Task object, that is why I can directly bind to the properties of a Task (like {Binding Milestone}), no need to specify any source for the binding.
The second point is the order of the triggers. I place the last trigger that changes the background to red for critical tasks at the end because a milestone or a summary task can also be critical. The last data trigger will win in such a case and thus all critical tasks will be red.
We now have the following result, showing the critical tasks :

Critical Path

We can also use data triggers in data templates. For example, for a milestone task it is not necessary to display the start and finish date since the dates are the same.
In order to achieve this we add a data trigger in the data template of nodes:

<DataTemplate x:Key="TaskTemplate">
  <Grid  Margin="5" MaxWidth="150" x:Name="grid">
    <Grid.RowDefinitions>
      <RowDefinition/>
      <RowDefinition/>
      <RowDefinition/>
    </Grid.RowDefinitions>
    <TextBlock
       x:Name="header" Grid.Row ="0"
       Text="{Binding Path=Name}"
       TextWrapping="WrapWithOverflow"
       Foreground="#FFB03B"
       FontWeight="Bold"/>
    <StackPanel x:Name="startField"  Grid.Row ="1" Orientation="Horizontal">
      <TextBlock x:Name="startLabel" Text="Start:"/>
      <TextBlock Text="{Binding Path=Start}"/>
    </StackPanel>
    <StackPanel x:Name="finishField" Grid.Row ="2" Orientation="Horizontal">
      <TextBlock x:Name="finishLabel" Text="Finish:"/>
      <TextBlock Text="{Binding Path=Finish}"/>
    </StackPanel>
  </Grid>
  <DataTemplate.Triggers>
    <DataTrigger Binding=”{Binding Milestone}” Value=”true”>
        <Setter TargetName=”startLabel” Property=”Text” Value=”Milestone Date:”/>
        <Setter TargetName=”finishField” Property=”Visibility” Value=”Collapsed”/>
    </DataTrigger>
  </DataTemplate.Triggers> 
</DataTemplate>

The trigger here applies to task with Milestone property set to true, it changes the label of the start date to “Milestone Date” and hides the part that corresponds to the end date.

Here is the result for a one of the milestones:

Templated Milestone

Let’s now use data triggers for links. In order to make the critical path more obvious, we will try to draw the links between two critical tasks in red. Painting a critical task in red was easy; we have simply added a data trigger in the style. For links it is a little bit more complex because there is no instance in the data source that represents a link. To answer this problem, The Wpf diagram component creates a convenient data context for each link that is an instance of the class Diagram.LinkContext. This class has two properties, StartItem and EndItem that represents the items in the data source (for us the tasks) that are at the displayed at the begining and end of the link. Thus we can easily use data triggers in a link of the diagram.
We want the link to be red if both the origin and destination tasks are critical, so we need to use a MultiDataTrigger that is a kind of data trigger with multiple conditions:

Here is the new style for links:

<Style TargetType="{x:Type ilog:Link}" >
  <Setter Property="Radius" Value="10"/>
  <Setter Property="Stroke" Value="#FFF0A5"/>
  <Setter Property="StrokeThickness" Value="2"/>
  <Setter Property="StartArrow">
    <Setter.Value>
      <ilog:LinkArrow  Fill="White"  Shape="Circle"/>
    </Setter.Value>
  </Setter>
  <Setter Property="EndArrow">
    <Setter.Value>
      <ilog:LinkArrow Fill="{x:Null}"  Shape="Open"/>
    </Setter.Value>
  </Setter>
  <Style.Triggers>
    <MultiDataTrigger>
      <MultiDataTrigger.Conditions>
        <Condition Binding=”{Binding StartItem.Critical}” Value=”true”/>
        <Condition Binding=”{Binding EndItem.Critical}” Value=”true”/>
      </MultiDataTrigger.Conditions>
      <Setter Property=”Stroke”  Value=”red”/>
      <Setter Property=”StrokeThickness”  Value=”3″/>
    </MultiDataTrigger>
  </Style.Triggers>
</Style>

I hope I have shown how the combination of the diagram control with its graph layout algorithms and the styling and templating features of WPF can help to easily create nice looking diagrams.

If you want to test the project, here is the source code and Visual Studio 2008 solution. In order to compile and run you will need and evaluation of ILOG Diagrammer for .NET that you can get at http://www.ilog.com/products/diagrammernet/eval.

Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • del.icio.us
  • Netvouz
  • DZone
  • ThisNext
  • MisterWong
  • Wists

Leave a Reply