Task 1: Create the Workflow Designer Loader
In this task, you create the class responsible for the loading and unloading of a workflow definition. This is accomplished by deriving from the WorkflowDesignerLoader class. The WorkflowDesignerLoader class can be implemented to support custom loading of a workflow designer and workflow designer components. A workflow designer loader is also responsible for writing changes to an open document back to the storage the loader used when loading the document after the Flush method is called.
Note
Although you are encouraged to follow the exercises in a linear manner, it is not required. You can start this exercise by opening the sample project and proceeding to the steps in the following section.
To create the WorkflowDesignerLoader source code file
In your projects directory, create a new file named Loader.
Give the file a .cs extension if you are creating a C# application or a .vb extension if you are creating a Visual Basic application.In your main project file (WFEdit), in the ItemGroup element that you created in the first procedure, add a new Compile element.
Add a new attribute to the Compile element named Include.
Use the file name that you created in step 1 for the attribute value.
Your final ItemGroup node will appear as follows:
<ItemGroup> <Compile Include="Loader.vb" /> <Compile Include="WFEditForm.vb"> <SubType>Form</SubType> </Compile> <Compile Include="Program.vb" /> </ItemGroup>
<ItemGroup> <Compile Include="Loader.cs" /> <Compile Include="WFEditForm.cs"> <SubType>Form</SubType> </Compile> <Compile Include="Program.cs" /> </ItemGroup>
To create the WorkflowLoader class
In the Loader file you created in the previous procedure, create a namespace block using the same namespace as the Windows Form source code. For this tutorial, the namespace is Microsoft.Samples.Workflow.Quickstarts.WFEdit.
' To be provided
namespace Microsoft.Samples.Workflow.Quickstarts.WFEdit { }
Add the following namespace directives to import the types that you will need for the WorkflowLoader class. These directives should be placed within the namespace block you created in the previous step.
using System; using System.IO; using System.ComponentModel; using System.ComponentModel.Design; using System.ComponentModel.Design.Serialization; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Compiler; using System.Workflow.ComponentModel.Design; using System.Workflow.ComponentModel.Serialization; using System.Collections; using System.Collections.Generic; using System.Xml; using System.Windows.Forms;
Create an internal sealed class named WorkflowLoader that is derived from the WorkflowDesignerLoader class.
' To be provided
internal sealed class WorkflowLoader : WorkflowDesignerLoader { }
In the WorkflowLoader class you created in the previous step, create a private string variable named xaml and initialize it to an empty string.
Create a public string property named Xaml and define get and set methods to return and set, respectively, the private string variable you created in the previous step.
private string xaml = string.Empty; public string Xaml { get { return this.xaml; } set { this.xaml = value; } }
Override the protected Initialize method in the WorkflowLoader class and call the base class implementation of Initialize in the definition of the method.
Override the public Dispose method in the WorkflowLoader class and call the base class implementation of Dispose in the definition of the method.
protected override void Initialize() { base.Initialize(); } public override void Dispose() { base.Dispose(); }
Override the public string property named FileName and define a get method. In the definition of the get method, return null (Nothing in Visual Basic).
Note
This tutorial will not use file storage to save workflow definitions. All workflow definitions are created in memory but are not saved for further use after the application closes. Therefore, any file storage related methods in the WorkflowLoader class will return null for their implementations.
Override the GetFileReader method using a string named filePath as a parameter. This method should return null (Nothing in Visual Basic) in its definition.
Override the GetFileWriter method using a string named filePath as a parameter. This method should return null (Nothing in Visual Basic) in its definition.
public override string FileName { get { return null; } } public override TextReader GetFileReader(string filePath) { return null; } public override TextWriter GetFileWriter(string filePath) { return null; }
To load workflow definitions using WorkflowLoader
In the WorkflowLoader class you created in the previous procedure, override the protected PerformLoad method. This method expects an IDesignerSerializationManager object as a parameter to the method.
' To be provided
protected override void PerformLoad(IDesignerSerializationManager serializationManager) { }
In the PerformLoad method, create a local Activity variable named rootActivity and initialize it to null (Nothing in Visual Basic).
In the PerformLoad method, create a IDesignerHost local variable named designerHost. Initialize this variable by calling the GetService method, passing the type of the IDesignerHost interface.
Create a local TextReader variable in the PerformLoad method and initialize it by creating a new StringReader object, passing the xaml field of the WorkflowLoader class as a parameter to the StringReader constructor. The PerformLoadmethod should appear similar to the following code at this point:
Activity rootActivity = null; IDesignerHost designerHost = (IDesignerHost)GetService(typeof(IDesignerHost)); TextReader reader = new StringReader(this.xaml);
Following the declaration of the local variables you created in the previous steps, create a try block. In the try block, create an XmlReader named xmlReader and initialize it using the static Create method of the XmlReader class, passing the local reader variable you created in step 4.
Create a new local WorkflowMarkupSerializer variable named xomlSerializer and create a new instance of that class.
Call the Deserialize method of the WorkflowMarkupSerializer object you created in the previous step, passing the xmlReader local variable as a parameter to the method. Cast the return value to an Activity object and assign the return value to the rootActivity local variable you created in step 2.
Create a catch block following the try block you created in step 5. Use the WorkflowMarkupSerializationException type as the exception to catch. This exception will be ignored, so the body of the catch block will be empty.
Create a finally block following the catch block you created in the previous step. In the body of the finally block, call the Close method of the TextReader object you created in step 4. The try-catch-finally block should appear similar to the following code:
try { using (XmlReader xmlReader = XmlReader.Create(reader)) { WorkflowMarkupSerializer xomlSerializer = new WorkflowMarkupSerializer(); rootActivity = xomlSerializer.Deserialize(xmlReader) as Activity; } } catch (WorkflowMarkupSerializationException) { } finally { reader.Close(); }
Following the try-catch-finally block you created in the previous steps, create a conditional statement to ensure that the rootActivity and designerHost local variables are not null (Nothing in Visual Basic). If this statement is true, call the AddObjectGraphToDesignerHost method, passing the designerHost and rootActivity variables as parameters to the method.
Note
The AddObjectGraphToDesignerHost method will be defined in the next procedure.
if (rootActivity != null && designerHost != null) { AddObjectGraphToDesignerHost(designerHost, rootActivity); }
The PerformLoad method you defined in this procedure should appear similar to the following code:
protected override void PerformLoad(IDesignerSerializationManager serializationManager) { Activity rootActivity = null; IDesignerHost designerHost = (IDesignerHost)GetService(typeof(IDesignerHost)); TextReader reader = new StringReader(this.xaml); try { using (XmlReader xmlReader = XmlReader.Create(reader)) { WorkflowMarkupSerializer xomlSerializer = new WorkflowMarkupSerializer(); rootActivity = xomlSerializer.Deserialize(xmlReader) as Activity; } } catch (WorkflowMarkupSerializationException) { } finally { reader.Close(); } if (rootActivity != null && designerHost != null) { AddObjectGraphToDesignerHost(designerHost, rootActivity); } }
To add activities to the workflow designer host
In the WorkflowLoader class, create a new private static method named AddObjectGraphToDesignerHost using an IDesignerHost object named designerHost and an Activity object named activity as parameters to the method.
' To be provided
private static void AddObjectGraphToDesignerHost(IDesignerHost designerHost, Activity activity) { }
In the AddObjectGraphToDesignerHost method, create a string variable named fullClassName and initialize it by accessing the FullName property from the Type object returned from the GetType method of the activity parameter.
Create a string variable named rootSiteName and initialize it using the substring of the fullClassName corresponding to the last portion of the string immediately following the last period in that string. For instance, if the fullClassName is equal to System.Workflow.Activities.SequentialWorkflowActivity, rootSiteName would be equal to SequentialWorkflowActivity. The following code shows how to declare and initialize the fullClassName and rootSiteName local variables.
string fullClassName = activity.GetType().FullName; string rootSiteName = (fullClassName.LastIndexOf('.') != -1) ? fullClassName.Substring(fullClassName.LastIndexOf('.') + 1) : fullClassName;
Add the activity object to the Container collection of the designerHost object, passing the activity and rootSiteName objects as parameters to the collection's Add method.
If the activity object is a CompositeActivity, enumerate through each child activity by calling the GetNestedActivities method, passing the activity object as a parameter and adding each child activity to the designerHost Container collection.
Note
The GetNestedActivities method will be defined in the next procedure.
designerHost.Container.Add(activity, rootSiteName); if (activity is CompositeActivity) { foreach (Activity childActivity in GetNestedActivities(activity as CompositeActivity)) designerHost.Container.Add(childActivity, childActivity.QualifiedName); }
The final AddObjectGraphToDesignerHost method should appear similar to the following code:
private static void AddObjectGraphToDesignerHost(IDesignerHost designerHost, Activity activity) { string fullClassName = activity.GetType().FullName; string rootSiteName = (fullClassName.LastIndexOf('.') != -1) ? fullClassName.Substring(fullClassName.LastIndexOf('.') + 1) : fullClassName; designerHost.Container.Add(activity, rootSiteName); if (activity is CompositeActivity) { foreach (Activity childActivity in GetNestedActivities(activity as CompositeActivity)) designerHost.Container.Add(childActivity, childActivity.QualifiedName); } }
To create an array of nested child activities
In the WorkflowLoader class, create a new private static method named GetNestedActivities that accepts a CompositeActivity parameter named compositeActivity and returns an array of Activity objects.
' To be provided
private static Activity[] GetNestedActivities(CompositeActivity compositeActivity) { }
In the GetNestedActivities method, throw a new ArgumentNullException exception if the compositeActivity parameter passed into the method is null (Nothing in Visual Basic).
if (compositeActivity == null) throw new ArgumentNullException("compositeActivity");
Create an ArrayList object named nestedActivities and create an instance of that type.
Create a Queue object named compositeActivities and create an instance of that type.
Add the compositeActivity object passed in as a parameter to the GetNestedActivities method to the compositeActivities queue by calling the Enqueue method.
ArrayList nestedActivities = new ArrayList(); Queue compositeActivities = new Queue(); compositeActivities.Enqueue(compositeActivity);
Create a while loop that will check the Count property of the compositeActivities object and will continue the loop if the Count is greater than 0.
In the while loop, create a CompositeActivity variable named currentCompositeActivity and initialize it by calling the Dequeue method of the compositeActivities collection.
Enumerate through the Activities collection of the currentCompositeActivity object using a foreach loop and add the resulting child Activity to the nestedActivities collection. Additionally, if the child activity is a CompositeActivity, add it to the compositeActivities collection by calling the Enqueue method.
The while loop you created in steps 6-8 should appear similar to the following code:
while (compositeActivities.Count > 0) { CompositeActivity currentCompositeActivity = (CompositeActivity)compositeActivities.Dequeue(); foreach (Activity activity in currentCompositeActivity.Activities) { nestedActivities.Add(activity); if (activity is CompositeActivity) compositeActivities.Enqueue(activity); } }
Convert the nestedActivities ArrayList collection to an array of Activity objects by calling the ToArray method and passing the Type of the Activity class. You will need to cast the result to an Activity array. Your entire GetNestedActivities method should appear similar to the following code:
private static Activity[] GetNestedActivities(CompositeActivity compositeActivity) { if (compositeActivity == null) throw new ArgumentNullException("compositeActivity"); ArrayList nestedActivities = new ArrayList(); Queue compositeActivities = new Queue(); compositeActivities.Enqueue(compositeActivity); while (compositeActivities.Count > 0) { CompositeActivity currentCompositeActivity = (CompositeActivity)compositeActivities.Dequeue(); foreach (Activity activity in currentCompositeActivity.Activities) { nestedActivities.Add(activity); if (activity is CompositeActivity) compositeActivities.Enqueue(activity); } } return (Activity[])nestedActivities.ToArray(typeof(Activity)); }
To remove activities from the designer host
In the WorkflowLoader class, create a new static method named DestroyObjectGraphFromDesignerHost that accepts an IDesignerHost named designerHost and an Activity named activity as parameters.
' To be provided
private static Activity[] GetNestedActivities(CompositeActivity compositeActivity) { }
In the DestroyObjectGraphFromDesignerHost method, throw a new ArgumentNullException if the designerHost or activity parameter passed into the method is null (Nothing in Visual Basic).
if (designerHost == null) throw new ArgumentNullException("designerHost"); if (activity == null) throw new ArgumentNullException("activity");
Call the DestroyComponent method from the designerHost object, passing the activity object as a parameter to the method.
If the activity object is a CompositeActivity, enumerate through each child activity of the activity using the GetNestedActivities method you created in the previous procedure. For each child activity, call the DestroyComponent method, passing the child activity as a parameter to that method as shown in the following code:
designerHost.DestroyComponent(activity); if (activity is CompositeActivity) { foreach (Activity activity2 in GetNestedActivities(activity as CompositeActivity)) designerHost.DestroyComponent(activity2); }
The final DestroyObjectGraphFromDesignerHost method should appear similar to the following code:
internal static void DestroyObjectGraphFromDesignerHost(IDesignerHost designerHost, Activity activity) { if (designerHost == null) throw new ArgumentNullException("designerHost"); if (activity == null) throw new ArgumentNullException("activity"); designerHost.DestroyComponent(activity); if (activity is CompositeActivity) { foreach (Activity activity2 in GetNestedActivities(activity as CompositeActivity)) designerHost.DestroyComponent(activity2); } }
Compiling the Code
For information about compiling your code, see Compiling the Code.
In the next step, you will add the necessary code to the Windows Form class you created in Task 2: Create the Workflow Designer Hosting Windows Form in order to use the WorkflowLoader class you created in this task. When the task is completed, the Windows Workflow Designer will be visible in your Windows Form application.
See Also
Concepts
Other Resources
Basic Designer Hosting Sample
Outlook Workflow Wizard Sample
Workflow Monitor Sample
Tracking Profile Designer Sample
Copyright © 2007 by Microsoft Corporation. All rights reserved.
Last Published: 2010-03-04