WF 工作流(1)
选择Workflow Console Application,如图1-1,输入名字Chapter01。为这个解决方案选择一个适合的路径。
图1-1:创建一个新的工作流项目
一个简单的工作流
该这个模板生成的一个Program.cs文件,这个文件执行控制台应用程序。它还生成一个Workflow1.xaml文件,用它来定义工作流流程中的活动。如果你开发过
Windows Presentation Framework (WPF)应用程序,你可能熟悉的XAML,它语法类似XML,用于声明方案的内容。而不是用label, textbox, 和grid等等,而且这
文件将包含在您的工作流程定义的活动派生的活动。VS2010年提供了一个设计器,让您以图形方式查看和编辑这些活动。
IDE的探索
图1-2演示了Visual Studio 2010的集成开发环境(IDE)的典型布局。在左边的工具箱中包含了供你使用的内置活动和你自定义的活动。我展开了的常用活动的分组。
解决方案资源管理器和属性窗口在右边。底部窗口包含一个错误列表和输出窗口等。
图1-2.典型的Visual Studio 2010 IDE
WF4.0的设计器是在中间。在右下角有一个缩放控件。在版本4.0中,工作流设计会有点长,这就成了一个很方便的功能,能帮助我们查看“大图片”或找到一个特定的活动。在左下方有三个用于显示变量(Variables),参数(Arguments),引用的程序集(Imports)的控件。当您单击变量(Variables)控件,出现一个窗口显示现有的变量,如图1-3所示。要关闭这个窗口,再次单击变量控件。
图1-3.查看工作流变量
如果你把workflow看成一个类,变量就是类的成员。你可以使用它们来存储活动之间共享的数据。你可以为变量定义一个范围,这个范围可以是整个工作流的也可以只是一个特定的活动(及其子活动的)。参数类似变量,但是它倾向将数据传入或者传出工作流。你可以把它想象成函数的参数。
图1-4显示了参数窗口的模样。注意Direction列,它定义了数据是否被传递到工作流还是被传出的工作流。
图1-4.查看流程的参数
设计流程
初始的工作流设计器是空的。你要拖一些活动到设计器上面来定义流程的行为。我们要实现的项目最初将只显示问候语“Hello, World!”。以后用一些程序活动来完善它。现在开始,拖一个Sequence活动到设计器上。然后拖动一个WriteLine活动到Sequence上。这样看起来像图1-5所示。
图1-5.添加一个WriteLine活动
属性窗口,如图1-6所示。
图1-6.WriteLine的属性窗口
DisplayName属性是在流程图上显示的文字。你应该给一个更有意义名字,因为当你有很多的WriteLine活动时候,这将有助于你记住这个活动是干什么的。将其修改成Hello。此外,在Text属性中输入如下字符串:
"Hello, World!"
Text属性可以是任何结果为字符串的表达式。您可以单击省略号,将弹出一个对话框,你可以在其中输入一个表达式。
你可以设置TextWriter属性为空。默认情况下,Text属性的值将被输出到控制台。如果您想用一个不同的实现方式,您可以指定一个TextWriter的派生类(.NET 4.0中新内容)。这个将在第九章讲述。
回顾Program.cs
打开Program.cs文件,它执行控制台应用程序,并启动工作流。默认代码由模板生成,如清单1-1所示。
清单1-1.默认的Program.cs代码
using System; using System.Linq; using System.Activities; using System.Activities.Statements; namespace Chapter01 { class Program { static void Main(string[] args) { WorkflowInvoker.Invoke(new Workflow1()); Console.WriteLine("Press ENTER to exit"); Console.ReadLine(); } } }静态WorkflowInvoker类用于启动Workflow1类中定义的流程。
以粗体显示的不是默认的生成的代码行:
Console.WriteLine("Press ENTER to exit");
Console.ReadLine();
我添加这几行代码,能让控制台应用程序在退出之前,你有机会看到输出。请您将此代码添加到项目中。
运行应用程序
现在按F5运行该应用程序。结果应该是这样的:
Hello, World!
Press ENTER to exit
添加可编程的活动
WF4.0提供了很多编程的活动,例如If, While, Assign, Sequence等。为了说明他们是如何工作的,你会加强这个“greeting”程序。首先,让它像一些老式的钟表,用钟声数来表示时间(一声表示一小时)。打开Workflow1.xaml
使用变量(Variables)
对于WF 4.0,你必须声明流程活动中所有使用的变量,这个项目中,您将需要两个变量:一个变量表示需要响多少钟,另一个作为一个计数器表示迄今响了多少次。点击变量(Variables)按钮。如果变量窗口看起来像图1-3(没有变量,也没有添加变量的方式),它意味着没有选择一个定义范围。
点击Sequence活动,变量窗口看起来像图1-7。
图1-7.变量窗口定义的范围
点击创建变量的连接。在Name中输入counter,选择Int32作为变量名的类型。scope保持不变为Sequence。这意味着该变量是提供给Sequence活动使用的,以及它所有的子活动。在Default中输入1。现在变量窗口应该看起来像图1-8。
图1-8.有一个变量的变量窗体
在属性窗口中也有同样的值(见图1-9)。您可以在属性窗口或变量窗口设置变量的属性。
再次点击创建变量的链接。这一次,使用属性窗口来输入属性。在Name输入numberBells和Type为Int32。保持Scope为Sequence不变。对于Default属性,单击省略号,将弹出表达式编辑器,如图1-10所示。
图1-10.表达式编辑器
在Default属性的表达中输入DateAndTime.Now.Hour。这将设置numberBells变量表示这一天的当前小时。现在变量窗口应该看起来像图1-11。
图1-11.完成变量窗口
If
DateAndTime类的Hour属性将返回一天24小时制的小时。例如,对于下午2时,它会返回14。所以你就需要调整一下,因为你要响2钟,而不是14。在代码中,你将它写成如下:
if (numberBells > 12)
numberBells -= 12;
但是,在WF4.0中,您将需要使用一个If和一个Assign 活动来完成。在Hello活动下方拖一个If活动。流程图看起来如图1-12所示。
在属性窗口,更改DisplayName属性为Adjust for PM。If活动由三部分组成。Condition它应是一个布尔值(true或false)。当条件为真时,执行Then包含的活动,当条件为假时,执行Else包含的活动。您不需要同时指定Then和Else;它们之中只有一个是必须的。如果没有定义活动,那么将没有活动被执行。在Condition中输入numberBells > 12。
Assign
向Then中拖动一个Assign活动,Assign活动可以允许你给个变量或参数分配一个值。这个活动如图1-13所示。
To和Value属都接收一个表达式。您可以在提供的方格中直接输入表达式,或单击省略号来使用表达式编辑器。在To属性中,输入numberBells。在Value属性中,输入numberBells – 12。在属性窗口看起来如图1-14所示。
许多活动是复合活动,这意味着它们可以包含其他活动。If活动就是一个很好的复合活动的例子。当您设计更复杂的流程的时候,您将会设计有几个层次的流程。
While
现在,添加一个While活动来响铃,在“Adjust for PM”.下方拖一个 While 活动,设置它的DisplayName属性为Sound Bells。此时流程图看起来如图1-15所示。
在While 活动中,只要 Condition为真,活动的Body部分将被执行。先计算Condition,然后如果为真,Body中的活动才会被执行,重复执行直到条件为假。在Condition 中输入:counter <= numberBells。拖一个 Sequence 活动到Body 部分。设置这个Sequence活动的DisplayName属性为Sound Bell。这个流程图如图1-16所示:
图1-16.包含一个sequence的While活动
Sequence
现在你将会拖三个活动到名为“Sound Bell”的 Sequence上。在这个例子中,你将不会听到真正钟声。而是在控制台输出一行计算钟声的文本。向“Sound Bell”活动拖一个WriteLine活动。在Text属性中输入以下内容:
counter.ToString()
这个活动将在控制台的显示计数器的当前值,然后在刚才的WriteLine活动下面拖动一个Assign活动。
在To属性中输入counter。在Value属性中输入counter + 1.
这是简单的计数器递增。
Delay
最后,在Assign活动下面拖一个Delay活动,一个Delay活动在指定的一段期间内暂停了工作流。Delay活动只有唯一的Duration属性,这个属性表明暂停多长时间。这个属性被指定为为TimeSpan的类。输入下面的表达式:
TimeSpan.FromSeconds(1)
流程图看起来如图1-17所示。
Figure 1-17.sequence完成
更多装饰
按一下名字“Sound Bells” 的While 活动右上角Collapse链接 。工作流图看起来如图1-18所示。
Figure 1-18.折叠的While活动
在Sound Bells活动下面,拖一个WriteLine 活动。将DisplayName修改成DisplayTime,在Text属性中输入下面的表达式:
"The time is: " + DateAndTime.Now.ToString()。
在“Display Time”下面拖一个If活动,设置 DisplayName 为Greeting。在Condition输入下面表达式;
DateAndTime.Now.Hour >= 18
向Then 和Else中都拖一个WriteLine活动,在Then中的WriteLine活动的Text属性中输入“Good Evening”;在Else中的输入“Good Day”。“Greeting”活动如图1-19所示。
Figure 1-19. Greeting活动
运行应用程序
按F5运行该应用程序。输出的结果取决于一天的时间,结果将类似下面:
Hello, World!
1
2
The time is: 10/28/2009 2:26:02 PM
Good Day
Press ENTER to exit
设计器导航
即使是这样比较简单的工作流程,你可以看到,将很难显示整个图。幸运的是,设计器有一些有用的功能,来帮助您设计一些大的流程。在设计器的右上方的角落,点击Collapse All 连接。这个流程图类似于图1-20所示。
Figure 1-20.折叠的流程示意图
这给你一个快速的方法来看到最顶层的活动。现在点击展开全部链接。这将展开所有的活动,但现在你可以看到的只是流程图的一部分。点击设计器右下角的 Overview 控件,它弹出一个窗口来展现整个图。黄色的框显示窗体中可视流程区域。您可以在周围拖动,这时主窗体也会随之而动。关闭overview 窗口,并单击Fit to screen 控件。这将尽可能放大并且仍然保持整个图可见。由于显示器的大小,这可能导致有点难以阅读。drop-down控件允许你改变缩放级别。最后,如果你点击reset zoom to 100%,变焦将返回到默认百分之百的水平。
双击 “Sound Bell”活动。这将只显示该活动(和其子活动)。为了帮助你知道你在整个流程的哪个地方,显示导航栏如图1-21。
图1-21.设计器导航栏
您可以点击此导航栏中的任何一个链接来显示工作流设计器的相应的层级。点击Workflow1链接来显示流程的顶层工作流。
深入了解
让我们来看看刚刚实现的流程的简要介绍。首先,我之前就说过是工作流的流程是以XAML文件定义的。到目前为止,一直在使用设计器以图形方式来定义工作流。现在,你会看到设计器实际上产生了什么。在Solution Explorer右击Sequence1.xaml文件,并选择Code View。你可能会得到一个文件已经打开警告。单击Yes,关闭现有的设计器窗口。XAML代码如清单1-2所示。
清单1-2. Sequence1.xaml源代码
<p:Activity mc:Ignorable="" x:Class="Chapter01.Sequence1" xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities/design" xmlns:__Sequence1="clr-namespace:Chapter01;" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:p="http://schemas.microsoft.com/netfx/2009/xaml/activities" xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <p:Sequence sad:XamlDebuggerXmlReader.FileName= "C:\Documents\Books\WF40\Code\Chapter01\Chapter01\Sequence1.xaml"> <p:Sequence.Variables> <p:Variable x:TypeArguments="x:Int32" Default="[1]" Name="counter" /> <p:Variable x:TypeArguments="x:Int32" Default="[DateTime.Now.Hour]" Name="numberBells" /> </p:Sequence.Variables> <p:WriteLine DisplayName="Hello">["Hello, World!"]</p:WriteLine> <p:If Condition="[numberBells > 12]" DisplayName="Adjust for PM"> <p:If.Then> <p:Assign> <p:Assign.To> <p:OutArgument x:TypeArguments="x:Int32">[numberBells]</p:OutArgument> </p:Assign.To> <p:Assign.Value> <p:InArgument x:TypeArguments="x:Int32">[numberBells - 12] </p:InArgument> </p:Assign.Value> </p:Assign> xmlns:s3="clr-namespace:System;assembly=System.Xml" xmlns:s4="clr-namespace:System;assembly=System.Core" xmlns:sa="clr-namespace:System.Activities;assembly=System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" xmlns:sad="clr-namespace:System.Activities.Debugger;assembly=System.Activities" xmlns:sap="http://schemas.microsoft.com/netfx/2009/xaml/activities/presentation" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=System" xmlns:scg1="clr-namespace:System.Collections.Generic; assembly=System.ServiceModel" xmlns:scg2="clr-namespace:System.Collections.Generic;assembly=System.Core" xmlns:scg3="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:sd="clr-namespace:System.Data;assembly=System.Data" xmlns:sd1="clr-namespace:System.Data;assembly=System.Data.DataSetExtensions" xmlns:sl="clr-namespace:System.Linq;assembly=System.Core" xmlns:st="clr-namespace:System.Text;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <sap:WorkflowViewStateService.ViewState> <scg3:Dictionary x:TypeArguments="x:String, x:Object"> <x:Boolean x:Key="ShouldExpandAll">False</x:Boolean> <x:Boolean x:Key="ShouldCollapseAll">True</x:Boolean> </scg3:Dictionary> </sap:WorkflowViewStateService.ViewState> <Sequence sad:XamlDebuggerXmlReader.FileName= "C:\Documents\Books\WF40\Code\Chapter01\Chapter01\Workflow1.xaml" sap:VirtualizedContainerService.HintSize="233.6,552"> <Sequence.Variables> <Variable x:TypeArguments="x:Int32" Default="1" Name="counter" /> <Variable x:TypeArguments="x:Int32" Default="[DateAndTime.Now.Hour]" Name="numberBells" /> </Sequence.Variables> <sap:WorkflowViewStateService.ViewState> <scg3:Dictionary x:TypeArguments="x:String, x:Object"> <x:Boolean x:Key="IsExpanded">True</x:Boolean> </scg3:Dictionary> </sap:WorkflowViewStateService.ViewState> <WriteLine DisplayName="Hello" sap:VirtualizedContainerService.HintSize="211.2,59.2" Text="Hello, World!" /> <If Condition="[numberBells > 12]" sap:VirtualizedContainerService.HintSize="211.2,49.6"> <sap:WorkflowViewStateService.ViewState> <scg3:Dictionary x:TypeArguments="x:String, x:Object"> <x:Boolean x:Key="IsExpanded">True</x:Boolean> <x:Boolean x:Key="IsPinned">False</x:Boolean> </scg3:Dictionary> </sap:WorkflowViewStateService.ViewState> <If.Then> <Assign sap:VirtualizedContainerService.HintSize="289.6,100.8"> <Assign.To> <OutArgument x:TypeArguments="x:Int32">[numberBells]</OutArgument> </Assign.To> <Assign.Value> <InArgument x:TypeArguments="x:Int32">[numberBells - 12]</InArgument> </Assign.Value> </Assign> </If.Then> </If> <While DisplayName="Sound Bells" sap:VirtualizedContainerService.HintSize="211.2,49.6"> <sap:WorkflowViewStateService.ViewState> <scg3:Dictionary x:TypeArguments="x:String, x:Object"> <x:Boolean x:Key="IsExpanded">False</x:Boolean> <x:Boolean x:Key="IsPinned">False</x:Boolean> </scg3:Dictionary> </sap:WorkflowViewStateService.ViewState> <While.Condition>[counter <= numberBells]</While.Condition> <Sequence DisplayName="Sound Bell" sap:VirtualizedContainerService.HintSize="438.4,100.8"> <sap:WorkflowViewStateService.ViewState> <scg3:Dictionary x:TypeArguments="x:String, x:Object"> <x:Boolean x:Key="IsExpanded">True</x:Boolean> </scg3:Dictionary> </sap:WorkflowViewStateService.ViewState> <WriteLine sap:VirtualizedContainerService.HintSize="243.2,59.2" Text="[counter.ToString()]" /> <Assign sap:VirtualizedContainerService.HintSize="243.2,57.6"> <Assign.To> <OutArgument x:TypeArguments="x:Int32">[counter]</OutArgument> </Assign.To> <Assign.Value> <InArgument x:TypeArguments="x:Int32">[counter + 1]</InArgument> </Assign.Value> </Assign> <Delay Duration="[TimeSpan.FromSeconds(1)]" sap:VirtualizedContainerService.HintSize="243.2,22.4" /> </Sequence> </While> <WriteLine DisplayName="Display Time" sap:VirtualizedContainerService.HintSize="211.2,59.2" Text="["The time is: " + DateAndTime.Now.ToString()]" /> <If Condition="[DateAndTime.Now.Hour >= 18]" DisplayName="Greeting" sap:VirtualizedContainerService.HintSize="211.2,49.6"> <sap:WorkflowViewStateService.ViewState> <scg3:Dictionary x:TypeArguments="x:String, x:Object"> <x:Boolean x:Key="IsExpanded">False</x:Boolean> <x:Boolean x:Key="IsPinned">False</x:Boolean> </scg3:Dictionary> </sap:WorkflowViewStateService.ViewState> <If.Then> <WriteLine sap:VirtualizedContainerService.HintSize="219.2,100.8" Text="Good Evening" /> </If.Then> <If.Else> <WriteLine sap:VirtualizedContainerService.HintSize="219.2,100.8" Text="Good Day" /> </If.Else> </If> </Sequence> </Activity>
我将一些代码行变粗,帮助您找到最顶层活动。首先,变量部分只创建两个的变量。然后有一个名为“Hello”的WriteLine活动,If活动命名为“Adjust for PM”。接下来是一个名为 “Sound Bells”的While活动,一个WriteLine活动名为“Display Time”,一个名为““Greeting”的If活动。我想让你看到的关键的一点是,它没有可执行代码。这个文件是一个嵌套的属性集合。例如,要增加counter,你通常会使用这样一行代码:
counter = counter + 1;
现在你使用带counter和counter + 1两个表达式的Assign类。实际上计数器是由Assign活动执行counter = counter + 1实现的。执行代码仅在Activity类中,在流程中是没有执行代码。
与以前的版本差异
如果你有使用以前版本的Workflow Foundation(版本3.0或版本3.5)。你可能想知道WF4.0发生了什么改变。WF4.0是完全远离了之前的Workflow版本。你之前开发的工作流应用程序将会在.Net4.0下面运行良好,因为之前的活动和服务只进行了最小的变化【指.net3.0和.net4.0中WF3.0(3.5)只有微小的变化】。但是WF 4.0是一个完全新的设计,WF4.0的活动和服务不能与之前的版本互换。所以,你只可以使用3.5的方法设计,实现,维护你开发的WF工作流应用程序。或者你可以选择使用WF 4.0模式。
在WF 3.5中,有一代码类和一个设计类。代码类中包含的实现CodeActivity的对象。它也包含类的成员的定义和事件处理程序代码。在WF 4.0,没有代码类。也许面最显着的差别是在WF 4.0没有CodeActivity对象。为了弥补这一点,WF 4.0提供了一些活动来完成以前CodeActivity对象的功能。WriteLine和Assign就是两个这样的活动。如果内置的活动不够用。你可以创建一个自定义活动来完成使用CodeActivity对象执行的任务。
另一个关键的区别是变量和参数的使用。因为没有代码文件,所以你不能像普通的类一样能简单地添加类成员。你需要使用“工作流的方式”定义这些。
最后,你可能已经注意到在的Program.cs文件中没有看到WorkflowRuntime类。以前您将创建WorkflowRuntime类,然后调用它的CreateWorkflow()方法。在WF 4.0中,代码只是很简单的调用:
WorkflowInvoker.Invoke(new Workflow1());
在本文中,你肯定看到其他方面的差异。例如,没有状态机工作流。我将不会将全部不同的地方指出,因为这本书的目的不是为了阐述它们的不同点。尽管如此,当我第一次看 WF 4.0的时候,我注意到了一些很明显的变化。
作者:朱祁林
出处:http://zhuqil.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。