Rehosting the Workflow Designer in WF4
Note: This blog post is written using the .NET framework 4.0 Beta 2
With Windows Workflow Foundation 3 it was possible to rehost the workflow designer in your own application. But possible is about all there was to say about it as it was pretty hard to do anything beyond the basics.
With Windows Workflow Foundation 4 live has become much better on the rehosting front
In fact it is possible to create the fully functional and useful workflow editor below in about 200 lines of code. Now that is more like it!
The WorkflowDesigner
The WorkflowDesigner is the main class to work with. This exposes the actual design surface through the View property and the linked property sheet through the PropertyInspectorView property. Both these properties point to ready to use WPF UIElement’s so, as long as the host used WPF, adding them to a form is easy. And loading or saving a workflow is easy to, all it takes is a Load() and Save() function pointing to a XAML file. The code that does this part is below:
_workflowDesigner = new WorkflowDesigner();
_workflowDesigner.Load(_fileName);
var view = _workflowDesigner.View;
Grid.SetColumn(view, 1);
Grid.SetRow(view, 1);
LayoutGrid.Children.Add(view);
var propInspector = _workflowDesigner.PropertyInspectorView;
Grid.SetColumn(propInspector, 2);
Grid.SetRow(propInspector, 1);
LayoutGrid.Children.Add(propInspector);
DesignerMetadata
One thing that is needed is to register the workflow activity designer metadata. It is just a single call but leaving it out means all activities are just a small collapsed shape and won’t expand.
new DesignerMetadata().Register();
Displaying the toolbox with activities
The toolbox on the left if another standard WPF control called the ToolboxControl. Again easy to add to any WPF form. Add to it ToolboxItemWrapper with the activity type to add and you are good to go. Dragging controls onto the design surface just works, no extra work required. In the code below I just scan through all types in a few assemblies and if they are valid activities I add them to the toolbox. Again nice and easy.
var toolbox = new ToolboxControl();
var cat = new ToolboxCategory("Standard Activities");
var assemblies = new List<Assembly>();
assemblies.Add(typeof(Send).Assembly);
assemblies.Add(typeof(Delay).Assembly);
assemblies.Add(typeof(ReceiveAndSendReplyFactory).Assembly);
var query = from asm in assemblies
from type in asm.GetTypes()
where type.IsPublic &&
!type.IsNested &&
!type.IsAbstract &&
!type.ContainsGenericParameters &&
(typeof(Activity).IsAssignableFrom(type) ||
typeof(IActivityTemplateFactory).IsAssignableFrom(type))
orderby type.Name
select new ToolboxItemWrapper(type);
query.ToList().ForEach(ti => cat.Add(ti));
toolbox.Categories.Add(cat);
Grid.SetColumn(toolbox, 0);
Grid.SetRow(toolbox, 1);
LayoutGrid.Children.Add(toolbox);
The current selection
At the top of the form I display the currently selected activity and its parents. The WorkflowDesigner has an Items collection with a bunch of useful objects in there. One of these is a Selection object. We could periodically check this Selection but its even easier to subscribe to chances using the Subscribe() function and passing in a handler that is called whenever the selection changes. Setting up the subscription is another one-liner.
_workflowDesigner.Context.Items.Subscribe<Selection>(SelectionChanged);
The handler itself isn’t very complex either.
private void SelectionChanged(Selection selection)
{
var modelItem = selection.PrimarySelection;
var sb = new StringBuilder();
while (modelItem != null)
{
var displayName = modelItem.Properties["DisplayName"];
if (displayName != null)
{
if (sb.Length > 0)
sb.Insert(0, " - ");
sb.Insert(0, displayName.ComputedValue);
}
modelItem = modelItem.Parent;
}
CurrentActivityName.Text = sb.ToString();
}
Validating the workflow
It would be nice to let the user know if the workflow is valid. Again quite easy to do by adding a IValidationErrorService to the WorkflowDesigner services collection. In this case I added a listbox to the form and let the IValidationErrorService add each error to the ListBox items. No need to call anything, the IValidationErrorService is automatically called as soon as anything in the workflow changes.
var validationErrorService = new ValidationErrorService(WorkflowErrors.Items);
_workflowDesigner.Context.Services.Publish<IValidationErrorService>(validationErrorService);
The IValidationErrorService consists of a single function that gets the list of errors as a parameter.
public class ValidationErrorService : IValidationErrorService
{
private IList _errorList;
public ValidationErrorService(IList errorList)
{
_errorList = errorList;
}
public void ShowValidationErrors(IList<ValidationErrorInfo> errors)
{
_errorList.Clear();
foreach (var error in errors)
{
_errorList.Add(error.Message);
}
}
}
Running the workflow
Just to be able to run the workflow I added a bit of code that uses a WorkflowApplication to run the workflow. Loading it is also easy, just call the ActivityXamlServices.Load() passing in the file and it will return a DynamicActivity ready to run.
var writer = new StringWriter();
var workflow = ActivityXamlServices.Load(_fileName);
var wa = new WorkflowApplication(workflow);
wa.Extensions.Add(writer);
wa.Completed = WorkflowCompleted;
wa.OnUnhandledException = WorkflowUnhandledException;
wa.Run();
So all together I have a working application that I can use to edit and run workflows. So who still needs Visual Studio 2010?
Sweet 
Enjoy!
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu