Windows Workflow Foundation之旅(二)——指南1(构造一个顺序工作流)
翻译自 ms-help://MS.WinWF.v1.EN/WinWF_GettingStarted/html/9c3e5551-4eff-4977-89ac-f81ab092d996.htm

1
public ExpenseReportWorkflow()
2
3

{
4
5
InitializeComponent();
6
7
}
8
9
10
11
private void InitializeComponent()
12
13

{
14
15
System.Workflow.ComponentModel.ParameterDeclaration Amount = new System.Workflow.ComponentModel.ParameterDeclaration();
16
17
System.Workflow.ComponentModel.ParameterDeclaration Result = new System.Workflow.ComponentModel.ParameterDeclaration();
18
19
//
20
21
// Workflow Parameters
22
23
//
24
25
Amount.Direction = System.Workflow.ComponentModel.ParameterDirection.In;
26
27
Amount.Name = "Amount";
28
29
Amount.Type = typeof(int);
30
31
Amount.Value = null;
32
33
Result.Direction = System.Workflow.ComponentModel.ParameterDirection.Out;
34
35
Result.Name = "Result";
36
37
Result.Type = typeof(string);
38
39
Result.Value = null;
40
41
this.Parameters.Add(Amount);
42
43
this.Parameters.Add(Result);
44
45
}
46
47
完成顺序工作流
翻译自 ms-help://MS.WinWF.v1.EN/WinWF_GettingStarted/html/9c3e5551-4eff-4977-89ac-f81ab092d996.htm
顺序工作流(sequentialworkflow)是为执行一种由一系列预定义的步骤组成的任务而设计的。这种体系结构是模拟基于过程的应用程序的。这一节将用几个步骤来编写一个简单的开支报告程序,这个小程序使用WinFrom做界面,用顺序工作流做业务逻辑。
这个小程序有一个TextBox来输入开支报告的总数,一个Button点击提交报告。工作流将评估开支,如果开支小于1000则提请领班审批,如果大于等于1000则提请经理审批。之后,工作流会发送一个审批意见,此时,出现一个Label显示审批意见,两个Button分别表示通过和拒绝审批。当某一个按钮被点击的时候,应用程序会通知回应工作流,工作流继续处理发生的事件。
开始构造顺序工作流
创建工作流类
WWF SDK中定义了一个SequentialWorkFlow类,我们定义一个ExpenseRoportWorkflow类,并继
承自SequentialWorkflow,这样就创建一个顺序工作流。如:
1
using
System;
2
using
System.Workflow.Activities;
3
using
System.Workflow.Activities.Rules;
4
5
namespace
Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow
6

{
7
[RuleConditionsAttribute(typeof(Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.ExpenseReportWorkflow))]
8
public sealed partial class ExpenseReportWorkflow : System.Workflow.Activities.SequentialWorkflow
9
{
10
public ExpenseReportWorkflow()
11
{
12
13
}
14
}
15
}
16

2

3

4

5

6



7

8

9



10

11



12

13

14

15

16

声明工作流参数
在一个工作流运行时,它可以从宿主应用程序中接收参数。参数是ParameterDeclaration类型的对象,一旦工作流初始化完成,参数的值就能通过工作流的Parameters集合来访问。
这里的开始报告程序用了两个参数。第一个参数是开支的总数;第二个是一个传出参数,用来放置审批意见。
定义一个新的方法InitializeComponent,并在构造ExpenseRoportWorkflow类的构造函数中调用它。一下的例子示范了怎样定义两个参数并把它们加到Parameters集合中。


1

2

3



4

5

6

7

8

9

10

11

12

13



14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

使用IfElse活动
IfElse活动用条件表达式来控制工作流中流程的运行。工作流将根据条件表达式的结果来决定执行条件分支(IfElseBranch)中的哪一个活动。
例子中将使用IfElse活动。通过判断从宿主应用程序中传入的Amount参数的值是否小于1000,来决定是否将审报发送到领班,否则发送到经理。
创建IfElse活动
1.定义4个私有变量
类型
|
名称
|
IfElse
|
evaluateExpenseReportAmount
|
IfElseBranch
|
ifNeedsLeadApproval
|
IfElseBranch
|
elseNeedsManagerApproval
|
CodeCondition
|
ifElseLogicStatement
|
2.在InitializeComponent中用默认构造函数实例以上4个对象。
以下的代码示例了怎样创建IfElse活动,并用IfElseBranch活动联系两个逻辑分支。你需要把以下代码放到InitializeComponent方法底部。
1
//
2
3
//
EvaluateExpenseReportAmount
4
5
//
6
7
this.EvaluateExpenseReportAmount.Activities.Add(this
.ifNeedsLeadApproval);
8
9
this.EvaluateExpenseReportAmount.Activities.Add(this
.elseNeedsManagerApproval);
10
11
this.EvaluateExpenseReportAmount.ID = "EvaluateExpenseReportAmount"
;
12
13
//
14
15
//
ifNeedsLeadApproval
16
17
//
18
19
this.ifNeedsLeadApproval.Activities.Add(this
.invokeGetLeadApproval);
20
21
ifElseLogicStatement.Condition += new System.Workflow.Activities.ConditionalExpression(this
.DetermineApprovalContact);
22
23
this.ifNeedsLeadApproval.Condition =
ifElseLogicStatement;
24
25
this.ifNeedsLeadApproval.ID = "ifNeedsLeadApproval"
;
26
27
//
28
29
//
elseNeedsManagerApproval
30
31
//
32
33
this.elseNeedsManagerApproval.Activities.Add(this
.invokeGetManagerApproval);
34
35
this.elseNeedsManagerApproval.Condition = null
;
36
37
this.elseNeedsManagerApproval.ID = "elseNeedsManagerApproval"
;
38

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

WWF在IfElse活动中,有两种评估条件表达式的方式。一种是RoleCondition,这个对象通过使用一组规则来判断条件表达式的结果;另一种就是使用CodeCondition活动。CodeCondition使用一个回调方法,这个回调方法返回一个代表评估结果的布尔值。上面的例子就是使用CodeCondition来决定条件表达式的值。如果Amount参数小于1000,回调方法返回true,否则返回false。以下的代码就是这个回调函数的定义,你可以把它加到工作流类的定义中。
1
private bool DetermineApprovalContact(object
sender, EventArgs e)
2
3

{
4
5
if ( Convert.ToInt32(this.Parameters["Amount"].Value) < 1000 )
6
7
return true;
8
9
10
11
return false;
12
13
}
14

2

3



4

5

6

7

8

9

10

11

12

13

14

构造IfElse分支(IfElseBranch)活动
创建完IfElse活动之后,我们来构造IfElseBranch活动
在这个例子中,每一IfElse活动的分支都使用InvokeMethodActivity活动来通知宿主程序——工作流需要领班或经理的审批才能继续执行。InvokeMethodActivity被设计成调用一个在WWF运行时中的服务接口。我们在同一份代码文件中定义了这个接口。当我们在之后构造宿主程序时,宿主类将实现这个接口,以便能建立工作流和宿主程序的通信(这一段文档上写的很模糊,我reflect后看了源码才明白过来,在最后将补充描述一下)。
构建IfElseBranch活动
构建IfElseBranch活动
1. 在类中定义两个私有字段
类型
|
名称
|
InvokeMethodActivity
|
invokeGetLeadApproval
|
InvokeMethodActivity
|
invokeGetManagerApproval
|
2. 在InitializeComponent中用默认构造函数实例化这两个对象
以下的代码示例了怎样在父活动(IfElse)中创建IfElseBranch活动,并把两个的InvokeMethodActivity联系到对应的IfElseBranch活动上,每个InvokeMethodActivity将调用定义在IExpenseReportService接口中的方法,接口会在稍微实现。你需要把以下代码放到InitializeComponent方法底部。
1
//
2
3
//
invokeGetLeadApproval
4
5
//
6
7
this.invokeGetLeadApproval.ID = "invokeGetLeadApproval"
;
8
9
this.invokeGetLeadApproval.InterfaceType = typeof
(Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.IExpenseReportService);
10
11
this.invokeGetLeadApproval.MethodName = "GetLeadApproval"
;
12
13
//
14
15
//
invokeGetManagerApproval
16
17
//
18
19
this.invokeGetManagerApproval.ID = "invokeGetManagerApproval"
;
20
21
this.invokeGetManagerApproval.InterfaceType = typeof
(Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.IExpenseReportService);
22
23
this.invokeGetManagerApproval.MethodName = "GetManagerApproval"
;
24
25

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

以下代码定义了IExpenseReportService接口
1
using
System;
2
3
using
System.Workflow.ComponentModel;
4
5
using
System.Workflow.Runtime.Messaging;
6
7
8
9
namespace
Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow
10
11

{
12
13
[DataExchangeService]
14
15
public interface IExpenseReportService
16
17
{
18
19
void GetLeadApproval();
20
21
void GetManagerApproval();
22
23
event EventHandler<WorkflowMessageEventArgs> ExpenseReportApproved;
24
25
event EventHandler<WorkflowMessageEventArgs> ExpenseReportRejected;
26
27
}
28
29
}
30
31

2

3

4

5

6

7

8

9

10

11



12

13

14

15

16

17



18

19

20

21

22

23

24

25

26

27

28

29

30

31

监听宿主事件
在这个阶段,工作流已经从宿主程序接受了两个参数(译者注:其中一个为out参数,此时设为null),评估了Amount参数,作出了到底该提请谁确认审批的决定,并通知了宿主程序在继续接下来的处理之前,确认审批。这里,Listen活动和EventSinkActivity活动往往配合使用,来监听宿主程序触发指定的事件。接着,一个approval或rejection事件被引发,工作流继续执行,返回审批结果Result,并终止流程。
Listen活动的每个分支是一个EventDriven活动。EventDriven活动只能使用实现了IEventActivity接口的活动。Listen活动的每个分支中的EventDriven各有一个EventSinkActivity,它们是用来监听宿主程序触发的ExpenseReportApproved或者ExpenseReportRejected事件的。这种工作流和宿主的通信方法其实类似于之前的InvokeMethodActivity的过程,只不过前者是工作流监听宿主事件,而后者是宿主事件工作流中注册的接口。
我们已经在前面的步骤中,把接口和两个事件的定义都完成了。在这里,我们将创建一个Listen活动并和两个EventDriven分支建立连接。每个分支包含一个EventSinkActivity活动,每个EventSink监听一种对应的事件。此外,我们还将创建一些事件处理程序,来处理AfterInvoke事件(译者注:这里的AfterInvoke事件应为Invoked事件)。这些事件处理程序将会把Result参数的值设为approval或者rejected。
构造监听活动
1.在工作流类中定义5个私有字段
类型
|
名称
|
Listen
|
listenApproveReject
|
EventDriven
|
approveEventDriven
|
EventDriven
|
rejectEventDriven
|
EventSinkActivity
|
approveEvent
|
EventSinkActivity
|
rejectEvent
|
2. 在InitializeComponent中实例化。
以下的代码示例了怎样在创建Listen活动和EventSinkActivity活动,来监听宿主程序发出的事件。你需要把以下代码放到InitializeComponent方法底部。
使用EventSinkActivity时,为了在工作流中加入一些附加逻辑,你可以为Invoked事件创建一个事件处理程序。一下是事件处理程序的代码。
1
//
2
3
//
listenApproveReject
4
5
//
6
7
this.listenApproveReject.Activities.Add(this
.approveEventDriven);
8
9
this.listenApproveReject.Activities.Add(this
.rejectEventDriven);
10
11
this.listenApproveReject.ID = "listenApproveReject"
;
12
13
//
14
15
//
approveEventDriven
16
17
//
18
19
this.approveEventDriven.Activities.Add(this
.approveEvent);
20
21
this.approveEventDriven.ID = "approveEventDriven"
;
22
23
//
24
25
//
approveEvent
26
27
//
28
29
this.approveEvent.EventName = "ExpenseReportApproved"
;
30
31
this.approveEvent.ID = "approveEvent"
;
32
33
this.approveEvent.InterfaceType = typeof
(Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.IExpenseReportService);
34
35
this.approveEvent.Roles = null
;
36
37
this.approveEvent.Invoked += new System.EventHandler(this
.approveEvent_Invoked);
38
39
//
40
41
//
rejectEventDriven
42
43
//
44
45
this.rejectEventDriven.Activities.Add(this
.rejectEvent);
46
47
this.rejectEventDriven.ID = "rejectEventDriven"
;
48
49
//
50
51
//
rejectEvent
52
53
//
54
55
this.rejectEvent.EventName = "ExpenseReportRejected"
;
56
57
this.rejectEvent.ID = "rejectEvent"
;
58
59
this.rejectEvent.InterfaceType = typeof
(Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.IExpenseReportService);
60
61
this.rejectEvent.Roles = null
;
62
63
this.rejectEvent.Invoked += new System.EventHandler(this
.rejectEvent_Invoked);
64
65

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

完成顺序工作流
1
private void approveEvent_Invoked(object
sender, EventArgs e)
2
3

{
4
5
this.Parameters["Result"].Value = "Report Approved";
6
7
}
8
9
10
11
private void rejectEvent_Invoked(object
sender, EventArgs e)
12
13

{
14
15
this.Parameters["Result"].Value = "Report Rejected";
16
17
}
18
19

2

3



4

5

6

7

8

9

10

11

12

13



14

15

16

17

18

19

这个工作流包括两个主要的步骤:第一,监听宿主程序的递交审批事件,并把传入的值作为工作流参数;第二,监听通过或拒绝审批消息。一下的代码示例了怎样通过把之前创建好的活动加到工作流活动集中,来完成工作流的构造。
1
//
2
3
//
ExpenseReportWorkflow
4
5
//
6
7
this.Activities.Add(this
.EvaluateExpenseReportAmount);
8
9
this.Activities.Add(this
.listenApproveReject);
10
11
this.DynamicUpdateCondition = null
;
12
13
this.ID = "ExpenseReportWorkflow"
;
14
15

2

3

4

5

6

7

8

9

10

11

12

13

14

15

创建宿主程序
WWF需要一个宿主程序来运行工作流。当程序开始运行,WWF运行时引擎也随之启动。而之前构造好的工作流,则到用户点击了Submit按钮后才真正启动。
建立一个新的源文件,取名Program。以下的代码包含了完整的WinForm应用程序。IExpenseReportService接口GetLeadApproval和GetmanagerApproval方法已经定义在另一个文件中。宿主程序实现了这个接口。在approval和rejected按钮的click事件中,将引发ExpenseReprotApproval或ExpenseReprotRejected事件。
1
using
System;
2
3
using
System.ComponentModel;
4
5
using
System.Drawing;
6
7
using
System.Windows.Forms;
8
9
using
System.Collections.Generic;
10
11
using
System.Workflow.Runtime;
12
13
using
System.Workflow.Runtime.Hosting;
14
15
using
System.Workflow.Runtime.Messaging;
16
17
18
19
namespace
Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflowHost
20
21

{
22
23
public class MainForm : Form, Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.IExpenseReportService
24
25
{
26
27
private System.Windows.Forms.Label label1;
28
29
private System.Windows.Forms.TextBox result;
30
31
private System.Windows.Forms.Label label2;
32
33
private System.Windows.Forms.Button submitButton;
34
35
private System.Windows.Forms.Label approvalState;
36
37
private System.Windows.Forms.Button approveButton;
38
39
private System.Windows.Forms.Button rejectButton;
40
41
private System.Windows.Forms.TextBox amount;
42
43
private System.Windows.Forms.Panel panel1;
44
45
46
47
private System.ComponentModel.IContainer components = null;
48
49
50
51
private delegate void GetApprovalDelegate();
52
53
private WorkflowRuntime workflowRuntime = null;
54
55
private WorkflowInstance workflowInstance = null;
56
57
58
59
public MainForm()
60
61
{
62
63
InitializeComponent();
64
65
66
67
// Collapse approve/reject panel
68
69
this.Height -= this.panel1.Height;
70
71
72
73
workflowRuntime = new WorkflowRuntime();
74
75
workflowRuntime.AddService(this);
76
77
workflowRuntime.StartRuntime();
78
79
80
81
workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(workflowRuntime_WorkflowCompleted);
82
83
}
84
85
86
87
protected override void Dispose(bool disposing)
88
89
{
90
91
if (disposing && (components != null))
92
93
{
94
95
components.Dispose();
96
97
}
98
99
base.Dispose(disposing);
100
101
}
102
103
104
105
private void InitializeComponent()
106
107
{
108
109
this.label1 = new System.Windows.Forms.Label();
110
111
this.result = new System.Windows.Forms.TextBox();
112
113
this.label2 = new System.Windows.Forms.Label();
114
115
this.submitButton = new System.Windows.Forms.Button();
116
117
this.approvalState = new System.Windows.Forms.Label();
118
119
this.approveButton = new System.Windows.Forms.Button();
120
121
this.rejectButton = new System.Windows.Forms.Button();
122
123
this.amount = new System.Windows.Forms.TextBox();
124
125
this.panel1 = new System.Windows.Forms.Panel();
126
127
this.panel1.SuspendLayout();
128
129
this.SuspendLayout();
130
131
//
132
133
// label1
134
135
//
136
137
this.label1.AutoSize = true;
138
139
this.label1.Location = new System.Drawing.Point(13, 13);
140
141
this.label1.Name = "label1";
142
143
this.label1.Size = new System.Drawing.Size(39, 13);
144
145
this.label1.TabIndex = 1;
146
147
this.label1.Text = "Amount";
148
149
//
150
151
// result
152
153
//
154
155
this.result.Location = new System.Drawing.Point(13, 69);
156
157
this.result.Name = "result";
158
159
this.result.ReadOnly = true;
160
161
this.result.Size = new System.Drawing.Size(162, 20);
162
163
this.result.TabIndex = 1;
164
165
this.result.TabStop = false;
166
167
//
168
169
// label2
170
171
//
172
173
this.label2.AutoSize = true;
174
175
this.label2.Location = new System.Drawing.Point(13, 54);
176
177
this.label2.Name = "label2";
178
179
this.label2.Size = new System.Drawing.Size(33, 13);
180
181
this.label2.TabIndex = 3;
182
183
this.label2.Text = "Result";
184
185
//
186
187
// submitButton
188
189
//
190
191
this.submitButton.Enabled = false;
192
193
this.submitButton.Location = new System.Drawing.Point(56, 95);
194
195
this.submitButton.Name = "submitButton";
196
197
this.submitButton.Size = new System.Drawing.Size(75, 23);
198
199
this.submitButton.TabIndex = 2;
200
201
this.submitButton.Text = "Submit";
202
203
this.submitButton.Click += new System.EventHandler(this.submitButton_Click);
204
205
//
206
207
// approvalState
208
209
//
210
211
this.approvalState.AutoSize = true;
212
213
this.approvalState.Location = new System.Drawing.Point(10, 9);
214
215
this.approvalState.Name = "approvalState";
216
217
this.approvalState.Size = new System.Drawing.Size(45, 13);
218
219
this.approvalState.TabIndex = 4;
220
221
this.approvalState.Text = "Approval";
222
223
//
224
225
// approveButton
226
227
//
228
229
this.approveButton.Enabled = false;
230
231
this.approveButton.Location = new System.Drawing.Point(11, 25);
232
233
this.approveButton.Name = "approveButton";
234
235
this.approveButton.Size = new System.Drawing.Size(75, 23);
236
237
this.approveButton.TabIndex = 0;
238
239
this.approveButton.Text = "Approve";
240
241
this.approveButton.Click += new System.EventHandler(this.approveButton_Click);
242
243
//
244
245
// rejectButton
246
247
//
248
249
this.rejectButton.Enabled = false;
250
251
this.rejectButton.Location = new System.Drawing.Point(86, 25);
252
253
this.rejectButton.Name = "rejectButton";
254
255
this.rejectButton.Size = new System.Drawing.Size(75, 23);
256
257
this.rejectButton.TabIndex = 1;
258
259
this.rejectButton.Text = "Reject";
260
261
this.rejectButton.Click += new System.EventHandler(this.rejectButton_Click);
262
263
//
264
265
// amount
266
267
//
268
269
this.amount.Location = new System.Drawing.Point(13, 29);
270
271
this.amount.MaxLength = 9;
272
273
this.amount.Name = "amount";
274
275
this.amount.Size = new System.Drawing.Size(162, 20);
276
277
this.amount.TabIndex = 0;
278
279
this.amount.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.amount_KeyPress);
280
281
this.amount.TextChanged += new System.EventHandler(this.amount_TextChanged);
282
283
//
284
285
// panel1
286
287
//
288
289
this.panel1.Controls.Add(this.approvalState);
290
291
this.panel1.Controls.Add(this.approveButton);
292
293
this.panel1.Controls.Add(this.rejectButton);
294
295
this.panel1.Location = new System.Drawing.Point(3, 124);
296
297
this.panel1.Name = "panel1";
298
299
this.panel1.Size = new System.Drawing.Size(172, 66);
300
301
this.panel1.TabIndex = 8;
302
303
//
304
305
// MainForm
306
307
//
308
309
this.AcceptButton = this.submitButton;
310
311
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
312
313
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
314
315
this.ClientSize = new System.Drawing.Size(187, 201);
316
317
this.Controls.Add(this.panel1);
318
319
this.Controls.Add(this.amount);
320
321
this.Controls.Add(this.submitButton);
322
323
this.Controls.Add(this.label2);
324
325
this.Controls.Add(this.result);
326
327
this.Controls.Add(this.label1);
328
329
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;
330
331
this.MaximizeBox = false;
332
333
this.MinimizeBox = false;
334
335
this.Name = "MainForm";
336
337
this.Text = "Simple Expense Report";
338
339
this.panel1.ResumeLayout(false);
340
341
this.panel1.PerformLayout();
342
343
this.ResumeLayout(false);
344
345
this.PerformLayout();
346
347
348
349
}
350
351
352
353
private void submitButton_Click(object sender, EventArgs e)
354
355
{
356
357
Type type = typeof(Microsoft.Samples.Workflow.Quickstarts.SequentialWorkflow.ExpenseReportWorkflow);
358
359
360
361
// Construct workflow parameters
362
363
Dictionary<string, object> properties = new Dictionary<string, object>();
364
365
properties.Add("Amount", Int32.Parse(this.amount.Text));
366
367
properties.Add("Result", string.Empty);
368
369
370
371
// Start the workflow
372
373
workflowInstance = workflowRuntime.StartWorkflow(type, properties);
374
375
}
376
377
378
379
void workflowRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
380
381
{
382
383
if (this.result.InvokeRequired)
384
385
this.result.Invoke(new EventHandler<WorkflowCompletedEventArgs>(this.workflowRuntime_WorkflowCompleted), sender, e);
386
387
else
388
389
{
390
391
this.result.Text = e.OutputParameters["Result"].ToString();
392
393
394
395
// Clear fields
396
397
this.amount.Text = string.Empty;
398
399
400
401
// Disable buttons
402
403
this.approveButton.Enabled = false;
404
405
this.rejectButton.Enabled = false;
406
407
}
408
409
}
410
411
412
413
private void approveButton_Click(object sender, EventArgs e)
414
415
{
416
417
// Raise the ExpenseReportApproved event back to the workflow
418
419
ExpenseReportApproved(null, new WorkflowMessageEventArgs(this.workflowInstance.InstanceId));
420
421
this.Height -= this.panel1.Height;
422
423
this.submitButton.Enabled = true;
424
425
}
426
427
428
429
private void rejectButton_Click(object sender, EventArgs e)
430
431
{
432
433
// Raise the ExpenseReportRejected event back to the workflow
434
435
ExpenseReportRejected(null, new WorkflowMessageEventArgs(this.workflowInstance.InstanceId));
436
437
this.Height -= this.panel1.Height;
438
439
this.submitButton.Enabled = true;
440
441
}
442
443
444
445
IExpenseReportService Members#region IExpenseReportService Members
446
447
448
449
public void GetLeadApproval()
450
451
{
452
453
if (this.approvalState.InvokeRequired)
454
455
this.approvalState.Invoke(new GetApprovalDelegate(this.GetLeadApproval));
456
457
else
458
459
{
460
461
this.approvalState.Text = "Lead approval needed";
462
463
this.approveButton.Enabled = true;
464
465
this.rejectButton.Enabled = true;
466
467
468
469
// expand the panel
470
471
this.Height += this.panel1.Height;
472
473
this.submitButton.Enabled = false;
474
475
}
476
477
}
478
479
480
481
public void GetManagerApproval()
482
483
{
484
485
if (this.approvalState.InvokeRequired)
486
487
this.approvalState.Invoke(new GetApprovalDelegate(this.GetManagerApproval));
488
489
else
490
491
{
492
493
this.approvalState.Text = "Manager approval needed";
494
495
this.approveButton.Enabled = true;
496
497
this.rejectButton.Enabled = true;
498
499
500
501
// expand the panel
502
503
this.Height += this.panel1.Height;
504
505
this.submitButton.Enabled = false;
506
507
}
508
509
}
510
511
512
513
public event EventHandler<WorkflowMessageEventArgs> ExpenseReportApproved;
514
515
public event EventHandler<WorkflowMessageEventArgs> ExpenseReportRejected;
516
517
518
519
#endregion
520
521
522
523
private void amount_KeyPress(object sender, KeyPressEventArgs e)
524
525
{
526
527
if (!Char.IsControl(e.KeyChar) && (!Char.IsDigit(e.KeyChar)))
528
529
e.KeyChar = Char.MinValue;
530
531
}
532
533
534
535
private void amount_TextChanged(object sender, EventArgs e)
536
537
{
538
539
submitButton.Enabled = amount.Text.Length > 0;
540
541
}
542
543
}
544
545
546
547
static class Program
548
549
{
550
551
/**//// <summary>
552
553
/// The main entry point for the application.
554
555
/// </summary>
556
557
[STAThread]
558
559
static void Main()
560
561
{
562
563
Application.EnableVisualStyles();
564
565
Application.Run(new MainForm());
566
567
}
568
569
}
570
571
}
572
573

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21



22

23

24

25



26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61



62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89



90

91

92

93



94

95

96

97

98

99

100

101

102

103

104

105

106

107



108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355



356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381



382

383

384

385

386

387

388

389



390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415



416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431



432

433

434

435

436

437

438

439

440

441

442

443

444

445


446

447

448

449

450

451



452

453

454

455

456

457

458

459



460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483



484

485

486

487

488

489

490

491



492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525



526

527

528

529

530

531

532

533

534

535

536

537



538

539

540

541

542

543

544

545

546

547

548

549



550

551


552

553

554

555

556

557

558

559

560

561



562

563

564

565

566

567

568

569

570

571

572

573

Ps:上面还有个问题没有解释清楚,我reflect了一下,看了源码才知道个大概。
那就是IfElseBranch中的InvokeMethodActivity。
那就是IfElseBranch中的InvokeMethodActivity。
InvokeMethodActivity中有一个Type类型的InterfaceType属性,使用时,需要设置这个属性,并把MethodName设为这个接口中的一个方法的名称。运行时,工作流引擎(也就是WorkflowRuntime)将通过反射调用这个接口。
但引擎怎么知道调用接口的哪个实现呢?你看
workflowRuntime = new WorkflowRuntime();
workflowRuntime.AddService(this);
workflowRuntime.StartRuntime();
原来,初始化引擎时,我们已经把实现了这个interface的类型的实例(this)注册到工作流中了。运行时,引擎就遍历所有已经注册的服务,如果实现了这个接口,这调用它。