事件是面嚮對象語言普遍支持和使用的一種模式。事件不僅在與用戶交互的系統中應用很廣泛,設計對象時恰當地採用事件對寫出結構清晰、獨立的代碼也很有幫助。LotusScript 支持事件,各個 UI 對象公佈的事件在程序中都經常使用。不過在 LotusScript 支持的三種對象:Notes 對象、自定義對象和 OLE 對象中,只有 Notes 對象支持事件。也就是說我們只能使用 Notes 類公佈的事件,無法在自定義類中定義事件。
那麼,是否可以在 LotusScript 模擬事件?
事件處理的核心就是當某個“狀態”變化時一個程序(事件源 event emitters)通知預訂(subscribe)處理此事件的另一個程序(事件消費者 event consumers),很多語言通過回調函數實現這樣的機制。LotusScript 不支持任何形式的“函數指針”,所以只能另想辦法。
下面分析一個實際問題。
設想有一個資產管理的數據庫,其中要處理諸如接收、轉移、註銷的多個流程。我們將處理流程的公共代碼都放在一個流程類 SimpleFlow 中。具體的工作流只要創建一個 SimpleFlow 的實例然後調用它的 Submit 方法。每個流程會有一些特定的業務邏輯,比如在資產註銷流程提交前,要檢查一個 Amount 域的值,如果金額大於 5000 需要彈出一個對話框讓用戶輸入更多信息;流程提交後再更新對應的資產文檔。這些業務邏輯可以放在很多地方,比如在表單上提交動作的按鈕或操作中,在調用 SimpleFlow 的 Submit 方法前後加入。如果採用事件的方式考慮,我們希望在 Submit 之前和之後分別引發事件 QuerySubmit 和 PostSubmit,在它們的處理程序(event handlers)中添加流程實例特定的代碼。
爲了使討論集中,本文的示例代碼都只包含與主題相關的部分,不包含處理流程的細節以及錯誤處理代碼。
清單 1. SimpleFlow 類(代碼包含在一個名爲 SimpleFlowLib 的 Script Library 中)
Public Class SimpleFlow '定義公共對象變量 '流程變量 Private strFlow As String Private strNode As String Private strAction As String 'Notes 對象 Private s As NotesSession Private ws As NotesUIWorkspace Private uidoc As NotesUIDocument Private doc As NotesDocument Private db As NotesDatabase '下面是 SimpleFlow 公開的一些屬性 Public Property Get FlowName As String FlowName=strFlow End Property Public Property Get NodeName As String NodeName=strNode End Property Public Property Get ActionName As String ActionName=strAction End Property Public Property Get MainUIDoc As NotesUIDocument Set MainUIDoc=uidoc End Property Public Property Get MainDoc As NotesDocument Set MainDoc=doc End Property Public Property Get MainDb As NotesDatabase Set MainDb=db End Property Public Sub New(flowname As String,nodename As String,actionname As String) strFlow=flowname strNode=nodename strAction=actionname '初始化 Notes 對象 Set s=New NotesSession Set db=s.CurrentDatabase Set ws=New NotesUIWorkspace Set uidoc=ws.CurrentDocument Set doc=uidoc.Document End Sub '供外部調用的主方法 Public Sub Submit '如果 QuerySubmit 返回 False,則不再繼續 If Not QuerySubmit(Me) Then Exit Sub End If '處理流程的代碼 Call PostSubmit(Me) End Sub End Class |
SimpleFlowLib 中還定義了 SimpleFlow 類調用的 QuerySubmit 和 PostSubmit 方法。
清單 2. QuerySubmit 代碼
Private Function QuerySubmit(flowObj As SimpleFlow) As Boolean '流程提交之前運行 '返回 Boolean 值,如果爲 True 則繼續 Submit;否則取消 QuerySubmit=True Dim ws As New NotesUIWorkspace '檢查流程的名稱和狀態 If flowObj.FlowName="Asset" And _ flowObj.NodeName="Draft" And _ flowObj.ActionName="Submit" Then 'DlgInfo 是一個用來輸入更多信息的表單 If Not ws.DialogBox("DlgInfo") Then '如果用戶取消,就不再提交 QuerySubmit=False End If End If End Function |
清單 3. PostSubmit 代碼
Private Sub PostSubmit(flowObj As SimpleFlow) '流程提交之後運行 Dim viewAsset As NotesView '從 SimpleFlow 的屬性獲得對當前數據庫的引用 'Assets 視圖包含資產文檔並且第一列按 AssetID 排序 Set viewAsset=flowObj.MainDb.GetView("Assets") '資產文檔 Dim docAsset As NotesDocument '檢查流程的名稱和狀態 If flowObj.FlowName="Asset" And _ flowObj.NodeName="5.Manager Approval" And _ flowObj.ActionName="Approve" Then Set docAsset=viewAsset.GetDocumentByKey(flowObj.MainDoc.AssetID(0),True) If Not docAsset Is Nothing Then '更新資產文檔 docAsset.ApprovalStatus="Approved by manager" Call docAsset.Save(True,False) End If End If End Sub |
這樣在資產註銷的主表單中,我們只要引用 SimpleFlowLib,然後在某個按鈕或操作中建立一個 SimpleFlow 對象並提交就可以了。
這樣的模擬可以說和事件的本質相差很多。作爲事件源的 SimpleFlow 對象調用固定的方法而不是由消費者添加事件處理程序。結果就是 QuerySubmit 和 PostSubmit 方法只能和 SimpleFlow 類寫在同一個 ScriptLibrary 中。SimpleFlowLib 失去了部分通用性,每個使用此 Script Library 的數據庫都可能需要修改 QuerySubmit 和 PostSubmit 方法。
爲了克服上面的缺點,我們採用另一種更接近事件本質的方式來實現模擬。下面是一個簡單的公佈一個 Demo 事件的類。
清單 4. EventObject 類(代碼包含在一個名爲 EventObjectLib 的 Script Library 中)
Public Class EventObject '用於給 Demo 事件添加 event handler Public DemoEvent As String '在此方法中觸發 Demo 事件 Public Function Run Call OnDemoEvent End Function '運行 event consumer 添加的 event handler Private Function OnDemoEvent Execute DemoEvent End Function End Class |
在另一個方法中創建一個 EventObject 對象並且添加 Demo 事件的 handler。
清單 5. 添加事件處理程序(代碼包含在一個名爲 TestLib 的 Script Library 中)
Public Function Main Dim eo As New EventObject Set demoObj = New Demo Dim demoHandler As String demoHandler={MessageBox("Hello Everybody!")} eo.DemoEvent=demoHandler Call eo.Run() End Function |
這裏的 demoHandler 可以和普通的方法一樣引用 Notes 對象或者自定義的對象和方法。通常,事件消費者和 EventObject 不會定義在同一個模塊中,這個時候,被調用的對象和方法必須是 public 的,並且還需要在定義 demoHandler 時加上 Use 語句。這樣 LotusScript 在 Execute 語句中裝入(load)臨時模塊時才能訪問到 demoHandler 中引用的對象和方法。
下面在 TestLib 中定義一個用於測試的類。
清單 6. TestLib 中的 (Declarations) 部分
'用於測試的一個簡單的類 Public Class Demo Public Function DoSomething(lan As String) Msgbox "do something in " & lan End Function End Class ‘一個 Demo 類的實例 Public demoObj As Demo |
然後在添加 Demo 事件的 handler 時就可以引用它們。
清單 7. 稍微複雜的事件處理程序
'引用自定義的 Demo 對象的方法 demoHandler={use "Test":demoObj.DoSomething("LotusScript")} |
注意在上面的代碼中使用了冒號 (:),這是 LotusScript 從 Basic 中繼承下來的用於在一行中連接多個語句的方法。這個特性很少用到(在 Designer 中如果用冒號隔開多條語句,Designer 會自動將它們分成多行並去掉冒號),不過在這裏卻恰到好處地讓 demoHandler 的定義簡潔可讀,不用寫成多行字符串。
現在我們用上面的方法重新實現 SimpleFlow 的 QuerySubmit 和 PostSubmit 事件。
清單 8. 修改後的 SimpleFlowLib
'在 (Declarations) 中添加一個公共變量,用於保存事件處理程序返回的結果。 Public EventResult As Boolean '在 SimpleFlow 中添加下列模擬事件的代碼。 '定義 QuerySubmit 和 PostSubmit 事件 Public QuerySubmit As String Public PostSubmit As String '運行 QuerySubmit 的 event handler Private Function OnQuerySubmit OnQuerySubmit=Execute(QuerySubmit) End Function '運行 PostSubmit 的 event handler Private Function OnPostSubmit OnPostSubmit=Execute (PostSubmit) End Function '修改 Submit 方法 Public Sub Submit '如果 QuerySubmit 返回 False,則不再繼續 Call OnQuerySubmit If Not EventResult Then Exit Sub End If '處理流程的代碼 Msgbox ("oops") Call OnPostSubmit End Sub |
同時,我們可以把原來包含了業務邏輯的代碼移出 SimpleFlowLib,放入 TestLib 中,並將其訪問修飾符(access modifier)改爲 Public。這樣 SimpleFlowLib 就可以作爲一個標準的 Script Library 被各個流程公共使用。
清單 9. 修改後的 TestLib
'在這個新增加的方法中,添加 SimpleFlow 對象的事件處理程序,然後提交。 Public Function SubmitFlow Set flowObj = New SimpleFlow("Asset", "Draft", "Submit") flowObj.QuerySubmit={Use"TestLib":EventResult=QuerySubmit} flowObj.PostSubmit={Use"TestLib":PostSubmit} Call flowObj.Submit() End Function |
真正的事件機制往往比上面的示例複雜很多,比如事件源可以向處理程序傳遞參數,事件消費者可以爲一個事件添加多個處理程序,也可以去除已有的處理程序。下面的代碼部分地模擬了這些特性。在 SimpleFlow 類中添加了通用的事件處理代碼後,就可以自由地定義事件,引用該類的實例的程序也可以動態的增加刪除事件處理程序。
清單 10. SimpleFlow 中通用的事件處理代碼
'定義通用的事件列表 Private EventList List As Variant '添加事件處理程序 Public Function AddEventHandler(eventName As String, handler As String) Dim handlerList List As String Dim v As Variant If Iselement(EventList(eventName)) Then v=EventList(eventName) v(handler)=handler EventList(eventName)=v Else handlerList(handler)=handler EventList(eventName)=handlerList End If End Function '去除事件處理程序 Public Function RemoveEventHandler(eventName As String, handler As String) '需要在 (Options) 中添加 %INCLUDE "lserr.lss" On Error ErrListItemDoesNotExist Goto ExitFunction Dim handlerList As Variant handlerList=EventList(eventName) Erase handlerList(handler) EventList(eventName)=handlerList ExitFunction: Exit Function End Function '運行 EventList 中某事件的所有處理程序 Private Sub OnEvent(eventName As String) If Iselement(EventList(eventName)) Then Dim v As Variant v=EventList(eventName) Forall handler In v Execute handler End Forall End If End Sub '重新改寫的 Submit 方法 Public Sub Submit '如果 QuerySubmit 返回 False,則不再繼續 Call OnEvent("QuerySubmit") If Not EventResult Then Exit Sub End If '處理流程的代碼 Msgbox ("oops") Call OnEvent("PostSubmit") End Sub |
在 TestLib 中引用 SimpleFlow 的代碼也做相應的修改:
清單 11. 再次修改後的 TestLib
'採用通用事件 Public Function SubmitFlowEx Set flowObj = New SimpleFlow("Asset", "Draft", "Submit") Call flowObj.AddEventHandler("QuerySubmit",{Use"TestLib":EventResult=QuerySubmit}) '可以增加任意多個 event handler 'Call flowObj.AddEventHandler("QuerySubmit",{Use"TestLib":QuerySubmitHandler2}) Call flowObj.AddEventHandler("PostSubmit",{Use"TestLib":PostSubmit}) '去除 event handler Call flowObj.RemoveEventHandler("QuerySubmit",{Use"TestLib":QuerySubmitHandler2}) Call flowObj.Submit() End Function |
通過模擬事件可以寫出更方便移植的自定義類。不過與很多語言本身的事件機制相比,還是有很多侷限性。事件處理程序僅僅通過一個字符串來傳遞,無法檢查類型和簽名,缺乏安全性。只有通過公共變量才能在事件源和消費者之間傳遞事件的相關信息。事件處理程序必須定義在一個 Script Library 中,事件源才能通過 Use 語句引用並訪問。這些限制都使得模擬事件的應用不甚方便。
學習
- “如何在 LotusScript 中處理組”(developerWorks,2005 年 3 月):本文將介紹如何使用內置 NotesAdministrationProcess 類以及兩個定製 LotusScript 類在 LotusScript 中處理組。
- “用 LotusScript 實現 Excel 報表的自動生成和操作”(developerWorks,2006 年 10 月):本文介紹在 Lotus 平臺下,用 LotusScript 語言實現 Excel 報表操作功能的原理、方法和一些實用技巧。
- “使用 LotusScript 和自動化集成 IBM Lotus Notes 和 Microsoft Office”(developerWorks,2009 年 5 月):本文介紹的技術讓 Microsoft Office 應用程序可以以不同的格式向 IBM Lotus Notes 數據庫發佈信息。
- 參閱:developerWorks
Lotus Notes 和 Domino 產品頁面。
獲得產品和技術
- 下載試用版:Lotus Domino Designer V8.5.1。
- 下載試用版:Lotus Domino 和 Lotus iNotes。
- 下載試用版:IBM Lotus Notes 和 Domino Administrator 8.5 客戶端軟件。
討論