Share via


Task 2: Incorporate the Workflow Designer into the Windows Form

Download sample

In this task, you will use the WorkflowLoader class you created in Task 1: Create the Workflow Designer Loader and integrate it into the Windows Form you created in Task 2: Create the Workflow Designer Hosting Windows Form. The Windows Form contains a Panel control that is used to host the Windows Workflow designer. This is accomplished by creating a WorkflowView object and adding it to the child activities collection of the Panel control. You will also add the code to automatically parse any Xaml that is entered into the TextBox control and pass the Xaml to the WorkflowLoader object in order to display the resulting workflow definition within the Windows Workflow Designer.

Note

Although you are encouraged to follow the exercises in a linear manner, it is not required. You can start on this exercise by opening the sample project and proceeding to the steps in the following section.

To implement the IServiceProvider interface

  1. Implement the IServiceProvider interface in the WFEditForm class you created in Task 2: Create the Workflow Designer Hosting Windows Form.

  2. Create the GetService method required for the IServiceProvider implementation.

    Note

    The GetService method hides the inherited member implementation, so you will need to use the new keyword to use the method you are currently defining.

  3. In the GetService method, return null (Nothing in Visual Basic) if the workflowView field is null. Otherwise, call the GetService method of the workflowView class field, passing the serviceType as a parameter to the method as shown in the following code:

    new public object GetService(Type serviceType)
    {
        return (this.workflowView != null) ? ((IServiceProvider)this.workflowView).GetService(serviceType) : null;
    }
    

To create the necessary fields and properties for designer hosting

  1. In the WFEditForm class you created in Task 2: Create the Workflow Designer Hosting Windows Form, create the following member variables.

    Type Name

    WorkflowView

    workflowView

    DesignSurface

    designerSurface

    WorkflowLoader

    loader

    Activity

    currentWorkflow

    private WorkflowView workflowView;
    private DesignSurface designSurface;
    private WorkflowLoader loader;
    private Activity currentWorkflow;
    
  2. In the WFEditForm class, create a new public string property named Xaml containing both a get and a set method.

  3. In the get method of the Xaml property, create a local string variable named xaml and initialize it to an empty string.

  4. Create a conditional statement to ensure that the loader class variable is not null (Nothing in Visual Basic). In the body of the conditional statement, create a try/catch block.

  5. In the body of the try block, call the Flush method of the loader object and then set the xaml local variable equal to the Xaml property of the loader object.

    Note

    The catch block for the corresponding try block will have an empty body to allow the application to continually parse the Xaml property without interrupting a user with error messages.

  6. Use the xaml local variable as the return value of the get method of the Xaml property. The get method of the Xaml property should appear similar to the following code:

    get
    {
        string xaml = string.Empty;
        if (this.loader != null)
        {
            try
            {
                this.loader.Flush();
                xaml = this.loader.Xaml;
            }
            catch
            {
            }
        }
        return xaml;
    }
    
  7. In the set method of the Xaml property, create a try/catch block.

  8. In the body of the try block you created in the previous step, create a conditional statement that will evaluate to true if the value object is neither null nor empty. You can use the IsNullOrEmpty method defined in the String class to make this determination.

  9. In the conditional statement body, call the LoadWorkflow method, passing the value object as a parameter to the method.

    Note

    The LoadWorkflow method will be defined in the next procedure.

    set
    {
        try
        {
            if (!String.IsNullOrEmpty(value))
            {
                LoadWorkflow(value);
            }
        }
        catch
        {
        }
    }
    
  10. The code for the Xaml property of the WFEditForm class should appear similar to the following:

    public string Xaml
    {
        get
        {
            string xaml = string.Empty;
            if (this.loader != null)
            {
                try
                {
                    this.loader.Flush();
                    xaml = this.loader.Xaml;
                }
                catch
                {
                }
            }
            return xaml;
        }
        set
        {
            try
            {
                if (!String.IsNullOrEmpty(value))
                {
                    LoadWorkflow(value);
                }
            }
            catch
            {
            }
        }
    }
    
  11. In the xamlView_TextChanged method, create a try/catch block.

  12. In the body of the try block created in the previous step, set the Xaml class property equal to the Text property of the xamlView TextBox control.

    Note

    A user would receive errors while typing in Xaml code, so the body of the catch block should be empty to catch any exceptions that are thrown.

    private void xamlView_TextChanged(object sender, EventArgs e)
    {
        try
        {
            this.Xaml = this.xamlView.Text;
        }
        catch { }
    }
    

To load a new workflow definition

  1. In the OnLoad method of the WFEditForm class, call the LoadNewSequentialWorkflow method following the call to the base class implementation of OnLoad as shown in the following code:

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        LoadNewSequentialWorkflow();
    }
    
  2. In the WFEditForm class, create a new public method named LoadNewSequentialWorkflow.

  3. In the LoadNewSequentialWorkflow method, create a new SequentialWorkflowActivity instance and assign it to the currentWorkflow class variable.

  4. Set the Name property of the currentWorkflow variable equal to the "CustomSequentialWorkflow" string value.

  5. Call the LoadWorkflow method.

    Note

    The LoadWorkflow method will be defined in the next procedure.

  6. Set the Text property of the xamlView TextBox control equal to the return value of the GetCurrentXaml method.

    Note

    The GetCurrentXaml method is defined in the next step.

    The LoadNewSequentialWorkflow method should appear similar to the following code:

    public void LoadNewSequentialWorkflow()
    {
        this.currentWorkflow = new SequentialWorkflowActivity();
        currentWorkflow.Name = "CustomSequentialWorkflow";
    
        this.LoadWorkflow();
        this.xamlView.Text = GetCurrentXaml();
    }
    
  7. In the WFEditForm class, create a new method named GetCurrentXaml that will be used to retrieve the Xaml representation of the currently loaded workflow in the workflow designer.

  8. In the GetCurrentXaml method, create a IDesignerHost object named host and initialize it by calling GetService, passing the System.Type of the IDesignerHost interface as a parameter.

  9. Create a conditional statement to ensure the host object and the RootComponent property of the host object is not null (Nothing in Visual Basic).

  10. In the body of the conditional statement created in the previous step, use the WorkflowMarkupSerializer class to serialize the RootComponent property of the host object using an XmlWriter created with a StringWriter.

  11. Return the Xaml string using the ToString method of the StringWriter class created in the previous step. The GetCurrentXaml method should appear similar to the following code:

    private string GetCurrentXaml()
    {
        IDesignerHost host = (IDesignerHost)GetService(typeof(IDesignerHost));
    
        if (host != null && host.RootComponent != null)
        {
            Activity rootActivity = host.RootComponent as Activity;
    
            if (rootActivity != null)
            {
                using (StringWriter sw = new StringWriter())
                {
                    using (XmlWriter writer = XmlWriter.Create(sw))
                    {
                        WorkflowMarkupSerializer xomlSerializer = new WorkflowMarkupSerializer();
                        xomlSerializer.Serialize(writer, rootActivity);
                    }
    
                    return sw.ToString();
                }
            }
        }
    
        return "";
    }
    

To load workflow definitions

  1. In the WFEditForm class, create a private method named LoadWorkflow.

  2. In the LoadWorkflow method, use the WorkflowMarkupSerializer class to serialize the currentWorkflow class variable using an XmlWriter created with a StringWriter.

  3. Set the Xaml class property equal to the return value of the ToString method of the StringWriter object created in the previous step. The LoadWorkflow method should appear similar to the following:

    private void LoadWorkflow()
    {
        using (StringWriter stringWriter = new StringWriter())
        {
            using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter))
            {
                WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
                serializer.Serialize(xmlWriter, currentWorkflow);
                this.Xaml = stringWriter.ToString();
            }
        }
    }
    
  4. In the WFEditForm class, create a new private method named LoadWorkflow that accepts a string named xaml as a parameter and returns an ICollection object.

  5. In the LoadWorkflow method you created in the previous step, create a local WorkflowLoader variable named loader and create a new instance of that object.

  6. Set the Xaml property of the loader object you created in the previous step equal to the xaml parameter that is passed into the method.

  7. Return out of the method by calling the LoadWorkflow method, passing the WorkflowLoader object as a parameter to that method.

    Note

    You will create the overloaded LoadWorkflow method for this step in the next step.

    private ICollection LoadWorkflow(string xaml)
    {
        WorkflowLoader loader = new WorkflowLoader();
        loader.Xaml = xaml;
        return LoadWorkflow(loader);
    }
    
  8. In the WFEditForm class, create a new method named LoadWorkflow that accepts a WorkflowLoader object named loader as a parameter and returns an ICollection object.

  9. In the LoadWorkflow method you created in the previous step, create a try-catch-finally block.

  10. In the try block you created in step 9, call the SuspendLayout method.

  11. Create a new DesignSurface object named designSurface and create a new instance of it.

  12. Call the BeginLoad method of the DesignSurface object you created in the previous step, passing the WorkflowLoader object as a parameter.

  13. If the Count property of the LoadErrors collection defined in the designSurface object is greater than 0, return the LoadErrors property from the method. The try block up to this point should appear similar to the following:

    SuspendLayout();
    
    DesignSurface designSurface = new DesignSurface();
    designSurface.BeginLoad(loader);
    if (designSurface.LoadErrors.Count > 0)
        return designSurface.LoadErrors;
    
  14. Following the code you added to the try block in steps 10-13, obtain the current IDesignerHost object by calling the GetService method from the designSurface object, passing the IDesignerHost Type as a parameter.

  15. Create a conditional statement that will evaluate to true if the designerHost and the designerHost.RootComponent is not null (Nothing in Visual Basic).

  16. In the conditional statement body you created in the previous step, obtain the designer for the RootComponent by calling the GetDesigner method from the designerHost, passing the RootComponent property of the designerHost object. This method will return an IRootDesigner object.

  17. Create another conditional statement that will evaluate to true if the rootDesigner object is not null (Nothing in Visual Basic).

  18. In the conditional statement body created in the previous step, call the UnloadWorkflow method. This method will be defined in the next procedure.

  19. Set the designSurface class field equal to the designSurface local variable.

  20. Set the loader class field equal to the loader local variable.

  21. Call the GetView method from the rootDesigner object, passing ViewTechnology.Default as a parameter, and set the workflowView class field equal to the return value of that method. You will need to cast the result to a WorkflowView object because GetView returns a Object.

  22. Add the workflowView class field to the Panel control (placeHolderPanel) of the Windows Form class by calling the Add method of the Controls collection, passing the workflowView object as a parameter to that method.

  23. Ensure that the workflowView object fills the entire contents of the Panel control by setting the Dock property of the workflowView object to DockStyle.Fill.

  24. The try block that you created in steps 9-23 should appear similar to the following code:

    try
    {
        SuspendLayout();
    
        DesignSurface designSurface = new DesignSurface();
        designSurface.BeginLoad(loader);
        if (designSurface.LoadErrors.Count > 0)
            return designSurface.LoadErrors;
    
        IDesignerHost designerHost = designSurface.GetService(typeof(IDesignerHost)) as IDesignerHost;
        if (designerHost != null && designerHost.RootComponent != null)
        {
            IRootDesigner rootDesigner = designerHost.GetDesigner(designerHost.RootComponent) as IRootDesigner;
            if (rootDesigner != null)
            {
                UnloadWorkflow();
    
                this.designSurface = designSurface;
                this.loader = loader;
                this.workflowView = rootDesigner.GetView(ViewTechnology.Default) as WorkflowView;
                this.placeHolderPanel.Controls.Add(this.workflowView);
                this.workflowView.Dock = DockStyle.Fill;
            }
        }
    }
    
  25. In the finally block following the try/catch block, call the ResumeLayout method, passing true as a parameter.

  26. At the end of the LoadWorkflow method you created in the previous steps, return null (Nothing in Visual Basic) because no errors were encountered when the workflow definition was loaded.

  27. The LoadWorkflow method you created in steps 8-26 should appear similar to the following:

    private ICollection LoadWorkflow(WorkflowLoader loader)
    {
        try
        {
            SuspendLayout();
    
            DesignSurface designSurface = new DesignSurface();
            designSurface.BeginLoad(loader);
            if (designSurface.LoadErrors.Count > 0)
                return designSurface.LoadErrors;
    
            IDesignerHost designerHost = designSurface.GetService(typeof(IDesignerHost)) as IDesignerHost;
            if (designerHost != null && designerHost.RootComponent != null)
            {
                IRootDesigner rootDesigner = designerHost.GetDesigner(designerHost.RootComponent) as IRootDesigner;
                if (rootDesigner != null)
                {
                    UnloadWorkflow();
    
                    this.designSurface = designSurface;
                    this.loader = loader;
                    this.workflowView = rootDesigner.GetView(ViewTechnology.Default) as WorkflowView;
                    this.placeHolderPanel.Controls.Add(this.workflowView);
                    this.workflowView.Dock = DockStyle.Fill;
                }
            }
        }
    
        catch { }
    
        finally
        {
            ResumeLayout(true);
        }
    
        return null;
    }
    

To unload a workflow definition

  1. In the WFEditForm class, create a new private method named UnloadWorkflow.

  2. In the UnloadWorkflow method, call the GetService method, passing an IDesignerHost Type object as a parameter, and assign the return value to a new IDesignerHost local variable named designerHost.

  3. Create a new conditional statement that will evaluate to true if the designerHost object is not null (Nothing in Visual Basic) and the designerHost.Container.Components.Count property is greater than 0.

  4. In the body of the conditional statement created in the previous step, call the static DestroyObjectGraphFromDesignerHost method defined in the WorkflowLoader class, passing the designerHost object and the designerHost.RootComponent object as parameters to the method.

  5. Create a conditional statement that will evaluate to true if the designSurface object is not null (Nothing in Visual Basic).

  6. In the body of the conditional statement created in the previous step, call the Dispose method of the designSurface object and set the designSurface object equal to null (Nothing in Visual Basic).

  7. Create a conditional statement that will evaluate to true if the workflowView object is not null (Nothing in Visual Basic).

  8. In the body of the conditional statement created in the previous step, call the Remove method from the Controls collection of the Panel control (placeHolderPanel), passing the workflowView object as a parameter, call the Dispose method of the workflowView object, and set the workflowView object equal to null (Nothing in Visual Basic).

  9. The UnloadWorkflow method created in this procedure should appear similar to the following:

Compiling the Code

For information about compiling your code, see Compiling the Code.

In the next task, Task 3: Create the WorkflowMenuCommandService, you will add a service to the Workflow Designer in order to enable the workflow designer context menu and its associated workflow-specific commands defined in Windows Workflow Foundation.

See Also

Concepts

Hosting Workflow Designers

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