Click here to Skip to main content
Click here to Skip to main content

Methods of WPF – WF Data Exchange, Direct WPF – WF Data Binding

, 21 Sep 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
Small tutorial for WPF - WF data exchange and databinding implementation in VS2010.

Contents

Introduction

If you want to use WF 4.0 in a WPF based Desktop application, you must ensure data exchange between the workflow and WPF controls. In this article (wrote in tutorial form), I would like to discuss some tips of data exchange implementations; first of all, «direct» data binding between WPF and WF. I hope that your feedback will help me improve and extend the proposed solutions.

This tutorial demonstrates the creation of simple WPF/WF applications with «direct» data binding between the WPF window and the WF variables, in six steps. The resulting application code is available in the download link above.

Run a workflow in a WPF application

  1. Create a “WpfApplication1” project using the WPF Application template.
  2. Add the “ActivityLibrary1” project using the Workflow Activity Library template.
  3. Add “Flowchart1.xaml” to ActivityLibrary1 using the “Add>New Item…” menu and a Workflow Flowchart template. Add a “WriteLine” activity with Text="Flowchart1 completed" to Flowchart1 using Workflow Designer:
  4. Add System.Activities and ActivityLibrary1 references to “WpfApplication1”. Add a WorkflowInstance member to Window1.xaml.cs:
  5. WorkflowInstance _instance;
  6. Add a StackPanel with a "Run Flowchart1" button to Window1.xaml:
  7. <StackPanel>
         <Button Content="Run Flowchart1" 
           HorizontalAlignment="Left" Name="button1" 
           Margin="20" Click="button1_Click" />
    </StackPanel> 
  8. Add a click event handler to Window1.xaml.cs:
  9. private void button1_Click(object sender, RoutedEventArgs e)
    {
         _instance = new WorkflowInstance(new Flowchart1());
         _instance.Run();
    }

Compile and start a solution WpfApplication1. Now we can start a workflow instance using a button click and check the “Flowchart1 completed" text in the output window. We completed here a simple but important preparation for the more interesting thing!

Databinding of FrameworkElement and workflow variables

  1. Add a “variable1” string variable with a "MyVariable" default value to the Flowchart1 workflow in the workflow designer.
  2. Create an interface IContextProvider in ActivityLibrary1 using the “Add>Class” menu and the interface template
  3. .
    using System.Activities;
    
    namespace ActivityLibrary1
    {
        public interface IContextProvider
        {
            WorkflowDataContext WorkflowContext { get; set; }
        }
    }
  4. Add a SetDataContext workflow element to ActivityLibrary1 using “Add>New Item…” and the Workflow > Workflow Element template. Set it as the first activity in Flowchart1. Note: It is very important that the SetDataContext activity does not have arguments. Only in this case will it have the same context as the workflow.
  5. public class SetDataContext: CodeActivity
    {               
        protected override void Execute(CodeActivityContext context)
        {
            IContextProvider rootDataContext = context.GetExtension<icontextprovider>();
    
            rootDataContext.WorkflowContext = context.DataContext;
        }        
    }
  6. Add a 5 sec delay activity to Flowchart1 after SetDataContext:
  7. Add a “_workflowContext” member to Window1.xaml.cs:
  8. WorkflowDataContext _workflowContext;
  9. Add the IContextProvider interface to our Window1 class – we will store the workflow context in it: Note: we need a Dispatcher to set Window1.DataContext from the SetDataContext activity because a workflow will not be run on a GUI thread.
  10. public partial class Window1 : Window, IContextProvider
    public WorkflowDataContext WorkflowContext
    {
        get { return _workflowContext; }
        set
        {
            this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                (Action)delegate() { this.DataContext = value; });
    
            _workflowContext = value;
        }
    }

    Note: we can use the DataContext of the child activities in the same way.

  11. Add a workflow instance OnCompleted event handler to button1_Click. Remove Window1.DataContext in it. After the workflow is completed, the context will be disposed and cannot be used in WFP!
  12. _instance.OnCompleted = delegate(WorkflowCompletedEventArgs we)
    {
         this.Dispatcher.Invoke(DispatcherPriority.Normal,
             (Action)delegate() { this.DataContext = null; });
    };
  13. Add a IContextProvider implementation (Window1) to Extensions before the instance is run for providing access to this interface from the workflow activities:
  14. __instance.Extensions.Add(this);
  15. Add a TextBox to Window1.xaml. Add a binding to the text with variable1: Now, after starting the workflow, it will set the “MyVariable” text in the TextBox. After a 5 sec delay, the workflow will be completed, DataContext of Window1 will be removed, and the TextBox will be again empty.
  16. <TextBox Margin="20" Text="{Binding variable1, UpdateSourceTrigger=PropertyChanged}" 
             HorizontalAlignment="Left"  Name="textBox1" VerticalAlignment="Top"/>

Now after starting the workflow, it will set the “MyVariable” text in the TextBox. After a 5 sec delay, the workflow will be completed, the DataContext of Window1 will be removed, and TextBox will be empty again.

Resume bookmark from WPF

Okay, but maybe we want to control the workflow from the WPF window. In this case, we need to supply some events in the GUI:

  1. Add a “ReadBookmark” activity to ActivityLibrary1 using “Add>New Item…” and the Workflow Element Template. Add a string “Command” input argument. Set the input command as the result of the activity in the Continue() method. Build ActivityLibrary1.
  2. public class ReadBookmark<t> : NativeActivity<t>
    {         
         // Arguments:
         public InArgument<string>  Command { get; set; }
       
         protected override void Execute(ActivityExecutionContext context)
         {
             context.CreateNamedBookmark(Command.Get(context), 
                     new BookmarkCallback(this.Continue));
         }
         void Continue(ActivityExecutionContext context, 
                       Bookmark bookmark, object obj)
         {
             this.Result.Set(context, Command.Get(context));
         }
    }
  3. Add a Pick activity to Flowchart1 with a PickBranch instead of a 5 sec delay. Add ReadBookmark activities as a trigger to PickBranch. Set the Command argument of ReadBookmark to a value “stop".
  4. Now, the workflow will wait and resume the “stop” bookmark to continue. We can resume this bookmark with a new Window1 button “Stop Flowchart1”:
  5. <Button Content="Stop Flowchart1" 
       HorizontalAlignment="Left" Margin="20" 
       Name="button2" VerticalAlignment="Top" 
       Click="button2_Click" />
    private void button2_Click(object sender, RoutedEventArgs e)
    {
         _instance.ResumeBookmark("stop", null);
    }
  6. For the control workflow state, add a _isWorkflowStarted bool member to Window1.xaml.cs. Set _isWorkflowStarted to true before the workflow is started:
  7. _isWorkflowStarted = true;
    _instance.Run();

    And, false when the workflow is completed:

    _instance.OnCompleted = delegate(WorkflowCompletedEventArgs we)
    {
         this.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)delegate()
         {
              this.DataContext = null; });
          _isWorkflowStarted = false;
    };
  8. Add a window closed event handler and terminate the workflow in it, if workflow has started:
  9. private void Window_Closed(object sender, EventArgs e)
    {
         if (_instance!=null && _isWorkflowStarted==true)
             _instance.Terminate("Main Window closed");
    }

Now, after starting the workflow ,we see the “MyVariable” text. After this, click the Stop button. The “stop” bookmark will be resolved and flowchart1 will be completed. The DataContext of Window1 will be removed and TextBox will be empty again.

Control UI state from the workflow

Now, we will carefully enable the state of the buttons:

  1. Set the initial IsEnabled state of the stop button to false.
  2. <Button Content="Stop Flowchart1" IsEnabled="False">
  3. Add the UpdateUIState() method to the IContextProvider interface.
  4. public interface IContextProvider
    {
       WorkflowDataContext WorkflowContext { get; set; }
       void UpdateUIState();
    }
  5. Add an implementation of UpdateUIState() to the Window1 class. Set the stop button IsEnabled property to depend on the created workflow bookmarks:
  6. public void UpdateUIState()
    {
       this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)delegate() 
       {
           button2.IsEnabled = false; 
           foreach (BookmarkInfo bookmark in _instance.GetAllBookmarks())
           {
               switch (bookmark.BookmarkName)
               {
                   case "stop":
                       button2.IsEnabled = true;
                       break;
               }
            }
        });
    }
  7. Add the “UpdateUI” activity to ActivityLibrary1 using “Add>New Item…” and the Workflow Element Template. Build ActivityLibrary1.
  8. public class UpdateUI: CodeActivity
    { 
       protected override void Execute(CodeActivityContext context)
       {
          IContextProvider rootDataContext = context.GetExtension<IContextProvider>();
          rootDataContext.UpdateUIState();
       } 
    }
  9. Add the UpdateUI code activity to Flowchart1 after a Pick activity:
  10. Update UI using ReadBookmark.Execute:
  11. //update UI
    IContextProvider rootDataContext = context.GetExtension<IContextProvider>();
    rootDataContext.UpdateUIState();
  12. Disable button1 when workflow starts and enable it if the workflow has completed.
  13. private void button1_Click(object sender, RoutedEventArgs e)
    {
        button1.IsEnabled = false;
        
        //    
        _instance.OnCompleted = delegate(WorkflowCompletedEventArgs we)
        {
            this.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)delegate()
            {
                this.DataContext = null;
                button1.IsEnabled = true;
            });
    
            _isWorkflowStarted = false;
        }
    }; 

Now after application starts, the button stop is disabled. Only when the workflow is started and the bookmark “stop” is created, will the stop button be enabled. Great!

Create WPF controls in a workflow

Of course, we can create some GUI direct in our workflow:

  1. Add the WpfCustomControlLibrary1 project using the “Add>New Project .. ” menu and the Window WPF Custom Control Library template. Add “WorkflowWindow” using the “Add>New Item…” and the WPF Window template. Add a reference to System.Activities to the WpfCustomControlLibrary1 project. Add a “Workflow” property to “WorkflowWindow”:
  2. public WorkflowInstance Workflow { get; set; }
  3. Add a StackPanel with a TextBox to “WorkflowWindow.xaml” with binding to variable1:
  4. <TextBox Margin="20" Text="{Binding variable1}" HorizontalAlignment="Left"/>

    Note: to avoid conflicts between Workflow and WPF code we make separate projects.

    Note: We use the dialog window for simplicity; for real applications, WPF controls can be used, of course.

  5. Add a “Close” button to “WorkflowWindow.xaml”. Close the window by clicking this button. Add a Closed handler to “WorkflowWindow.xaml”. Resume the “close” bookmark on window close:
  6. void OnClosed(Object sender, EventArgs e) 
    {
        Workflow.ResumeBookmark("close", null);
    }
    <Button Content="Close" Margin="20" 
       HorizontalAlignment="Left" Click="button1_Click" />
    private void button1_Click(object sender, RoutedEventArgs e)
    {
        Close();
    }
  7. Add a workflow instance to the extensions on workflow start in the button1_Click() method of Window1.cs:
  8. _instance.Extensions.Add(_instance);
  9. Add a reference to WpfCustomControlLibrary1 in ActivityLibrary1.
  10. Create an “OpenWindowWorkflowElement in ActivityLibrary1 using “Add>New Item …” and the Workflow>Workflow Element template. It will create a WorkflowWindow, set its Workflow property to the workflow instance, set the WorkflowWindow DataContext to the workflow context, and show the window as a dialog:
  11. public class OpenWindow: CodeActivity
    { 
        protected override void Execute(CodeActivityContext context)
        {
            IContextProvider rootDataContext = context.GetExtension<IContextProvider>();
            WorkflowInstance instance = context.GetExtension<WorkflowInstance>();
            Application.Current.Dispatcher.BeginInvoke(
                        DispatcherPriority.Normal, (Action)delegate()
            {
                WorkflowWindow1 window = new WorkflowWindow1();
                window.DataContext = rootDataContext.WorkflowContext;
                window.Workflow = instance;
                window.ShowDialog(); 
            });
        }
    }
  12. Add an “Open” button to Window1.xaml. Resume the “open” bookmark on the “open” button click. Update the button in UpdateUIState():
  13. <Button Content="Open" IsEnabled="False" 
       HorizontalAlignment="Left" Margin="20" 
       Name="button3" Click="button3_Click" />
    private void button3_Click(object sender, RoutedEventArgs e)
    {
        _instance.ResumeBookmark("open", null);
    }
    
    public void UpdateUIState()
    {
       this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)delegate()
       {
          button2.IsEnabled = false;
          button3.IsEnabled = false;
          foreach (BookmarkInfo bookmark in 
                   _instance.GetAllBookmarks(new TimeSpan(0, 0, 1)))
          {
             switch (bookmark.BookmarkName)
             {
                 case "stop":
                    button2.IsEnabled = true;
                    break;
                 case "open":
                    button3.IsEnabled = true;
                    break;
             }
         }
       });
    }
  14. Add a _command string variable to Flowchart1workflow. Add a PickBranch with the ReadBookmark activity as the Trigger to the Pick activity using the workflow designer. Set ReadBookmark.Command to “open”. Set ReadBookmark.Result to _command.
  15. <p:Pick>
       <p:PickBranch>
         <p:PickBranch.Trigger>
           <a:ReadBookmark x:TypeArguments="x:String" 
              Command="[&quot;stop&quot;]" 
              Result="[_command]" />
         </p:PickBranch.Trigger>
       </p:PickBranch>
       <p:PickBranch>
         <p:PickBranch.Trigger>
           <a:ReadBookmark x:TypeArguments="x:String" 
             Command="[&quot;open&quot;]" Result="[_command]" />
         </p:PickBranch.Trigger>
       </p:PickBranch>
    </p:Pick> 
  16. Add a FlowSwitch with the expression “_command” and two cases to Flowchart1. If the _command variable is “open”, then switch to the OpenWindow activity. In the default case (_command variable is “stop”), switch to the WriteLine activity with Text="Flowchart1 completed”.
  17. Add a Pick activity to Flowchart1 after the OpenWindow activity. Add a PickBranch activity with ReadBookmark activities as Trigger. Set the Command argument of ReadBookmark to the value “close" and save the result in the _command variable. Add a FlowStep from this activity to the first Pick activity in Flowchart1.

Now, on the Open button click, we can open a WPF window from the workflow and change variable1 in this window.

Error handling

Advanced error handling lets you quickly find and correct system errors. Full error handling depends on the requirements (use cases) of the concrete application, but this minimal error handling has to be implemented:

  1. We use Application.Current.Dispatcher to invoke the necessary methods on the GUI thread. We can handle all unhandled Dispatcher exceptions in a special handler:
  2. void Dispatcher_UnhandledException(object sender, 
                    DispatcherUnhandledExceptionEventArgs e)
    {
        // TODO: error handling
    }
  3. Unhandled exceptions from the workflow can be handled in OnUnhandledException:
  4. _instance.OnUnhandledException = delegate(WorkflowUnhandledExceptionEventArgs we)
    {
        // TODO: error handling
    };

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Alexey Shalnov

Germany Germany
I'm a Russian (MS, Physics, Lomonosov Moscow State University) Software Engineer living in Berlin, Germany since 1999. I have a long experience in Windows programming and have been developing large GUI, Real Time Enterprise Client/Server and Database C++/C# commercial applications. I am interested in different aspects of the interop of MFC and WinForms/WPF applications.
 
Try last version of MfcAdapter 2.2 VS2010 (Date published: 04.04.2012) from my home page:
 
http://home.arcor.de/alexeyshalnov/home/index.htm

Comments and Discussions

 
GeneralVS 2010 beta 2 PinmemberMember 313218218-Jan-10 3:16 
GeneralRe: VS 2010 beta 2 PinmemberAlexey Shalnov18-Jan-10 3:44 
GeneralRe: VS 2010 beta 2 PinmemberMember 313218218-Jan-10 23:05 
GeneralGreat article! PinmemberProgramm3r22-Sep-09 2:04 
GeneralRe: Great article! PinmemberProgramm3r22-Sep-09 21:19 
So why the low votes?? Mad | :mad: Badger | [badger,badger,badger,badger...]
 

The only programmers that are better C# programmers, are those who look like this -> Green Alien | [Alien]

 

Java | [Coffee] Programm3r

My Blog: ^_^

GeneralRe: Great article! PinmvpSacha Barber22-Sep-09 21:33 
GeneralRe: Great article! PinmvpPete O'Hanlon24-Sep-09 11:57 
GeneralRe: Great article! Pinmemberranjan_namitaputra2-Oct-09 11:37 
GeneralThis is cool and very timely [modified] PinmvpSacha Barber21-Sep-09 23:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.141015.1 | Last Updated 22 Sep 2009
Article Copyright 2009 by Alexey Shalnov
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid