вторник, 26 мая 2009 г.

Взаимодействие WF с программным окружением

Продолжим разработку нашего примера с workflow. В данной части мы посмотрим как из WF взаимодествовать с программным окружением. В качестве конечного автомата возьмем конечный автомат от сюда.
В данном примере, для наглядности, давайте создадим приложение с GUI:

Кнопки соответствуют событиям, прямоугольники показывают состояние двери и замка (красный - закрыто, зеленый открыто).
Для взаимодействия с программным окружением используются следующие Activity:
1. СallExternalMethodActivity – используется для вызова метода внешнего по отношению к WF (основные свойства InterfaceType и MethodName)

2. EventDrivenActivity – используется для перехвата событий из кода внешнего по отношению к WF
3. HandleExternalEventActivity – помещается в EventDrivenActivity и ожидает события (основные свойства InterfaceType и EventName)


К событиям передаваемым из внешнего окружения в WF предъявляются следующие требования:
1. Событие должно быть потомок EventHandler
2. В качестве T должен быть любой класс потомок ExternalDataEventArgs
Кстати, конструктор ExternalDataEventArgs принимает Guid потока которому предназначается сообщение.

Для взаимодействия с WF объекты должны являться, во-первых, наследниками интерфейса помеченного атрибутом [ExternalDataExchange].
Введем два интерфейса:
  [ExternalDataExchange]
  public interface IDoor
  {
    void OpenLock();
    void OpenDoor();
    void CloseDoor();
    string GetKey();
  }
  [ExternalDataExchange]
  public interface IVisitor
  {
    event EventHandler<ExternalDataEventArgs> TestKey;
    event EventHandler<ExternalDataEventArgs> Open;
    event EventHandler<ExternalDataEventArgs> Close;
  }

* This source code was highlighted with Source Code Highlighter.

Во-вторых, если ссылка на вызывающий событие объект будет передаваться в WF объект должен быть сериализуемым.

Кстати, если методо вызываемый из WF возвращает значение, то его можно непосредственно в дизайнере привязать к полю WF, использовав для этгого свойство ReturnValue:


Добавив везде где это надо вместо CodeActivity вызов внешних методов и перехват внешних событий, получим конечный автомат вида:


Реализация интерфейса IDoor может иметь вид:
    #region IDoor Members
    public void OpenLock()
    {
      if (Dispatcher.Thread != Thread.CurrentThread)
      {
        Dispatcher.Invoke(new NoParamHandler(OpenLock), new object[] { });
      }
      else
      {
        rcDoorlock.Fill = new SolidColorBrush(Color.FromRgb(0, 255, 0));
      }
    }
    public void OpenDoor()
    {
      if (Dispatcher.Thread != Thread.CurrentThread)
      {
        Dispatcher.Invoke(new NoParamHandler(OpenDoor), new object[] { });
      }
      else
      {
        rcDoor.Fill = new SolidColorBrush(Color.FromRgb(0, 255, 0));
      }
    }
    private delegate void NoParamHandler();
    public void CloseDoor()
    {
      if (Dispatcher.Thread != Thread.CurrentThread)
      {
        Dispatcher.Invoke(new NoParamHandler(CloseDoor), new object[] { });
      }
      else
      {
        rcDoorlock.Fill = new SolidColorBrush(Color.FromRgb(255, 0, 0));
        rcDoor.Fill = new SolidColorBrush(Color.FromRgb(255, 0, 0));
      }
    }
    public string GetKey()
    {
      if (Dispatcher.Thread == Thread.CurrentThread)
      {
        return tbKey.Text;
      }
      else
      {
        return (string)Dispatcher.Invoke(new Func<string>(GetKey), new object[] { });
      }
    }
    #endregion


* This source code was highlighted with Source Code Highlighter.


Ну и в завершении как же создать host для WF. На самом деле все достаточно просто:
  1.     WorkflowRuntime workflowRuntime;
  2.     WorkflowInstance instance;
  3.     StateMachineWorkflowInstance machine;
  4.  
  5.     private void Window_Loaded(object sender, RoutedEventArgs e)
  6.     {
  7.       workflowRuntime = new WorkflowRuntime();
  8.       workflowRuntime.WorkflowStarted += delegate(object s, WorkflowEventArgs e1) { MessageBox.Show("Рабочий поток начал работу"); };
  9.       workflowRuntime.WorkflowCompleted += delegate(object s, WorkflowCompletedEventArgs e1) { MessageBox.Show("Рабочий поток закончил работу"); };
  10.       workflowRuntime.WorkflowTerminated += delegate(object s, WorkflowTerminatedEventArgs e1)
  11.       {
  12.         MessageBox.Show(e1.Exception.Message);
  13.       };
  14.       ExternalDataExchangeService externalDataExchangeService = new ExternalDataExchangeService();
  15.       workflowRuntime.AddService(externalDataExchangeService);
  16.       externalDataExchangeService.AddService(this);
  17.       
  18.       instance = workflowRuntime.CreateWorkflow(typeof(Workflow1));
  19.       machine = new StateMachineWorkflowInstance(workflowRuntime, instance.InstanceId);
  20.       instance.Start();
  21.  
  22.     }
* This source code was highlighted with Source Code Highlighter.


В строках 7-13 мы создаем среду выполнения WF и подписываемся на основные ее обработчики.
В строках 14-16 создаем сервис для взаимодействия с WF, регистрируем его в среде выполнения, и добавляем объект методы которого необходимо вызывать и события обрабатывать (в данном случае это текущая форма).
В строках 18,20 создается WF и запускается на выполнение.
В строке 19 получаем ссылку на WF в виде StateMachine которую можно использовать для проверки текущего состояния конечного автоамта, принудительного перевода из состояния в состояние и т.д.

Комментариев нет:

Отправить комментарий