Export (0) Print
Expand All
1 out of 3 rated this helpful - Rate this topic

Using Windows Workflow Foundation with Windows Presentation Foundation

.NET Framework 3.5

Windows Presentation Foundation (WPF) is one of the main pillars of .NET Framework 3.0. It provides developers with a unified programming model for building rich applications that incorporate UI, media, and documents; however, when using WPF and WF together, you must be aware of several factors in communicating locally between the two technologies.

A derived System.Windows.Window type implementation has been separated into the following sections to show how to communicate locally between a WPF application and your workflow.

Using the WPF Host to Implement your Communication Interface

The following is a local communications interface and custom ExternalDataEventArgs implementation that enables you to communicate with your host application.

[ExternalDataExchange]
public interface IWPFHostCommService
{
    void DoSomeWork();
    event EventHandler<WPFHostEventArgs> Event1;
}

[Serializable]
public class WPFHostEventArgs : ExternalDataEventArgs
{
    string message;

    public WPFHostEventArgs(Guid instanceID)
        : base(instanceId)
    {
    }

    public string Message 
    {
        get
        {
            return message;
        }

        set
        {
            message = value;
        } 
    }
}

The workflow in this example is fairly simple. The HandleExternalEventActivity activity waits for the host application to fire Event1. When the host application does this, it passes in a string through the Message property defined in the custom WPFHostEventArgs class. If the host does not fire the event before the TimeoutDuration expires, the workflow will finish executing. The workflow definition is as follows:

[C#]
public sealed class Workflow1: SequentialWorkflowActivity
{
    
    private EventDrivenActivity eventDrivenActivity2;
    private EventDrivenActivity eventDrivenActivity1;
    private ListenActivity listenActivity1;
    private DelayActivity delayActivity1;
    private HandleExternalEventActivity handleExternalEventActivity1;

    public Workflow1()
    {
        InitializeComponent();
    }

[System.Diagnostics.DebuggerNonUserCode]
private void InitializeComponent()
{
    this.CanModifyActivities = true;
    this.delayActivity1 = new System.Workflow.Activities.DelayActivity();
    this.handleExternalEventActivity1 = new System.Workflow.Activities.HandleExternalEventActivity();
    this.eventDrivenActivity2 = new System.Workflow.Activities.EventDrivenActivity();
    this.eventDrivenActivity1 = new System.Workflow.Activities.EventDrivenActivity();
    this.listenActivity1 = new System.Workflow.Activities.ListenActivity();
    // 
    // delayActivity1
    // 
    this.delayActivity1.Name = "delayActivity1";
    this.delayActivity1.TimeoutDuration = System.TimeSpan.Parse("00:30:00");
    // 
    // handleExternalEventActivity1
    // 
    this.handleExternalEventActivity1.EventName = "Event1";
    this.handleExternalEventActivity1.InterfaceType = typeof(WpfApplication1.IWPFHostCommService);
    this.handleExternalEventActivity1.Name = "handleExternalEventActivity1";
    this.handleExternalEventActivity1.Invoked += new System.EventHandler<System.Workflow.Activities.ExternalDataEventArgs>(this.handleExternalEventActivity1_Invoked);
    // 
    // eventDrivenActivity2
    // 
    this.eventDrivenActivity2.Activities.Add(this.delayActivity1);
    this.eventDrivenActivity2.Name = "eventDrivenActivity2";
    // 
    // eventDrivenActivity1
    // 
    this.eventDrivenActivity1.Activities.Add(this.handleExternalEventActivity1);
    this.eventDrivenActivity1.Name = "eventDrivenActivity1";
    // 
    // listenActivity1
    // 
    this.listenActivity1.Activities.Add(this.eventDrivenActivity1);
    this.listenActivity1.Activities.Add(this.eventDrivenActivity2);
    this.listenActivity1.Name = "listenActivity1";
    // 
    // Workflow1
    // 
    this.Activities.Add(this.listenActivity1);
    this.Name = "Workflow1";
    this.CanModifyActivities = false;
}

    private void handleExternalEventActivity1_Invoked(object sender, ExternalDataEventArgs e)
    {
        // Perform some action after Event1 has been routed to the 
        // HandleExternalEventActivity activity.
    }
}

You can instantiate your workflow instance from almost any entry point within the WPF application. The following example performs most of the initialization in an overridden version of the Window.OnInitialized method.

public partial class Window1 : System.Windows.Window, IWPFHostCommService
{
    // Declare WorkflowRuntime and WorkflowInstance objects that will 
    // be used in various methods of the WPF host application.
    internal WorkflowRuntime runtime = null;
    internal WorkflowInstance instance = null;

    // Define a delegate for use in updating a listbox in the
    // host application's UI.
    delegate void MessageUpdateDelegate(string message);

    public Window1()
    {
        InitializeComponent();
        this.button1.Click += new RoutedEventHandler(button1_Click);
    }

    protected override void OnInitialized(EventArgs e)
    {
        // Call the base implementation of OnInitialized so that the 
        // Initialized event fires and sets up fundamental elements of
        // your Window object.
        base.OnInitialized(e);
        try
        {
            runtime = new WorkflowRuntime();
            this.runtime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(runtime_WorkflowCompleted);
            this.runtime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(runtime_WorkflowTerminated);
            ExternalDataExchangeService dataService = new ExternalDataExchangeService();
            runtime.AddService(dataService);

            // Because your derived Window type implements your 
            // workflow local communication interface, use "this"
            // as the local communication service to add to the 
            // ExternalDataExchangeService.
            dataService.AddService(this);
            runtime.StartRuntime();
            instance = runtime.CreateWorkflow(typeof(Workflow1));
            instance.Start();
        }
        catch (Exception exc)
        {
            if (runtime.IsStarted)
            {
                runtime.StopRuntime();
            }
            this.UIMessageUpdate(exc.Message);
        }
    }

    void button1_Click(object sender, RoutedEventArgs e)
    {
        this.DoSomeWork();
        this.button1.IsEnabled = false;
    }

    // Before the WPF application closes, stop the workflow runtime.
    protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
    {
        if (runtime.IsStarted)
        {
            runtime.StopRuntime();
        }

            base.OnClosing(e);
    }
}

Updating the WPF Host UI

WPF stipulates that no background threads can update UI elements (because they are on the UI thread), so any calls made from a thread other than the main UI thread that want to update a UI element must use the Dispatcher object to do so.

The Dispatcher object contains a queue of work items for the UI thread. The Dispatcher selects work items on a priority basis and runs each one to completion.

The following code example shows how the Dispatcher object is used to update a Listbox element with messages from the WorkflowTerminated and WorkflowCompleted event handlers.

// This method adds a new message to the Listbox UI element.
void UIMessageUpdate(string message)
{
    this.listBox1.Items.Add(message);
}

void runtime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
{
    this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new MessageUpdateDelegate(UIMessageUpdate), string.Format("\n{0}\n", e.Exception.InnerException.Message));
}

void runtime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
{
    this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new MessageUpdateDelegate(UIMessageUpdate), "The workflow instance has completed.");
}

For more information about the WPF threading model, see Threading Model.

Implement your Local Communication Interface

As with any other local communication service, you must implement the method(s) defined on your communication interface and include any events as well. Add this method implementation to your Window1 class.

public void DoSomeWork()
{
    // WPFHostEventArgs is derived from ExternalDataEventArgs and 
    // contains a property called "Message" that stores a string that
    // is passed back from the WPF application to the workflow.
    WPFHostEventArgs args = new WPFHostEventArgs(this.instance.InstanceId);
    args.Message = "This message is from the DoSomeWork method implementation.";

    // Raise Event1 to the workflow.
    Event1(null, args);
}

public event EventHandler<WPFHostEventArgs> Event1;

See Also

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback

Community Additions

ADD
Show:
© 2014 Microsoft. All rights reserved.