【翻译】WF从入门到精通(第九章):逻辑流活动
上一篇:【翻译】WF从入门到精通(第八章):调用外部方法及工作流
学习完本章,你将掌握:
1.学会怎样使用IfElse活动来执行条件表达式
2.学会怎样使用While活动来执行循环
3.理解Replicator活动是怎样来模拟for循环的,以及它的使用方法。
我们已经看到过怎样执行工作流内部和外部的代码,已经知道怎样处理异常,暂停进程,在事情脱离控制时终止我们的工作流。但无疑对于任何一个计算机系统的主要组成部分来说,都应具有根据运行时的条件做出判断以执行不同的任务的能力。在本章,我们将演示要求我们应付if/else场景及基本的循环的一些工作流活动。
条件及条件处理
现在,你可能不会感到奇怪,你发现WF提供了基于运行时的条件进行逻辑处理控制流的活动。毕竟,假如WF提供了活动去抛出并捕获异常,那它为什么就没有相应的活动来根据工作流的执行情况进行检测并根据它们获取的结果作出决策呢?
我们将在本章中进行测试的活动包括IfElse活动、While活动和Replicator活动。IfElse活动的作用是测试一个条件并根据测试结果执行不同的工作流路径。While活动用来执行一个While循环。而对于for循环,则是使用Replicator活动来完成。现在通过本章的示例应用程序开始我们的学习。
备注:在本章你将依靠CodeCondition来进行条件的处理,它(CodeCondition)的意思是你将写下C#代码来处理条件表达式。在12章(“策略和规则”)中,你将使用RuleCondition来对条件表达式的值进行处理,RuleCondition使用了WF的基于规则的处理方式。两种方式都同样有效。
Qustioner应用程序
本章的示例应用程序是一个Windows Form应用程序,它会请你回答三个问题,问题内容你能够进行修改。(问题的内容保存在应用程序的settings property中。)你也可指定这些问题是各自独立还是相互关联的。
当工作流开始执行时你要把这些问题和相关的情况传入该工作流。相互关联的问题只有在前面的问题回答正确时才会被进一步提出。例如,假如有人问你:“谈到的文档你看过吗?”,假如你没有,则没多大意义问接下来这一问题:“这个文档你批准吗?”假如问题是相关的,则第一个问题回答是否定的话,就将返回否定的回答,余下的问题不予考虑也都将返回否定的回答。
各自独立的问题要求你必须回答,而不管前面的问题中你回答的是什么。例如这个问题,“你喜欢冰淇淋吗?”就和问题“现在外面在下雨吗?”是不相关的。无论你喜不喜欢冰淇淋,你的答案都和外面的天气这个问题是各自独立的。对于相互独立的问题来说,不管你在前面的问题中是肯定还是否定的回答,都会进一步被问到。
用户界面如图9-1。假如你修改三个问题中的任何一个的内容,新问题的都将自动地保存到你的应用程序的settings property中(问题的类型也一样)。这些问题会产生“是/否”的回答,使工作流能够把这些回答作为一个Boolean类型的数组传回到宿主应用程序中。
图9-1 Questioner主应用程序界面
当你点击Execute按钮时,这些问题通过带“是”和“否”按钮的信息框依次呈现。一旦工作流处理完所有的这些问题,它就返回一个Boolean数组给宿主应用程序。宿主应用程序将检查该数组以显示不同的用户界面。
当工作流执行时,回答结果将以蓝色圆球的形式显示(如图9-1)。当工作流任务完成后,通过的回答将以绿色圆球的形式出现,未通过的回答将以红色圆球的形式出现。假如所有的回答都通过了,则“最终回答结果”图片将以绿色圆球的形式呈现。但是,假如三个问题中的任何一个没有通过,则“最终回答结果”图片将以带“8”字的圆球的形式呈现。如图9-2。
图9-2 Questioner应用程序执行期间的用户界面
对你来说,使用这个应用程序的目的是测试本章中的三个活动。第一次迭代,Questioner将使用IfElse活动来判断要执行什么动作过程。第二次迭代时这些问题仍然会被问到,我们将使用While活动来提问。最后一次迭代我们将使用Replicator活动来模拟for循环进行提问。对于该应用程序的每一次迭代,我们都将使用前一章中演示的技术来把回答的结果传回给宿主应用程序。
使用IfElse活动
IfElse活动的作用是对if-then-else条件表达式进行模拟,其实你在前几章使用过这个活动。
IfElse活动要求你提供一个条件表达式,它其实是作为一个event handler执行。你创建的event handler有一个类型为ConditionalEventArgs的参数,它有一个Boolean类型的(名称为)Result属性。你可对其进行set,以指明该条件表达式的结果。
IfElse活动根据Result的值来指挥工作流到底该执行两个分支中的哪一个。在Microsoft Visual Studio的工作流视图设计器中,true执行的是显示在左边的路径,而false执行的是右边的路径。两个分支都可作为其它活动的容器,允许你插入任何一个你需要的工作流活动。
备注:通过本节的学习,你可能会认为,IfElse活动可能不是构建下面的工作流的最合适的活动。你在本章的后面部分将找到更加适合下面特定的工作流的活动。
使用IfElse活动创建QuestionFlow工作流
1.下载本章源代码,打开IfElse Questioner文件夹中的解决方案。
2.看看Visual Studio解决方案资源管理器,你会看到解决方案的层次结构和前一章中的相似。主应用程序的文件位置在Questioner项目中,而宿主通信服务文件的位置则在QuestionService项目中。为使你把注意力放到工作流上,我已经创建了服务接口(具体过程参见前一章):IQuestionService,并且使用wca.exe工具(使用方法参见前一章)生成了一个必需的通信活动:SendReponseDataToHost。现在,找到QuestionFlow项目的Workflow1.cs文件并在视图设计器中打开它。
3.从工具箱中拖拽一个IfElse活动到设计器界面上。
4.你会看到一个内含感叹号(!)标记的红色圆圈,这是提醒你还需要更多的信息才能编译你的工作流。其实,缺少的就是条件表达式!选中ifElseActivity1的左边分支以便在Visual Studio的属性面板上呈现该活动的属性。选中它的Condition属性以激活它的下拉列表框,然后从列表中选择代码条件。
备注:你通常有两种方式来对条件表达式进行选择:code(代码)和rules-based(基于规则)。我们这里将使用基于代码的条件表达式,基于规则的技术将保留到第12章(策略活动)进行学习。
5.展开显示的Condition属性,输入AskQuestion1,然后按下回车键,Visual Studio这就为你插入了AskQuestion1的事件处理程序并会切换到代码视图下。现在,重新回到工作流视图设计器上,你还要把更多的活动添加到你的工作流中。
6.拖拽一个Code活动到设计器界面上,并把它放到ifElseActivity1的右边分支上。
7.指定它的ExecuteCode属性值为NegateQ1。当Visual Studio插入了NegateQ1事件出现程序后,重新回工作流视图设计器界面上。
8.重复步骤6和步骤7,在ifElseActivity1的左边分支上也添加一个Code活动。
指定它的ExecuteCode属性值为AffirmQ1,但是,当Visual Studio插入了AffirmQ1事件出现程序后,不要切换回工作流视图设计器界面上。相反,我们要添加一些代码。
9.我们现在需要为该工作流类添加一些属性,当我们起动工作流进程时可把它们作为参数。在Workflow1的构造器的下面,添加下面的代码,它们包含有工作流将问到的三个问题:
public string[] Questions
{
get { return _questions; }
set { _questions = value; }
}
10.我们也需要添加一个Dependent属性,它用来告知这些问题彼此是否是相关的。在上面所添加的代码下,添加如下代码:
public bool Dependent
{
get { return _dependent; }
set { _dependent = value; }
}
11.问题回答的结果是一些Boolean值,在传回给宿主应用程序前需要保存到某些地方。因此,在上面所插入的代码下添加该字段:
private bool[] _response = null;
12.该_response字段没有被初始化,因此找到Workflow1的构造器,在里面的InitializeComponent方法下面添加如下的代码:
_response = new bool[3];
_response[0] = false;
_response[1] = false;
_response[2] = false;
13.现在找到Visual Studio为你添加的AskQuestion1事件处理程序(event handler)。在该事件处理程序中添加下面的代码:
DialogResult result = MessageBox.Show(Questions[0], "Questioner:",
MessageBoxButtons.YesNo, MessageBoxIcon.Question);
e.Result = (result == DialogResult.Yes);
14.对于NegateQ1事件处理程序,则添加下面的代码:
_response[0] = false;
if (Dependent)
{
// Negate remaining answers.
_response[1] = false;
_response[2] = false;
}
15.接下来,在AffirmQ1事件处理程序中添加下面的代码:
_response[0] = true;
16.你现在就已为第一个问题的提问添加了对应的工作流组成部分,但是这还有两个问题。对于第二个问题,重复步骤3至步骤8来新添加一个IfElse活动添加到工作流中,把和问题1相关的地方用问题2替换掉,例如插入的事件处理程序就应该是AskQuestion2、NegateQ2和AffirmQ2。工作流视图设计器的界面现在如下所示:
17.现在找到AskQuestion2事件处理程序并添加下面的代码:
{
// No need to ask!
e.Result = false;
}
else
{
// Ask the question!
DialogResult result = MessageBox.Show(Questions[1], "Questioner:",
MessageBoxButtons.YesNo, MessageBoxIcon.Question);
e.Result = (result == DialogResult.Yes);
}
18.对于NegateQ2事件出现程序,添加下面的代码:
_response[1] = false;
if (Dependent)
{
// Negate remaining answer
_response[2] = false;
}
19.对于AffirmQ2事件处理程序,添加下面的代码:
_response[1] = true;
20.在一次重复步骤3至步骤8,添加第三个问题,把和问题1相关的内容替换掉(方法和添加第二个问题时一样)。此时,工作流视图设计器的界面如下所示:
21.找到AskQuestion3事件处理程序,插入下面的代码:
{
// No need to ask!
e.Result = false;
}
else
{
// Ask the question!
DialogResult result = MessageBox.Show(Questions[2], "Questioner:",
MessageBoxButtons.YesNo, MessageBoxIcon.Question);
e.Result = (result == DialogResult.Yes);
}
22.对于NegateQ3事件处理程序,添加下面的代码:
_response[2] = false;
23.对于AffirmQ3事件处理程序则添加下面的代码:
_response[2] = true;
24.现在回到工作流视图设计器。你会在工具箱中找到一个名称为SendResponseDataToHost的自定义活动。(注意:假如工具箱中没有这个SendResponseDataToHost活动,则编译该项目再重新看看。)
25.拖拽一个SendResponseDataToHost活动到你的工作流视图设计器界面上,把它放到第三个IfElse活动(ifElseActivity3)的下边。
26.因为返回的数据是一个简单的Boolean类型的数组,因此这里的处理方式和前一章有点点区别。和你去添加一个容纳该Boolean类型的数组的依赖属性不同,SendResponseDataToHost活动使用一个字段来容纳该数据,创建该字段的用户界面也就和你在第七章中看到的不同。在Visual Studio属性面板上选中responses属性,然后点击浏览(...)按钮。
这打开了如下面的Boolean集合编器对话框。
27.点击添加按钮,共重复三次,保留这些默认的False值,然后点击确定按钮。Visual Studio就为你把包含三个Boolean元素的数组添加进了你的Workflow1.designer.cs文件的代码中。
提示:在下面的第28步,你将添加一个CodeActivity,用它来把你在第11步添加的_response字段分配到我们刚刚为SendResponseDataToHost创建的Boolean数组中。但是,你也可直接使用SendResponseDataToHost的一个我们已经创建的response属性(来对它进行访问)。我选择这样做仅仅是因为(从阐述的角度来说)这样更有意义,这可展示出在涉及到宿主通信活动前该Ifelse活动是怎样添加和工作的。
28.现在我们需要把保存了问题回答结果的数组的值和将要使用这些值的SendResponseDataToHost活动联系起来。因此,我们现在就拖拽一个Code活动到工作流视图设计器的界面上,把它放到第三个IfElse活动(ifElseActivity3)和SendResponseDataToHost活动(sendResponseDataToHost1)之间。
29.设置该Code活动的ExecuteCode属性为CopyResponse,然后按下回车键。
30.在Visual Studio插入的CopyResponse事件处理程序中添加下面的代码:
sendResponseDataToHost1.responses = _response;
31.编译并运行该应用程序。改变问题的Dependency属性,看看在回答这些问题时,作出否定的回答其结果一样吗?
使用While活动
假如你回头看看前一节,你会至少注意到两件事。首先,毫无疑问你体验了IfElse活动;第二,它用了31个独立的步骤来创建了该工作流。有些程序结构使用if-then-else来进行处理很合适,但这个特殊的应用程序使用循环结构来进行问题的提问会更合适些。这些将在接下来演示。你将使用另一个使用了while循环的工作流来替换你刚刚创建好了的工作流。
WF的While活动处理条件表达式时的过程和IfElse活动相似。它触发了一个对循环是否继续进行验证的事件,它使用ConditionalEventArgs来返回你的判断结果(也要使用Result属性)。
但是,和IfElse活动不同的是,你在使用While活动的时候,假如设置Result为true将导致继续进行循环,设置Result为false则终止循环。我们就来看看怎样使用while循环来替换if-then-else进行条件处理,以简化我们的工作流。
使用While活动创建QuesionFlow工作流
1.从下载的本章源代码中使用Visual Studio打开While Questioner文件夹内的解决方案。
2.和前一节一样,该应用程序本质上是完整的,它包含了已创建好了的SendResponseDataToHost活动。剩下要去完成的工作是完善工作流的处理过程。在解决方案管理器面板上找到QuesionFlow项目中Workflow1.cs文件,然后在工作流的视图设计器中打开它。
3.从工具箱中拖拽一个While活动到视图设计器界面上。
4.和IfElse活动相似,选中whileActivity1活动的Condition属性以激活它的下拉列表框。从这个下拉列表框中选择代码条件。
5.展开该Condition属性,输入TestComplete,然后按下回车键。Visual Studio这就为你添加了TestComplete事件程序程序,然后回到工作流的视图设计器界面上。
6.拖拽一个Code活动到工作流视图设计器界面上,把它放到whileActivity1的里面。指定它的ExecuteCode的属性值为AskQuestion。同样,在生成了AskQuestion事件处理程序后重新回到工作流视图设计器界面上来。
7.为了使我们能把保存有问题回答结果的Boolean数组返回给宿主应用程序,我们需要重复前面一节的第24、25步,以把一个SendResponseDataToHost活动插入到我们的工作流中。(在这之前,需要编译该应用程序,否则SendResponseDataToHost活动不会在工具箱中显示。)我们把该SendResponseDataToHost活动放到whileActivity1的下面,以便它在while循环后被执行。
8.我们同样需要重复前一节的第9步至第12步,以便插入Questions和Dependent属性,并且对_response数组进行创建和初始化。
9.在_response数组的声明语句下面,添加下面的代码:
private Int32 _qNum = 0;
10.找到TestComplete事件处理程序,添加下面的代码:
if (_qNum >= Questions.Length)
{
// Assign outgoing data.
sendResponseDataToHost1.responses = _response;
// Done, so exit loop.
e.Result = false;
}
else
{
// Not done, so continue loop.
e.Result = true;
}
11.我们需要完成的最后一点代码实际上就是问题的回答。在Workflow1.cs文件中,你会找到AskQuestion事件处理程序,为该事件处理程序添加下面的代码。假如问题的回答是否定的并且Dependent属性是true(表示各个问题是相关的),则所有余下的问题的回答就都是否定的。
DialogResult result = MessageBox.Show(Questions[_qNum], "Questioner:",
MessageBoxButtons.YesNo, MessageBoxIcon.Question);
_response[_qNum] = (result == DialogResult.Yes);
// Check response versus dependency
if (!_response[_qNum] && Dependent)
{
// Negate remaining questions
while (_qNum < Questions.Length)
{
// Negate this question
_response[_qNum] = false;
// Next question
++_qNum;
}
} // if
else
{
// Set up for next iteration
++_qNum;
}
12.重复前一节的步骤28至步骤30,我们将使用Code活动来把保存了问题回答结果的数组传给SendResponseDataToHost活动。(该Code活动放到While1活动和SendResponseDataToHost1活动中间。)
13.编译并执行该应用程序。
假如你花一些时间来比较一下本节和上一节的最终的工作流视图设计器界面,你很容易发现使用While活动大大简化了工作流的处理。整个工作流视图设计器界面简单多了!
既然有和while循环等价的工作流活动,是否也有和for循环等价的工作流活动呢?答案是肯定的,它就是Replicator活动。
使用Replicator活动
说Replicator活动和C#术语中的for循环是等价的可能并不正确。C#语言规范1.2中告诉我们,C#中的for循环看起来和下面的类似:
for(for-initializer;for-condition;for-iterator) embedded-statement
embedded-statement(内嵌语句)在for-condition(条件判断)的值为true时执行(如果被省略,则假定为true),循环由for-initializer开始,每一次循环都要执行for-iterator。这当中没有涉及到任何的C#声明组件。对于replication来说,我们可以设想它是一个能重复地对源代码进行准确复制的软件工厂。C#的for循环则不是以这种方式进行操作的。
事实上,“重复”的概念在我们看到WF中和for循环等价的活动这一说明后就不会感到可怕了。假如你熟悉ASP.NET的话,你或许使用过Repeater控件。这个ASP.NET的Repeater控件有一个项模板(同时也有一个项替换模板),它能根据所绑定的数据项的数目进行重复处理。
Replicator活动和ASP.NET的绑定到基于IList数据源的Repeater控件相似,它重复它所包含的子活动,基于IList的数据源中的每个元素就相当于一个子活动。但是Replicator活动和C#中的for语句在有些方面是相似的,因为它也有一个循环初始化事件(这就像是for-initializer)、一个循环完成事件(这和用for-iterator和for-condition做比较类似)以及一个循环继续事件(和for-condition类似)。它提供了这些事件来指明重复性的(和嵌入语句相似)子活动的创建工作,以便你能个性化地进行数据绑定,子活动完成了它就触发一个事件,以便你能为每个子活动实例执行一些清理和管理的任务。
Replicator活动必须接受也只接受一个唯一的活动,它能作为其它活动的容器(和Sequence活动类似),它触发一个开始执行的初始化事件。在初始化事件的执行期间,你可把一个基于IList的集合绑定到Replicator活动的InitialChildData属性中。
该Replicator活动然后会重复你所提供的子活动,它们的次数和基于IList集合中的项的数目相等。这些子活动实例能够以依次按顺序的方式或以并行的方式执行(这可通过ExecutionType属性进行设置)。UntilCondition事件在每一个子活动执行前触发,在处理UntilCondition事件时,你可通过设置ConditionalEventArgs的Result属性为false来通知Replicator活动继续执行(为true则终止循环)。表9-1简要地列出了我们需要关注的Replicator活动的一些属性,而表9-2列出了在我们的工作流中使用Replicator活动时需要进行处理的一些事件。表9-1 Replicator活动的属性
属性 | 功能 |
ExecutionType | 获取或设置Replicator活动的ExecutionType(一个枚举值)。该ExecutionType的枚举值包含Parallel和Sequence。 |
InitialChildData | 获取或设置子活动数据的一个IList集合。该属性和其它.NET技术中的data-binging(数据绑定)属性相似,事实上要分配给该属性的对象必须是一个IList对象。Replicator活动会为分配给该属性的基于IList集合中的每一项创建一个子活动实例。 |
事件 | 功能 |
ChildCompleteEvent | 在Replicator活动中的子活动实例已完成后触发。对于每一个循环触发一次。 |
ChildInitializedEvent | 在Replicator活动中的子活动实例初始化后触发。对于每一个循环触发一次。 |
CompleteEvent | 在Replicator活动已完成后触发(也就是说,在所有循环中的子活动实例都已执行完成)。 |
InitializedEvent | 在Replicator活动开始执行时触发。该事件只触发一次,在所有的子活动执行前触发。 |
UntilCondition | UntilCondition在许多的WF文档中都是以属性的方式列出的,它其实表示的是一个事件处理程序,这和Code活动的ExecuteCode属性的作用一样(通过ExecuteCode属性就把一个去执行相应代码的事件处理程序和相应的Code活动联系了起来)。这个事件在每一个子活动实例执行前触发。它的ConditionalEventArgs事件参数控制了循环是否继续进行执行。指定Result的值为false则允许子活动继续执行。而指定Result的值为true则导致Replicator活动停止所有子活动的执行。 |
图9-3 Replicator活动的事件触发顺序流程图
你在图9-3中看到的基于IList的集合是通过InitialChildData属性指定的,你也可在Initialized事件处理前或处理期间进行指定。该工作流也没有说明所复制(生成)的子活动能以顺序或者以并行的方式执行,这取决于ExecutionType属性的设置。
在现实情形中怎样使用Replicator活动呢?从目前的描述来看,它比真实的使用情形要复杂得多。事实上,它的机制和其它活动相比并没有多大的区别。拖拽该活动到工作流视图设计器界面上,为各中事件处理程序指定值,再拖拽一个唯一的子活动到Replicator活动当中。该唯一的子活动和Replicator活动本身一样,也能作为一个(其它活动的)容器(就像Sequence活动一样),因此事实上多于一个的活动也能执行。在我们的头脑里有了这些表和这些图,我们就可使用Replicator活动来重新编写Questioner应用程序了。
使用Replicator活动创建QuestionFlow工作流
1.下载本章源代码,打开Replicator Questioner文件夹内的解决方案。
2.和前两节一样,宿主应用程序本质上已经完成,这可方便你把焦点放到工作流方面。选中Workflow1.cs文件并在Visual Studio的工作流视图设计器中打开它。
3.从工具箱中拖拽一个Replicator活动到视图设计器界面上,界面如下所示:
4.在Visual Studio属性面板上,选中Initialized属性并输入InitializeLoop,然后按下回车键。这样Visual Studio就在你的代码中插入了相应的事件处理程序并把界面切换到代码编辑视图界面中。我们回到工作流视图设计器界面上来,你还要继续设置属性。
5.对于Completed属性,输入LoopCompleted并按下回车键,在添加了LoopCompleted事件处理程序后,和前一步骤一样,我们重新回到工作流视图设计器界面上。
6.在ChildInitialized属性中,输入PrepareQuestion。在插入了PrepareQuestion事件处理程序后,我们再次回到工作流视图设计器界面上。
7.接着,在ChildCompleted属性中输入QuestionAsked,在创建了ChildCompleted事件处理程序后,我们同样回到工作流视图设计器界面上。
8.为了在问完所有的问题后(或者在问题是相关的,同时用户的回答是否定的情形下)终止循环,我们需要添加一个事件处理程序。因此,我们需要选中UntilCondition属性,从它的下拉列表框中选择“代码条件”选项。
9.对于该UntilCondition的Condition属性,我们输入TestContinue,按下回车键,在插入相应的事件处理程序后再次回到工作流的视图设计器界面上来。
10.对于Replicator活动的实例replicatorActivity1来说,需要一个唯一的子活动。因此,从工具箱中拖拽一个Code活动到replicatorActivity1中。指定它的ExecuteCode属性为AskQuestion。
11.在工作流视图设计器上需要完成的最后工作是把一个SendResponseDataToHost活动拖拽到设计器界面上,把它放到replicatorActivity1活动的下面。然后重复“使用IfElse活动创建QuestionFlow工作流”这一节中的步骤24至步骤30。(在这之前,你或许需要编译该应用程序以让SendReponseDataToHost活动显示在工具箱中。)
12.现在我们就来为Workflow1添加相应的代码,因此我们进入它的代码视图界面。
13.因为我们修改的replicatorActivity1活动的各个属性,Visual Studio都为我们添加了对应的事件处理程序,现在我们就来完成这些事件处理程序并为工作流添加其它一些所需要的代码。我们重复“使用IfElse活动创建QuestionFlow工作流”这一节中的步骤9至步骤12,这些过程为工作流添加了为进行问题处理所必须的一些属性。
14.Replicator活动需要一个基于IList的集合以便复制(生成)出它的子活动。我们有一个容纳问题的数组可以使用,因为基本的数组类型就是基于IList的。但是,我们怎样返回结果呢?在问题描述和问题编号之间并没有直接联系在一起。除了这些,我们还不能在所返回的数组的值中指定Boolean返回值。因此,我们将作出一些轻微的修改,我们需要创建一个新的数组——一个整形数组。它用来表示在问题描述数组中的元素的偏移量。对于生成的子活动,它将对所要提问的问题编号进行访问,给定它的一个索引,就可把问题描述数组和回答的Boolean类型数组联系起来。因此,我们需要在_respone数组的声明代码下面添加这样一个数组。
private Int32[] _qNums = null;
15._qNums数组还没有初始化,因此必须初始化才能使用。初始化最好的位置是在Question属性中,因此对它的set访问器进行修改,修改后的代码如下:
{
get { return _questions; }
set
{
// Save question values
_questions = value;
// Create new question number array
_qNums = new Int32[_questions.Length];
for (Int32 i = 0; i < _questions.Length; i++)
{
// Assign this question number to the array
_qNums[i] = i;
} // for
}
}
16.为了对Replicator活动的所有事件都会被使用到进行证明,我们需要在_qNums数组的声明语句下添加下面的代码:
private bool _currentQuestionResponse = false;
17.在InitializeLoop事件处理程序中添加下面的代码来对InitialChildData进行初始化:
replicatorActivity1.InitialChildData = _qNums;
备注:假如Workflow1有可绑定的属性的话,你可通过工作流视图设计器来直接对InitialChildData的值进行指定。但是,因为该Replicator活动正使用一个内部生成的数组(_qNums),因此和上面所展示的一样,你必须在InitializeLoop事件处理程序中对InitialChildData的值进行指定。
18.对于LoopCompleted事件处理程序,添加下面的代码来返回问题的回答结果:
replicatorActivity1.InitialChildData = _qNums;
19.现在我们的子活动将执行许多次来进行问题的提问。在每一个问题提问之前,Replicator活动都将触发ChildInitialized事件。我们将处理该事件并从事件参数中获取我们将要提问的问题编号。稍后,当Code活动执行时,将会对和该问题编号对应的问题进行提问。因此,把下面的代码添加到PrepareQuestion方法中(该方法是ChildInitialized事件的处理程序):
_currentQuestion = (Int32)e.InstanceData;
20.当对Code活动的回答结果进行保存时,我们需要做的过程都是相似的。定位到QuestionAsked事件处理程序(它用来处理Replicator活动的ChildCompleted事件),添加下面的代码:
_response[_currentQuestion] = _currentQuestionResponse;
21.紧接着是要对Replicator活动的UntilCondition进行编辑。找到TestContinue方法,添加下面的代码。这些在TestContinue方法中的代码将对Dependent属性进行检查。假如不再有问题,循环将被终止,同时,假如这些问题被指明是相关的,并且最近的一次回答是否定的,则所有余下未答问题的回答也被标明为否定的并终止循环。
{
// Check dependency.
if (!_response[_currentQuestion] && Dependent)
{
// Negate remaining questions.
for (Int32 i = _currentQuestion + 1; i < Questions.Length; i++)
{
// Negate this question.
_response[i] = false;
}
// Stop processing.
e.Result = true;
}
else
{
// Check for complete loop.
if (_currentQuestion == _qNums[Questions.Length - 1])
{
// Done.
e.Result = true;
}
else
{
// Continue processing.
e.Result = false;
}
}
}
22.找到Visual Studio已经为你添加了的AskQuestion方法,添加下面的代码。该方法使你有机会去进行问题的回答。
DialogResult result = MessageBox.Show(Questions[_currentQuestion],
"Questioner:", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
_currentQuestionResponse = (result == DialogResult.Yes);
23.编译并执行该应用程序。把它执行的结果和前面的两个示例做比较,你会发现它的功能和前面的示例完全一样。
本章源代码:里面包含本章的练习项目和完整代码