組織并執行一系列的操作或者活動的最自然的方式——那就是工作流——同時也是構造一個工作流程的可執行表現形式的最佳途徑。
Windows Workflow Foundation( 以下簡稱WWF)提供了一個程式設計架構和工具以開發和執行各種不同的基于工作流的應用程式,比如文檔管理、線型的商業應用、貿易單據流程、IT管理、B2B應用以及消費者應用。 有狀态的、持久化的、不間斷運作的應用程式 WWF 簡化了創造有狀态的,不間斷運作的異步工作流應用程式的過程。WWF運作時引擎管理工作流的運作,為工作流的長期運作提供保障,并能抵抗機器的重新開機。WWF運作時服務提供了一系列的附加功能,例如WWF服務為能溫和且正确的處理錯誤提供了事務和持久化。 工作流模型 WWF 為開發人員提供了一個工作流模型,來描述應用程式所需要的處理過程。通過使用工作流模型所提供的流程控件、狀态管理、事務和同步器,開發人員可以分離應用程式邏輯和業務邏輯,構造一個高層次的抽象,達到提高開發者效率的目的。 元件的重用 WWF 為開發者提供了一系列的活動——活動是一種包含了工作單元的可配置邏輯結構。這種結構封裝了開發者可能經常性用到的一些部件,這樣就節省了開發者的時間。
如果遇到一些特殊的需求或場景,WWF同樣為開發自定義的活動提供了簡單的方法。
通過将工作流引擎載入程序,WWF可以使任何應用程式和服務容器運作工作流。
運作時服務元件被設計成可插件形式的,這個可使應用程式以最合适的方式來提供它們的服務。WWF還提供了一組運作時服務的預設實作,這些服務能滿足大部分類型的應用程式。
另外,WWF還提供了對ASP.NET的out-of-the-box(啥意思?)支援,讓構造和運作能在IIS和ASP.NET環境的工作流變得簡單。 順序工作流(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
聲明工作流參數 在一個工作流運作時,它可以從宿主應用程式中接收參數。參數是ParameterDeclaration類型的對象,一旦工作流初始化完成,參數的值就能通過工作流的Parameters集合來通路。 這裡的開始報告程式用了兩個參數。第一個參數是開支的總數;第二個是一個傳出參數,用來放置審批意見。 定義一個新的方法InitializeComponent,并在構造ExpenseRoportWorkflow類的構造函數中調用它。一下的例子示範了怎樣定義兩個參數并把它們加到Parameters集合中。 使用IfElse活動
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
IfElse活動用條件表達式來控制工作流中流程的運作。工作流将根據條件表達式的結果來決定執行條件分支(IfElseBranch)中的哪一個活動。 例子中将使用IfElse活動。通過判斷從宿主應用程式中傳入的Amount參數的值是否小于1000,來決定是否将審報發送到領班,否則發送到經理。 建立IfElse活動 1.定義4個私有變量
類型 | 名稱 |
IfElse | evaluateExpenseReportAmount |
IfElseBranch | ifNeedsLeadApproval |
IfElseBranch | elseNeedsManagerApproval |
CodeCondition | ifElseLogicStatement |
2.在InitializeComponent中用預設構造函數執行個體以上4個對象。 以下的代碼示例了怎樣建立IfElse活動,并用IfElseBranch活動聯系兩個邏輯分支。你需要把以下代碼放到InitializeComponent方法底部。 WWF在IfElse活動中,有兩種評估條件表達式的方式。一種是RoleCondition,這個對象通過使用一組規則來判斷條件表達式的結果;另一種就是使用CodeCondition活動。CodeCondition使用一個回調方法,這個回調方法傳回一個代表評估結果的布爾值。上面的例子就是使用CodeCondition來決定條件表達式的值。如果Amount參數小于1000,回調方法傳回true,否則傳回false。以下的代碼就是這個回調函數的定義,你可以把它加到工作流類的定義中。構造IfElse分支(IfElseBranch)活動 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
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
建立完IfElse活動之後,我們來構造IfElseBranch活動 在這個例子中,每一IfElse活動的分支都使用InvokeMethodActivity活動來通知宿主程式——工作流需要領班或經理的審批才能繼續執行。InvokeMethodActivity被設計成調用一個在WWF運作時中的服務接口。我們在同一份代碼檔案中定義了這個接口。當我們在之後構造宿主程式時,宿主類将實作這個接口,以便能建立工作流和宿主程式的通信(這一段文檔上寫的很模糊,我reflect後看了源碼才明白過來,在最後将補充描述一下)。
建構IfElseBranch活動 1. 在類中定義兩個私有字段
類型 | 名稱 |
InvokeMethodActivity | invokeGetLeadApproval |
InvokeMethodActivity | invokeGetManagerApproval |
2. 在InitializeComponent中用預設構造函數執行個體化這兩個對象 以下的代碼示例了怎樣在父活動(IfElse)中建立IfElseBranch活動,并把兩個的InvokeMethodActivity聯系到對應的IfElseBranch活動上,每個InvokeMethodActivity将調用定義在IExpenseReportService接口中的方法,接口會在稍微實作。你需要把以下代碼放到InitializeComponent方法底部。 以下代碼定義了IExpenseReportService接口監聽宿主事件 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
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
在這個階段,工作流已經從宿主程式接受了兩個參數(譯者注:其中一個為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
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
這個工作流包括兩個主要的步驟:第一,監聽宿主程式的遞交審批事件,并把傳入的值作為工作流參數;第二,監聽通過或拒絕審批消息。一下的代碼示例了怎樣通過把之前建立好的活動加到工作流活動集中,來完成工作流的構造。 建立宿主程式 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
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
Ps:上面還有個問題沒有解釋清楚,我reflect了一下,看了源碼才知道個大概。
那就是IfElseBranch中的InvokeMethodActivity。 InvokeMethodActivity中有一個Type類型的InterfaceType屬性,使用時,需要設定這個屬性,并把MethodName設為這個接口中的一個方法的名稱。運作時,工作流引擎(也就是WorkflowRuntime)将通過反射調用這個接口。 但引擎怎麼知道調用接口的哪個實作呢?你看 workflowRuntime = new WorkflowRuntime(); workflowRuntime.AddService(this); workflowRuntime.StartRuntime(); 原來,初始化引擎時,我們已經把實作了這個interface的類型的執行個體(this)注冊到工作流中了。運作時,引擎就周遊所有已經注冊的服務,如果實作了這個接口,這調用它。 另外,注冊服務不一定要用AddService,也可以用WorkflowRuntime.StartWorkflow(Type)。