Bookmark Workflow - инструмент отображения динамики процесса.

Как отобразить динамику процесса, происходящего в WorkFlow, используя, например, стандартный компонент System.Windows.Forms.ProgressBar? Оказывается, довольно просто. Для этого можно применить обычную закладку - Bookmark. Известно, что работа с закладкой может сопровождаться передачей данных. Передположим, что отображение процесса происходит в одном потоке, а сам процесс выполняется в другом. Однако что может служить контейнером для обмена данными между потоками при работе с закладкой? Очень часто используется словарь - Dictionary.
Посмотрим, как реализовать это на практике. Для этого возьмем ранее описанный проект AccessTodBase и добавим на форму ProgressBar, он будет отображать процесс переписи данных из базы данных Access в dBase. Ну, а сам процесс переписи данных будет выполняться в дочернем потоке в ActivityTransfer.
Представлен рабочий проект MS Visual Studio 2012 WorkFlow + Windows Forms Application. Кстати, который можно загрузить бесплатно.
1. ActivityTransfer.
Эта Activity переносит данные из таблицы Access в файл dBase. Что в ней нового? Во-первых, появилась закладка: bookmark. Затем, введен новый метод для выполнения закладки: ResumeBookmark. В этот метод перенесена функциональность по записи в таблицу dataTableDbf. Данные между потоками передаются через контейнер Dictionary values. И, наконец, обратите внимание, закладка создана как BookmarkOptions.MultipleResume. Добавлю к сказанному, что за одну итерацию пишется десятая часть содержимого таблицы Access. Можно, конечно, установить и другое число.
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.ComponentModel;
using System.Data;
using System.Data.OleDb;
using System.IO;

namespace AccessTodBase
{
    public class ActivityTransfer : NativeActivity
    {
        // Define an activity input arguments of type string
        [RequiredArgument]
        [DefaultValue(null)]
        public InArgument<string>       PathAccess          { get; set; }

        [RequiredArgument]
        [DefaultValue(null)]
        public InArgument<string>       PathdBase           { get; set; }
        
        [RequiredArgument]
        [DefaultValue(null)]
        public OutArgument<string>      ResultTransfer      { get; set; }
  
        // Define private temporary
        private OleDbConnection     connectAccess, connectDbf   = null;
        private OleDbCommand        commandAccess, commandDbf   = null;
        private OleDbDataAdapter    adapterAccess, adapterDbf   = null;
        private OleDbDataReader     reader                      = null;
        private DataTable           dataTableAccess             = null;
        private DataTable           dataTableDbf                = null;
        private DataRow             rowDbf,row                  = null;
        private string              stringCommandAccessSQL      = null;
        private string              stringCommanddBaseSQL       = null;
        private string              readdBase                   = null;
        private int i                                           = 0;

        // Container and Bookmark
        Dictionary<string, int> values                          = null;
        private Bookmark bookmark                               = null;

        protected override void Execute(NativeActivityContext context)
        {
            string connectStringAccess  =   context.GetValue(PathAccess);
            string connectStringdBase   =   context.GetValue(PathdBase);
            try
            {
                i = 0;
                if (String.IsNullOrEmpty(connectStringAccess)) throw new ArgumentNullException("Value", "File Name Access is null");
                if (String.IsNullOrEmpty(connectStringdBase)) throw new ArgumentNullException("Value", "File Name dBase is null");
                // Read data Access table
                stringCommandAccessSQL = @"SELECT OutputTable.* FROM OutputTable";
                connectAccess = new OleDbConnection(connectStringAccess);
                connectAccess.Open();
                commandAccess = new OleDbCommand(stringCommandAccessSQL, connectAccess);
                adapterAccess = new OleDbDataAdapter(commandAccess);
                reader = commandAccess.ExecuteReader();
                dataTableAccess = new DataTable();
                dataTableAccess.Load(reader);
                connectAccess.Close();

                // Connect dBase
                stringCommanddBaseSQL = @"INSERT INTO INPUTP (NUM, FAM, IM, OT) VALUES (?, ?, ?, ?)";
                readdBase = @"SELECT INPUTP.* FROM INPUTP";
                connectDbf = new OleDbConnection(connectStringdBase);
                connectDbf.Open();

                commandDbf = new OleDbCommand(readdBase, connectDbf);
                adapterDbf = new OleDbDataAdapter(commandDbf);

                adapterDbf.InsertCommand = new OleDbCommand(stringCommanddBaseSQL, connectDbf);
                adapterDbf.InsertCommand.Parameters.Add("@NUM", OleDbType.Numeric, 10, "NUM");
                adapterDbf.InsertCommand.Parameters.Add("@FAM", OleDbType.Char, 40, "FAM");
                adapterDbf.InsertCommand.Parameters.Add("@IM", OleDbType.Char, 40, "IM");
                adapterDbf.InsertCommand.Parameters.Add("@OT", OleDbType.Char, 40, "OT");

                dataTableDbf = new DataTable();
                adapterDbf.Fill(dataTableDbf);

                // Start bookmark
                bookmark = context.CreateBookmark("Complete", ResumeBookmark, BookmarkOptions.MultipleResume);
            }
            catch (Exception ex) { ResultTransfer.Set(context, ex.ToString()); }            
        }

        protected override bool CanInduceIdle {get { return true; }}

        // ResumeBookmark
        public void ResumeBookmark(NativeActivityContext context, Bookmark bookmark, object obj)
        {
            // Obj is not empty?
            if (obj != null)
            {
                values = obj as Dictionary<string, int>;
                // Tenth rows
                for (int j = 0; j < dataTableAccess.Rows.Count / 10; j++)
                {
                    // End recording
                    if (i >= dataTableAccess.Rows.Count)
                    {
                        adapterDbf.Update(dataTableDbf);
                        connectDbf.Close();
                        values["End"] = 1;
                        ResultTransfer.Set(context, "O.K.");
                        break;
                    }
                    row = dataTableAccess.Rows[i];
                    rowDbf = dataTableDbf.NewRow();
                    rowDbf["NUM"]   = row[0];
                    rowDbf["FAM"]   = row[1];
                    rowDbf["IM"]    = row[2];
                    rowDbf["OT"]    = row[3];
                    // Add row
                    dataTableDbf.Rows.Add(rowDbf);
                    ++i;
                }
                // Set a complete and current number of Rows
                values["Current_value"] = i;
                values["CountRow"] = dataTableAccess.Rows.Count;                
            }
        }
    }
}
2. Основной поток, запуск WoprkFlow, Timer.
Текст приведен "как есть". Обратите внимание на обработчики событий wfApp.Idle и wfApp.Completed - это останов по закладке и завершение работы дочернего потока соответственно. В методе myTimer_Tick значения текущего и полного количества строк извлекаются из контейнера и отображаются на форме. Отображается и значение ProgressBar.
Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Activities;
using System.Threading;
using System.Timers;
using AccessTodBase;

namespace AccessTodBase
{
    public partial class SetParametersForm : Form
    {
        WorkflowApplication wfApp       = null;
        AutoResetEvent waitHandler      = null;
        WorkFlowTransfer addwf          = null;
        WorkflowInvoker  invoker        = null;

        private string connectStrAccess = string.Empty;
        private string connectStrDbf    = string.Empty;
        private int codeActivity        = 0;
        public int pauseWorkflow        = 0;

        Dictionary<string, int> Data = new Dictionary<string, int>();

        public SetParametersForm()
        {
            InitializeComponent();
            waitHandler = new AutoResetEvent(false);
            Data.Add("Current_value", 0);
            Data.Add("End", 0);
            Data.Add("CountRow", 0);
        }

        // Start transfer data
        private void Start_Click(object sender, EventArgs e)
        {            
            try
            {
                if (String.IsNullOrEmpty(textBoxAccess.Text))   throw new ArgumentNullException("Value", "File Name Access is null");
                if (String.IsNullOrEmpty(textBoxdBase.Text))    throw new ArgumentNullException("Value", "File Name dBase is null");

                // Make visible components
                Start.Enabled               = false;
                progressBarTransfer.Visible = true;
                progressBarTransfer.Value   = 2;
                progressBarTransfer.Focus();
                labelNumber.Text            = "0";
                labelCount.Text             = "0";
                labelNumber.Visible         = true;
                labelOf.Visible             = true;
                labelCount.Visible          = true;

                // Data Dictionary
                Data["End"]                 = 0;
                Data["Current_value"]       = 0;
                Data["CountRow"]            = 0;

                // Flag pause workflow
                pauseWorkflow               = 0;

                // Define local temporary
                string result           = string.Empty;
                string fileDirectory    = String.Empty;
                
                // Set code operation for ActivityTransfer
                codeActivity = 3;

                // Set Connect string for Access
                connectStrAccess    = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + textBoxAccess.Text;
                
                // Set connect string for Dbf
                int index           = textBoxdBase.Text.LastIndexOf(@"\");
                fileDirectory       = textBoxdBase.Text.Substring(0, index + 1);
                connectStrDbf       = @"Provider=Microsoft.ACE.OLEDB.12.0; Data Source=" + fileDirectory + @"; Extended Properties=dBASE IV";

                // Set AutoResetEvent
                waitHandler = new AutoResetEvent(false);

                // Create WorkFlowTransfer
                WorkFlowTransfer addwf  = new WorkFlowTransfer()
                {
                    PathAccessArgument  = new InArgument<String>(connectStrAccess),
                    PathdBaseArgument   = new InArgument<String>(connectStrDbf),
                    CodActivityArgument = new InArgument<int>(codeActivity)
                };

                // Create WorkflowApplication
                wfApp = new WorkflowApplication(addwf);
              
                // Create event handler wfApp.Completed
                wfApp.Completed = (workflowApplicationCompletedEventArgs) =>
                {
                    result = workflowApplicationCompletedEventArgs.Outputs["ResultWorkFlow"].ToString();
                    // Completeness check process
                    if (result != "O.K.") MessageBox.Show("Errot transfer Activity ...");
                    waitHandler.Set();
                };

                // Stop bookmark
                wfApp.Idle = (WorkflowApplicationIdleEventArgs) =>
                {
                    string idleResult = WorkflowApplicationIdleEventArgs.Bookmarks[0].BookmarkName.ToString();
                    waitHandler.Set();
                    pauseWorkflow = 1;
                };

                // Start workflow
                myTimer.Enabled = true;
                wfApp.Run();
            }
            catch (Exception ex)              
            {
                MessageBox.Show(ex.ToString(), "Error argument.", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }           
        }

        // Select Path Access DataBase
        private void PathAccess_Click(object sender, EventArgs e)
        {
            try
            {
                // Set code Activity
                codeActivity = 1;
                addwf = new WorkFlowTransfer() { CodActivityArgument = new InArgument<int>(codeActivity) };
                invoker = new WorkflowInvoker(addwf);
                invoker.InvokeCompleted += delegate(object obj, InvokeCompletedEventArgs args)
                {
                    textBoxAccess.Text = args.Outputs["ResultWorkFlow"].ToString();
                    waitHandler.Set();
                };
                invoker.InvokeAsync();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), "Error select Access", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        // Select path dBase DataBase
        private void PathdBase_Click(object sender, EventArgs e)
        {
            try
            {
                // Set code Activity
                codeActivity = 2;
                addwf = new WorkFlowTransfer() { CodActivityArgument = new InArgument<int>(codeActivity) };
                invoker = new WorkflowInvoker(addwf);
                invoker.InvokeCompleted += delegate(object obj, InvokeCompletedEventArgs args)
                {
                    textBoxdBase.Text = args.Outputs["ResultWorkFlow"].ToString();
                    waitHandler.Set();
                };
                invoker.InvokeAsync();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), "Error select dBase", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        // Tick metod timer
        private void myTimer_Tick(object sender, EventArgs e)
        {
            //  Pause in workflow?
            if (pauseWorkflow == 1)
            {
               wfApp.ResumeBookmark("Complete", Data);
               // Add step ProgressBar
               progressBarTransfer.PerformStep();
               // Display the data
               labelNumber.Text = Data["Current_value"].ToString();
               labelCount.Text  = Data["CountRow"].ToString();
               // The process is completed?
                if (Data["End"] == 1)
                {
                    myTimer.Enabled             = false;
                    progressBarTransfer.Visible = false;
                    labelNumber.Visible         = false;
                    labelOf.Visible             = false;
                    labelCount.Visible          = false;
                    Start.Enabled               = true;
                }
                pauseWorkflow = 0;   
            }
        }     
    }
}

Visual Studio 2012 проект приложения можно загрузить
C уважением, Евгений Вересов.
25.04.2013 года.
>