Adding activity event status to a workflow image
Yesterday I showed in my previous
blog post the minimal code required to create an image from a workflow definition. As I mentioned the most interesting reason for doing so is showing the status of an executing workflow in a sort of monitoring web site. So the next step is to add the
ExecutionStatus of each activity to the image. The following image is an example of the result.
This can be done using an IDesignerGlyphProvider in the WorkflowView type. The WorkflowView is already equipped with a IDesignerGlyphProviderService service to with we need to add our IDesignerGlyphProvider implementations. As we need to use the protected GetService to get a reference to the IDesignerGlyphProviderService we need to create a subclass of WorkflowView and add some code.
Imports System.Workflow.ComponentModel.Design
Class GlyphProvidedWorkflowView
Inherits WorkflowView
SubNew(ByVal serviceProvider As IServiceProvider)
MyBase.New(serviceProvider)
EndSub
PublicSub AddGlyphProvider(ByVal provider As IDesignerGlyphProvider)
Dim service As IDesignerGlyphProviderService
service = GetService(GetType(IDesignerGlyphProviderService))
service.AddGlyphProvider(provider)
EndSub
EndClass
The next step is to create the
IDesignerGlyphProvider implementation itself. Again no big deal. In this example I decided to use the Workflow SqlTrackingQuery to retrieve an actual SqlTrackingWorkflowInstanceinstead instead of a dummy workflow created on the spot. Using this SqlTrackingWorkflowInstance gives me the history of the activity events to display. After all that is wat we want in a monitoring web site. The class I created is DesignerGlyphProvider and it contains a function to add the activity event and the GetGlyphs as gefined by the IDesignerGlyphProvider interface. Again the code is no big deal 
Imports System.Workflow.ComponentModel.Design
Imports System.Workflow.Runtime.Tracking
Imports System.Workflow.ComponentModel
PublicClass DesignerGlyphProvider
Implements IDesignerGlyphProvider
Private _activityExecutionStatus AsNew Dictionary(OfString, ActivityExecutionStatus)
PublicFunction GetGlyphs(ByVal activityDesigner As ActivityDesigner) As ActivityDesignerGlyphCollection Implements IDesignerGlyphProvider.GetGlyphs
Dim result AsNew ActivityDesignerGlyphCollection()
SyncLock _activityExecutionStatus
If _activityExecutionStatus.ContainsKey(activityDesigner.Activity.QualifiedName) Then
Dim status As ActivityExecutionStatus = _activityExecutionStatus(activityDesigner.Activity.QualifiedName)
SelectCase status
Case ActivityExecutionStatus.Executing
result.Add(New ExecutingStatusGlyph())
Case ActivityExecutionStatus.Closed
result.Add(New ClosedStatusGlyph())
Case ActivityExecutionStatus.Faulting
result.Add(New FaultingStatusGlyph())
Case ActivityExecutionStatus.Canceling
result.Add(New CancelingStatusGlyph())
EndSelect
EndIf
EndSyncLock
Return result
EndFunction
PublicSub AddActivityEvents(ByVal trackingRecords As IList(Of ActivityTrackingRecord))
SyncLock _activityExecutionStatus
_activityExecutionStatus.Clear()
ForEach record As ActivityTrackingRecord In trackingRecords
If _activityExecutionStatus.ContainsKey(record.QualifiedName) Then
_activityExecutionStatus(record.QualifiedName) = record.ExecutionStatus
Else
_activityExecutionStatus.Add(record.QualifiedName, record.ExecutionStatus)
EndIf
Next
EndSyncLock
EndSub
EndClass
Of course we need the glyphs to draw on the design surface. I kept things really simple and just used the Winding font to write an appropriate character for each status.
Imports System.Drawing
Imports System.Workflow.ComponentModel.Design
PublicClass StatusDesignerGlyph
Inherits DesignerGlyph
Protected _text AsString = ""
Protected _brush As Brush = Brushes.Black
ProtectedOverridesSub OnPaint(ByVal graphics As Graphics, ByVal activated AsBoolean, ByVal ambientTheme As AmbientTheme, ByVal designer As ActivityDesigner)
Using font AsNew Font("Wingdings", 12)
graphics.DrawString(_text, font, _brush, designer.Bounds)
EndUsing
EndSub
EndClass
PublicClass ClosedStatusGlyph
Inherits StatusDesignerGlyph
SubNew()
_text = "J"
_brush = Brushes.Green
EndSub
EndClass
PublicClass ExecutingStatusGlyph
Inherits StatusDesignerGlyph
SubNew()
_text = "F"
EndSub
EndClass
PublicClass FaultingStatusGlyph
Inherits StatusDesignerGlyph
SubNew()
_text = "N"
_brush = Brushes.Red
EndSub
EndClass
PublicClass CancelingStatusGlyph
Inherits StatusDesignerGlyph
SubNew()
_text = "I"
_brush = Brushes.Red
EndSub
EndClass
That only leaves us with the main function that was slightly updated from yesterday. Of course I added the SqlTrackingQuery to load an actual SqlTrackingWorkflowInstance object. Next I changed the standard WorkflowView used to the custom GlyphProvidedWorkflowView type. The final addition is adding the DesignerGlyphProvider to the GlyphProvidedWorkflowView.
Imports System.ComponentModel.Design
Imports System.ComponentModel.Design.Serialization
Imports System.Drawing.Imaging
Imports System.Workflow.Activities
Imports System.Workflow.ComponentModel
Imports System.Workflow.ComponentModel.Design
Imports System.Workflow.Runtime.Tracking
Module Module1
Sub Main()
Dim query AsNew SqlTrackingQuery("Data Source=.\sqlexpress;Initial Catalog=WF_Tracking;Integrated Security=True")
Dim options AsNew SqlTrackingQueryOptions
Dim instances As IList(Of SqlTrackingWorkflowInstance) = query.GetWorkflows(options)
Dim instance As SqlTrackingWorkflowInstance = instances(instances.Count - 1)
Dim workflow As Activity = instance.WorkflowDefinition
Dim loader AsNew WorkflowLoader(workflow)
Dim surface AsNew DesignSurface
surface.BeginLoad(loader)
Dim view AsNew GlyphProvidedWorkflowView(CType(surface, IServiceProvider))
Dim glyphProvider AsNew DesignerGlyphProvider()
glyphProvider.AddActivityEvents(instance.ActivityEvents)
view.AddGlyphProvider(glyphProvider)
view.SaveWorkflowImage("workflow.png", ImageFormat.Png)
Process.Start("workflow.png")
EndSub
EndModule
Enjoy!