在第八章(“呼叫外部方法和工作流”)中,你看過工作流怎樣使用CallExternalMethod活動來和宿主應用程式進行通訊。當工作流呼叫一個外部方法時,使用一個你提供的本地通訊服務,該宿主應用程式會收到一個事件,然後宿主對資料進行處理併產生一些相應的動作。
相反的呼叫過程是宿主應用程式觸發的事件被工作流捕獲進行處理(儘管工作流事件處理可被用在更廣泛的任務中,而不僅僅是和宿主進行通訊)。在第八章中,我提到過在對工作流用來處理事件的活動進行敘述後,我們還將重溫宿主/工作流之間的通訊,在本章中我們將完成這件事。
在目前為止的其它章節中,我都是單獨地對某個工作流活動進行描述,然後提供一個小應用程式來演示該活動的操作過程。和這些章節不同,本章將在一個示例應用程式中對多個活動進行描述和演示。為什麼這樣做呢?因為我在這裡要描述的這些活動都是相互關聯互相依賴的。我不能演示其中一個活動而對其它的活動不進行演示。Listen活動可作為EventDriven活動的容器。在EventDriven活動的內部,你還會不出所料找到唯一的一個HandleExternalEvent活動等等。因此在本章中我將從始至終只建立一個應用程式來對這些活動進行描述和演示。“宿主到工作流”這一節是本章的主線。我們首先從HandleExternalEvent活動開始。
使用HandleExternalEvent活動
不管在你的工作流中在何處處理事件,也不管你的工作流正處於執行狀態時所發現要執行的是什麼樣的活動組合,只要當一個事件來到了你的工作流路徑當中,HandleExternalEvent活動就是最終去處理該事件的工作流活動。對我來說,.NET的強大的功能特性很多,它的觸發和處理事件的能力就是這些最強大的功能中的一個。包括工作流事件的處理也同樣強大。
HandleExternalEvent活動的作用是響應一個基於IEventActivity介面的事件,它有三個主要的成員:QueueName屬性、Subscribe和Unsubscribe方法。QueueName表示正等待該事件的工作流佇列,而Subscribe和Unsubscribe方法用來把你的事件處理程式將要接收(或者不將進行接收)的特定事件例項告知工作流執行時。
HandleExternalEvent活動本身也可和CallExternalMethod活動一起使用(我們在第8章中看到過)。工作流使用CallExternalMethod活動來把資料傳送給宿主應用程式,但是在工作流執行時,工作流使用HandleExternalEvent來接收從宿主中傳送過來的資料。
備註:牢記:使用外部資料交換的時機並不僅僅是在把資料從你的宿主應用程式傳送到工作流的時候。當你建立你的工作流例項的時候,你可總是為其提供初始資料。但是,一旦工作流正在執行時,對於直接和你的宿主應用程式進行本地通訊來說,它是唯一可使用的機制(當然也可使用更加間接的方式替代,例如使用FTP協議或者Web服務呼叫這些手段)。
表10-1和表10-2列出了使用HandleExternalEvent活動時經常用到的一些主要的屬性和方法。注意有些方法和屬性是所有活動共有的(如在第四章“活動和工作流型別介紹”中表4-1和表4-2展示的一樣)。我在此展示的屬性和方法無疑不是所有可使用的屬性和方法,但他們卻是經常要被用到的。
表10-1 經常用到的HandleExternalEvent活動的屬性
屬性 | 功能 |
CorrelationToken | 獲取或設定一個到關聯標記(correlation token)的繫結。我們將在第17章(“關聯和本地宿主通訊”)中處理關聯。 |
EventName | 活動將要處理的事件。注意如果沒有對其進行設定,該活動將不會對事件進行監聽並且和宿主通訊也就不可能進行。奇怪的是,忽略該屬性值你不會收到任何錯誤驗證資訊。 |
InterfaceType | 獲取或設定進行通訊所要使用的介面型別。該介面必須使用ExternalDataExchange特性進行裝飾(標記)。(你或許可回憶一下第8章,你為CallExternalMethod方法提供了一個相同的介面。) |
表10-2 經常用到的HandleExternalEvent活動的方法
方法 | 功能 |
OnInvoked | 這是一個有很用的保護型(protected)方法,它用來把本事件引數中的值和你工作流中的欄位或依賴屬性進行繫結。重寫該方法(或者處理它所觸發的事件)是檢索來自於宿主並被儲存到事件引數中的資料一個主要的機制,通常,你會建立一個自定義的事件引數來把資料嵌入進引數物件自身中。 |
儘管你能直接從Visual Studio的工具箱中使用HandleExternalEvent活動,但更普遍的情形是使用你在第8章中看過的wca.exe工具來為你正使用的通訊介面建立一個派生自HandleExternalEvent的自定義類。例如,假如在你的介面中定義了一個名稱為SendDataToHost的事件,wca.exe將會生成一個稱作SendDataToHost的新活動(它派生自HandleExternalEvent),併為你指定了EventName和InterfaceType,而且通過你建立的事件引數也為你和SendDataToHost事件進行了資料繫結。在本章晚些時候我將提供一個例子。
使用HandleExternalEvent很容易,只需簡單地在你的工作流中放入該活動,指定介面和事件名。假如你需要的話,還可為Invoked事件提供一個event handler,然後就可執行你的工作流了。假如你使用wca.exe,就可為你提供一個派生自HandleExternalEvent的活動,你可直接把它拖拽到你的工作流中,在屬性視窗中新增繫結,把事件引數中的資料和一個區域性欄位或者依賴屬性繫結在一起。
在你的工作流中有了HandleExternalEvent活動後,在等待事件發生時所有通過該順序流的處理過程都會停止。在一定程度上,在你的工作流中放入這個活動的行為就像.NET Framework程式設計術語中的AutoResetEvent。和AutoResetEvent不同的是,該處理過程的執行緒不是暫停。它就像是一扇門或通道,只有在該事件被觸發時才允許工作流處理過程沿著它的路徑次序繼續前進。
使用Delay活動
在本書中我們目前為止已經幾次看到並使用過Delay活動,但現在我將對它進行更加正式的敘述。為什麼呢?很巧,Delay活動實現了IEventActivity介面,因此,它同樣也被歸類為Windows Workflow Foundation(WF)的基於事件的活動。
傳給Delay的是一個TimeSpan物件,它將延時指定的時間間隔。在延時時間過期後,它將觸發一個事件。你可通過在Visual Studio的工作流檢視設計器上,或者以程式設計的方式設定一個屬性(TimeoutDuration)來初始化該延時的時間間隔。它也為你提供了一個event handler(InitializeTimeoutDuration),當Delay活動被初始化並獲取所需的時間間隔資訊時將呼叫該事件處理程式。
提示:延時事件和定時器(timer)事件是密切相關的。WF沒有timer活動,但你能通過用While活動組合該Delay活動來建立一個timer,本章的示例應用程式就使用了這種方式。
HandleExternalEvent和Delay相對於組合(composite)活動而言,它們都是basic(基本)活動。也就是說,HandleExternalEvent和Delay都只執行一個單一的功能,它們不能作為其它活動的容器。正如你可能預料到的,這些活動的普遍用法是基於一個單一的事件來觸發一系列的活動。你又如何支配這些事件序列會是怎麼樣的呢?答案是使用另一個WF活動:EventDriven活動。
使用EventDriven活動
EventDriven的行為就像是一個組合活動,這個組合活動以順序執行的方式執行它所包含的一系列活動。這並不是說你不能在這個容器中插入一個Parallel(並行)活動,但在並行活動之前插入的活動和之後插入的活動都將依順序進行執行。對於它容納的活動的唯一限制是在執行路徑上的第一個活動必須是對IEventActivity進行了處理的活動。(HandleExternalEvent和Delay就是這種型別的兩個活動。)除了從基類繼承而來的屬性和方法外,該EventDriven再沒有其它可使用的屬性和方法。(它僅僅是一個容器。)
和順序活動不同的是,EventDriven在有事件觸發並被第一個活動處理前是不會允許所容納的活動執行的。(記住,第一個活動必須處理IEventActivity。)。
EventDriven的使用還有第二個限制。它的父活動必須是Listen、State或者StateMachineWorkflow之中的一個,在有些地方你是不能把EventDriven拖到你的工作流中的,它只能拖到上述三種容器中。我們將在第14章(“基於狀態的工作流”)中全面介紹State和StateMachineWorkflow活動。但現在還是來看看Listen活動吧。
使用Listen活動
假如說EventDriven的行為像是一個順序活動的話,那Listen活動的行為就像是一個並行(parallel)活動。Listen可作為兩個或更多的EventDriven活動的容器。其中的這些EventDriven活動選定的路徑完全取決於它們中誰第一個收到事件。但是,一旦其中的一個對某個事件進行了處理,其它的和它並行的EventDriven活動的執行路徑都會被忽略而不會被執行,它們不會再繼續等待它們各自的事件,在EventDriven活動處理了相應的事件後,又將按順序繼續執行接下來的路徑。在它的Activity基類所暴露出的屬性和方法外,再沒有我們感興趣的屬性和方法。
需注意的是在Listen活動內必須至少包含兩個及以上的EventDriven活動物件,並且僅僅只有EventDriven型別的活動能直接放到Listen活動中。此外,Listen不能用到基於狀態機的工作流中。為什麼這裡有這些規則和限制呢?
假如WF允許少於兩個的EventDriven子活動的話,Listen活動的作用就值得懷疑。你更好的做法是直接使用一個EventDriven活動。假如子活動中沒有EventDriven活動的話,你也就沒有要去處理的事件。
在基於狀態機的工作流中禁止使用Listen看起來或許是一個很奇怪的限制,其實這是出於可能產生迴圈的考慮。狀態機中迴圈這一術語指的是一系列事件的觸發彼此相互依賴。在一定程度上,這和多執行緒程式設計中的死鎖概念相似。假如事件A依賴於事件B觸發,但事件B又在等待事件A觸發才能執行,我們就說產生了迴圈。在基於狀態機的工作流中禁用並行事件處理是WF設計器用來減少產生潛在的這種迴圈的一種措施。
使用EventHandlingScope活動
回顧目前為止我們看到過的活動中,有處理事件的基本活動、觸發事件的delay活動、能夠組合順序流的組合活動和組合並行流的組合活動。你相信會有結合了順序化和並行化行為特點的和事件有關的活動嗎?這就是EventHandlingScope活動。
EventHandlingScope是一個組合活動,它的作用是去容納一組EventHandler活動(它本身就是IEventActivity型別的物件的容器),以及唯一一個其它的非基於事件的如Sequence或Parallel之類的組合活動。非基於事件的組合活動在EventHandler活動中所容納的全部事件都已處理完畢前會一直執行。在所有這些事件都已觸發並被處理完後,在該工作流的EventHandlingScope活動外面的下一個活動才繼續執行。
宿主到工作流的通訊
在介紹了WF中涉及事件的這些活動後,我現在要展示前面未完成的工作流和宿主之間的通訊體系的另一半。你可以回憶一下第8章,我們通過在工作流例項中使用CallExternalMethod活動來把資訊傳送到宿主程式中。這個被呼叫的“external method”其實是一個你所提供的方法,它由一個你所寫的本地通訊服務暴露出來。該服務能把預定的資料傳給宿主並觸發一個事件,這個事件傳送一個資料到達的訊號,然後宿主採取措施把資料從該服務中讀出(從工作流中接收到了資料後,該服務對資料進行了快取)。
對於相反的過程,即宿主把資料傳送給一個已經執行的工作流來說,也涉及到本地通訊服務、事件以及為處理這些事件的事件處理程式。當你為宿主和工作流之間設計好了進行通訊所要使用的介面時(就像是第8章中“建立服務介面”這一節所展示的一樣),你在介面中新增的方法就是被工作流用來把資料傳送到宿主所使用的方法。在該介面中新增事件能使宿主把資料傳送給已經開始執行的工作流。
本章的示例應用程式將會用到我所描述過的每一個活動。一個EventHandlingScope活動將處理“stop processing(停止處理)”事件。一個Sequence活動將包含一個對股票行情更新進行模擬的工作流處理過程。當股價被更新時,新價將會被傳到宿主中並在使用者介面上顯示出來(如圖10-1所示)。本eBroker應用程式並不是真實地對每一隻股票程式碼的當前股價進行檢查,它使用一個簡單的蒙特卡羅模擬法來計算最新的股價。蒙特卡羅模擬是使用了隨機數字的模擬方法,它和通過擲骰子來獲取相應結果的過程類似。我們這樣做的目的只是為了去看看工作流和宿主之間是怎樣進行通訊的。
圖10-1 eBroker的主使用者介面
該eBroker應用程式應能讓工作流知道,新增的新的當前並未被監視的股票應該要被監視到,而新增時如該股票本已存在則不予考慮(目的是為了簡化處理)。你可使用Add和Remove按鈕來模擬股票的新增和刪除。點選Add將彈出如圖10-2所示的對話方塊。當你輸完後點選OK,這個新的要被監視的股票就被新增進被監視股票的列表中了。
圖10-2 新增一個新的要被監視的股票
在“Ticker values”列表中選擇一條記錄,這會啟用Remove按鈕。點選該Remove按鈕就可把該項從被監視的股票列表中移除。該移除動作產生的結果如圖10-3。你正監視的股票被儲存在應用程式的Settings檔案(XML格式的配置檔案)中。下一次你執行eBroker時,它將“記起”你的這些股票並重新開始進行監視。
圖10-3 移除一個已存在的被監視的股票
在圖10-2中,你看到了應用程式需要知道你當前有多少股份以便能計算你所擁有的股份的總價值,這些數字可被用來計算當前的市值。假如你後來想要修正股份的數量(通過買賣股票),你可選中市值(market value)列表中的股票然後點選Buy!或者Sell!該對話方塊如圖10-4所示。
圖10-4 需要去買或賣的股份數對話方塊
圖10-2中的這個Add新增對話方塊也需要輸入買或賣的“觸發”條件值,當你不應該買進或賣出你目前所監視的任何公司的股票時,工作流中包含的使用了這些值的業務邏輯會通告你。假如股票價格超過了預定的觸發賣價的值,則在市值列表中將顯示一個紅色的標記。假如股票價格低於預定的觸發買價的值,將顯示一個綠色的標記。你能在任何時候進行買賣...這些標記只是起提示作用,在圖10-5中你可看到這組標記。
圖10-5 指出了買賣建議的eBroker使用者介面
這四個按鈕(Add、Remove、Buy!和Sell!)中的每一個都會觸發一個到工作流的事件。還有第5個事件,就是Stop,它用來停止模擬的執行過程,這個事件由Quit按鈕觸發。
該應用程式的許多地方其實我已經為你寫完了,這使你能把注意力放到和工作流相關的地方。首先,你要完成工作流和宿主將用來進行通訊的介面,然後你要使用wca.exe工具來建立繼承自CallExternalMethod和HandleExternalEvent的一組活動。準備好了這些,你就可使用本章中看到過的每一個活動來佈置該工作流。你將看到本地通訊服務是怎樣把宿主應用程式和工作流通訊處理程式粘合到一起的。最後,你將簡要地進行檢查並新增一些程式碼到eBroker使用者介面原始檔中,以指引它和工作流進行互動。我們就開始吧!
建立通訊介面
我們只需要一個方法:MarketUpdate,它把市場價格資訊返回到使用者介面上,另外還需要五個事件,這些事件分別是AddTicker、RemoveTicker、BuyStock、SellStock和Stop,它們用來驅動工作流。這唯一的一個方法和五個事件都要新增到一個介面中,我們將首先建立這個介面。任何和本地通訊服務相關的事情都依賴於這個介面。
建立一個工作流資料通訊介面
1.下載本章原始碼,從Visual Studio中開啟eBroker應用程式解決方案。
備註:和本書中絕大部分示例應用程式一樣,本eBroker示例應用程式也有兩個版本:完整版本和非完整版本。非完整版是學習本示例應用程式的版本,你在此需要開啟該版本以便進行本示例應用程式的練習和學習。
2.在開啟的本解決方案中你將找到三個專案。展開eBrokerService專案,然後開啟IWFBroker.cs檔案準備進行修改。
3.定位到eBrokerService名稱空間,在該名稱空間中新增下面的程式碼並儲存:
[ExternalDataExchange]
public interface IWFBroker
{
void MarketUpdate(string xmlMarketValues);
event EventHandler<TickerActionEventArgs> AddTicker;
event EventHandler<TickerActionEventArgs> RemoveTicker;
event EventHandler<SharesActionEventArgs> BuyStock;
event EventHandler<SharesActionEventArgs> SellStock;
event EventHandler<StopActionEventArgs> Stop;
}
4.對本專案進行編譯,假如存在編譯錯誤,修正所有錯誤。
不要忘記ExternalDataExchange屬性。沒有它你就不能使用我在這裡所描述的資料傳送機制來成功地在工作流和宿主之間進行資訊的傳送。
在你建立通訊活動(使用wca.exe工具)之前,花點時間來看看eBrokerService專案中的event arguments。MarketUpdateEventArgs實際上只不過是System.Workflow.ExternalDataEventArgs的強型別版本,StopActionEventArgs也是。System.Workflow.ExternalDataEventArgs這個event argument類不傳送資料,但是,TickerActionEventArgs和SharesActionEventArgs都要傳送資料給工作流。TickerActionEventArgs承載的是代表要新增和移除的股票的XML資料,而SharesActionEventArgs承載的是作為主鍵的股票程式碼以及要買或賣的股票數目。
提示:設計這些event argumeents是很重要的,因為這些event arguments把資料從宿主傳給工作流。此外,wca.exe工具會檢查這些event arguments並建立到派生類的繫結,使你能從event arguments中訪問到這些派生類中的資料,彷彿這些資料就是這些event arguments所固有的。換句話說,假如event arugment有一個命名為OrderNumber的屬性,則wca.exe建立的類就會有一個命名為OrderNumber的屬性。它的值來自於事件的事件引數,並會為你自動指定該值。
現在我們就使用wca.exe工具來建立通訊活動
建立通訊活動
1.點選“開始”選單,然後點選“執行”按鈕開啟“執行”對話方塊。
2.輸入cmd,然後點選確定。
3.使用cd命令把起始目錄定位到eBrokerService專案生成的程式集所對應的目錄下,如cd "...\eBroker\eBrokerService\bin\Debug"。
4.就如第8章中做過的一樣,在命令列提示符中輸入下面的命令(包含有雙引號):"<%Program Files%>\Microsoft SDKs\Windows\v6.0A\bin\wca.exe" /n:eBrokerFlow eBrokerService.dll。(注意該“<%Program Files%>”表示你的Program Files目錄的位置,通常是“C:\Program Files”。)然後按下Enter鍵。
5.wca.exe會載入它在eBrokerService.dll找到的程式集,然後掃描使用了ExternalDataExchange特性修飾的介面,在這個例子中這個介面是IWFBroker。被解析出的那個方法被轉換成派生自CallExternalMethod活動的類並儲存到名稱為IWFBroker.Invokes.cs的檔案中。那些事件也相似地被轉換為派生自HandleExternalEvent活動的類並被放進IWFBroker.Sinks.cs檔案中。在命令提示符的命令列中鍵入下面的命令來對該“invokes”檔案重新命名:ren IWFBroker.Invokes.cs ExternalMethodActivities.cs。
6.通過在命令提示符的命令列中鍵入下面的命令來對該“sinks”檔案重新命名:ren IWFBroker.Sinks.cs ExternalEventHandlers.cs。
7.使用下面的命令把當前目錄下的這兩個檔案移到工作流專案的目錄中:move External*.cs ..\..\..\eBrokerFlow。
8.現在回到Visual Studio中,向eBrokerFlow工作流專案中新增這兩個剛建立好的檔案。
9.編譯eBrokerFlow專案,在成功編譯後,在工作流的檢視設計器介面下的工具箱中將呈現出AddTicker、ButStock等自定義事件活動。
注意:作為提醒,假如編譯了工作流解決方案後這些新活動沒有在工具箱中呈現出來,就請關閉eBroker解決方案再重新開啟它,以強制對這些自定義活動進行載入。在下一節我們將使用它們。
建立broker工作流
1.在Visual Studio的檢視設計器中開啟eBrokerFlow專案中的Workflow1.cs檔案。
2.我們需要插入一個Code活動,它被用來為一個Delay活動(你稍後將會插入它)指定預期的延遲時間,並初始化一些內部資料結構。因此,拖拽一個Code活動到工作流檢視設計器的介面上,然後在ExecuteCode屬性中鍵入Initialize並按下Enter鍵,以便在工作流程式碼中建立該Initialize事件處理程式。然後,回到工作流的檢視設計器介面上繼續新增活動。
備註:延時時間值儲存在Settings(配置檔案)中。
3.接下來拖拽一個EventHandlingScope到工作流檢視設計器介面上。
4.記住,你需要為EventHandlingScope提供一個事件處理程式以及一個子活動,以便在它監聽事件時執行。我們首先建立事件處理程式。為了存取這些事件處理程式,需要把滑鼠指標移到eventHandlingScop1下面的微小矩形圖示上。(這個矩形就是一個“智慧標記。”)
然後這個矩形變成了一個更大、更黑並帶有向下箭頭的矩形。
點選這個向下的箭頭,這會啟用一個帶有圖示的四個快捷選單:檢視 EventHandlingScope、檢視取消處理程式、檢視錯誤處理程式和檢視事件處理程式。
點選最下面的一個選單項、切換到事件處理程式檢視。你看到的這個使用者介面和你在第七章(“基本活動操作”)中看到的和錯誤處理程式相聯絡的使用者介面很相似。
拖拽一個EventDriven活動到工作流檢視設計器介面上,把它放到這個矩形的中間(在這裡你會看到“將EventDrivenActivity拖放至此”的文字說明)。
5.現在回到工具箱中,在eBrokerFlow元件區域中找到Stop活動。拖拽一個該活動到工作流檢視設計器介面上,把它放進你在前一個步驟所新增的EventDriven活動中。假如你想對多個事件進行監聽的話,在這時你還可把它們都新增進去。在我們的例子中,這裡只有Stop事件是我們需要的。
6.你剛才就新增好了EventHandlingScope活動將對停止執行進行監聽的事件。下面,你需要為EventHandlingScope新增子活動,當監聽到Stop活動觸發時EventHandlingScope將執行這個子活動。因此,我們需要通過第4步中的第一個子步驟回到eventHandlingScopeActivity1的檢視 EventHandlingScope介面上,但你需要選擇最上面的選單項,而不是最下面的一個。
7.拖拽一個While活動到工作流檢視設計器介面上,把它放到EventHandlingScope活動內。
8.指定它的Condition屬性為程式碼條件而不是宣告性規則條件,指定該事件處理程式的名稱為TestContinue。一旦Visual Studio新增了該TestContinue事件處理程式後,需要回到工作流檢視設計器上,還有更多的活動要進行新增。
9.While活動只能接受唯一的一個子活動,因此拖拽一個Sequence活動到該While活動中。
10.在這裡你需要一個Code活動來對股票價值進行蒙特卡羅模擬,因此拖拽一個Code活動到檢視設計器介面上,把它放進你在前一步驟所新增的Sequence活動中。在屬性視窗中把它重新命名為updateMarket。
11.指定updateMarket這個Code活動的ExecuteCode屬性為UpdateMarketValues。在Visual Studio新增了相應的事件處理程式後回到工作流檢視設計器介面上來,以便繼續佈置你的工作流。
12.在模擬完成後(你將新增的程式碼實際上就是進行模擬),你需要把這些潛在的進行了修改的值傳給宿主應用程式。為此,把滑鼠指標移到工具箱上,找到你在IWFBroker中建立的MarketUpdate活動,把它拖到檢視設計器介面上並放到Sequence活動中的你在前一步驟中所新增的Code活動的下面。
13.MarketUpdate活動需要把一小段XML放送給宿主,要做到這一點,它必須繫結到容納有此時將傳送的XML的欄位屬性。為此,在Visual Studio的屬性皮膚中選擇xmlMarketValues屬性,然後點選瀏覽(...)按鈕,開啟一個“將‘xmlMarketValues’繫結到活動的屬性”的對話方塊。然後點選繫結到新成員選項卡,點選建立屬性,在新成員名稱中輸入Updates。最後點選確定。Visual Studio就新增了Updates這個依賴屬性。
14.為了讓你能處理來自於宿主的事件,拖拽一個Listen活動到設計器介面上,把它放進Sequence活動中。
15.假如你回憶一下,你會記起IWFBroker介面宣告瞭五個事件,它們中的一個是我們已經用過的Stop,還有四個事件要去處理。Listen活動目前僅僅容納了兩個EventDriven活動,但新增更多的EventDriven活動也很容易。你需要簡單地拖拽多達三個的EventDriven活動進Listen活動中。為什麼要新增三個而不是正好的兩個呢?因為第五個EventDriven活動要包含一個行為像是定時器的Delay活動,當延時過期後,Listen活動會結束該工作流執行緒。然後While活動對執行條件進行檢測判斷,而返回的這個條件總被設定為true,於是使While活動不停地迴圈。在股票價值被更新併傳送給宿主後,Listen活動又對新一輪來自宿主的事件進行監聽。
16.在最右邊的EventDriven活動中,拖拽並放入一個Delay活動,在屬性皮膚中把它命名為updateDelay。
17.接下來從eBrokerFlow中拖拽一個SellStock活動到工作流檢視設計器介面上,把它放到從右邊數起的第二個EventDriven活動中。
18.在Visual Studio的屬性皮膚中選擇NumberOfShares屬性,點選瀏覽(...)按鈕,這會又一次開啟一個“將‘NumberOfShares’繫結到活動的屬性”的對話方塊。點選繫結到新成員選項卡,然後再點選建立欄位,並在新成員名稱中輸入_sharesToSell,最後點選確定。Visual Studio就新增了這個_sharesToSell欄位。
備註:我在這裡選擇建立_sharesToSell依賴屬性而不是欄位是因為欄位從來不會被Workflow1類的外部訪問到。它提供的基於XML格式的市場價值資訊要傳給宿主,因此應當把外部訪問許可權暴露出來。
19.Symbol屬性也必須進行繫結。下面的步驟和上一步驟是一樣的,只是欄位名稱要命名為_tickerToSell。
20.為了賣出股票,要拖拽一個Code活動放到SellStock事件處理程式的下面。在它的ExecuteCode屬性中輸入SellStock,在插入了對應的事件處理程式後請回到工作流檢視設計器介面上來。
21.我們現在要對買股票的邏輯進行新增。拖拽一個BuyStock事件處理活動(也來自於eBrokerFlow)到設計器介面上,把它放到正中間的EventDriven活動中。
22.使用第18步的步驟,把BuyStock活動的NumberOfShares屬性繫結到一個新的欄位,名稱為_sharesToBuy。同樣,使用第19步的步驟,把它的Symbol屬性也繫結到一個新的欄位,名稱為_tickerToBuy。
23.和你需要一個Code活動去賣股票一樣,你也需要一個Code活動去買股票。重複第12步新增一個新的Code活動,設定它的ExecuteCode屬性為BuyStock。
24.重複第17步至第20步兩次,把RemoveTicker和AddTicker事件也新增到Listen活動中。RemoveTicker活動的TickerXML屬性要繫結到一個新的名稱為_tickerToRemove的欄位,而為該RemoveTicker事件新增的Code活動的ExecuteCode屬性指定為RemoveTicker。同樣地,AddTicker活動的TickerXML屬性要繫結到_tickerToAdd,和它相聯絡的Code活動的ExecuteCode屬性指定為AddTicker。完成這些後,Listen活動的外觀如下所示:
25.編譯你的這個工作流,糾正任何出現的編譯錯誤。
26.在Visual Studio中開啟Workflow1.cs的原始檔準備進行編輯。
27.Visual Studio為你新增了大量的程式碼,因此你首先定位到Workflow1的構造器並在該構造器下新增如下的程式碼。你插入的這些程式碼可被認為是初始化程式碼。當工作流啟動時,你將把一個資料字典傳給該工作流,這個資料字典包含有以股票程式碼(如“CONT”)作為關鍵字的要監視的若干股票資訊的集合。你也需要指定一個輪詢間隔,它是再一次對股票市值進行檢測前工作流所要等待的時間值。
new Dictionary<string, eBrokerService.Ticker>();
private string _tickersXML = null;
public string TickersXML
{
get { return _tickersXML; }
set { _tickersXML = value; }
}
private TimeSpan _interval = TimeSpan.FromSeconds(7);
public TimeSpan PollInterval
{
get { return _interval; }
set { _interval = value; }
}
28.下面定位到你在步驟2中為你的第一個Code活動新增的Initialize事件處理程式。插入下面的程式碼:
// Establish the market update timeout
updateDelay.TimeoutDuration = PollInterval;
// Stuff the known ticker values into the dictionary
// for later recall when updating market conditions.
eBrokerService.Tickers tickers = null;
using (StringReader rdr = new StringReader(TickersXML))
{
XmlSerializer serializer =
new XmlSerializer(typeof(eBrokerService.Tickers));
tickers = (eBrokerService.Tickers)serializer.Deserialize(rdr);
}
foreach (eBrokerService.Ticker ticker in tickers.Items)
{
// Add the ticker to the dictionary
_items.Add(ticker.Symbol, ticker);
}
提示:為了方便,我在這個初始化方法中對該Delay活動的TimeoutDuration進行了指定。但是不要忘了,你也能使用Delay活動的InitializeTimeoutDuration方法來做同樣的工作。
29.找到TestContinue事件處理程式,While活動使用它來對是否繼續進行迴圈進行判斷。插入下面的程式碼讓While活動不停迴圈(不用擔心...實際上它最終會停止迴圈的!):
e.Result = true;
30.下面要插入的程式碼塊很長,它使用了蒙特卡羅模擬來對股票市場價進行更新。找到和名稱為updateMarket的Code活動(參見第10步)相對應的UpdateMarketValues事件處理程式,插入下面的程式碼:
// Iterate over each item in the dictionary and decide
// what it's current value should be. Normally we'd call
// some external service with each of our watch values,
// but for demo purposes we'll just use random values.
Random rand = new Random(DateTime.Now.Millisecond);
eBrokerService.UpdateCollection updates = new eBrokerService.UpdateCollection();
foreach (string key in _items.Keys)
{
// Locate the item
eBrokerService.Ticker item = _items[key];
// If we're starting out, we have no current value,
// so place the value at half the distance between the
// buy and sell triggers.
if (item.LastPrice <= 0.0m)
{
// Assign a price
decimal delta = (item.SellTrigger - item.BuyTrigger) / 2.0m;
// The last price must be a positive value, so add
// the delta to the smaller value.
if (delta >= 0.0m)
{
// Add delta to buy trigger value
item.LastPrice = item.BuyTrigger + delta;
} // if
else
{
// Reverse it and add to the sell trigger
// value
item.LastPrice = item.SellTrigger + delta;
} // else
} // if
// Set up the simulation
decimal newPrice = item.LastPrice;
decimal onePercent = item.LastPrice * 0.1m;
Int32 multiplier = 0; // no change
// We'll now roll some dice. First roll: does the
// market value change? 0-79, no. 80-99, yes.
if (rand.Next(0, 99) >= 80)
{
// Yes, update the price. Next roll: will the
// value increase or decrease? 0-49, increase.
// 50-99, decrease
multiplier = 1;
if (rand.Next(0, 99) >= 50)
{
// Decrease the price.
multiplier = -1;
} // if
// Next roll, by how much? We'll calculate it
// as a percentage of the current share value.
// 0-74, .1% change. 75-89, .2% change. 90-97,
// .3% change. And 98-99, .4% change.
Int32 roll = rand.Next(0, 99);
if (roll < 75)
{
// 1% change
newPrice = item.LastPrice + (onePercent * multiplier * 0.1m);
} // if
else if (roll < 90)
{
// 2% change
newPrice = item.LastPrice + (onePercent * multiplier * 0.2m);
} // else if
else if (roll < 98)
{
// 3% change
newPrice = item.LastPrice + (onePercent * multiplier * 0.3m);
} // else if
else
{
// 4% change
newPrice = item.LastPrice + (onePercent * multiplier * 0.4m);
} // else if
} // if
else
{
// No change in price
newPrice = item.LastPrice;
} // else
// Now create the update for this ticker
eBrokerService.Update update = new eBrokerService.Update();
update.Symbol = item.Symbol;
update.LastPrice = item.LastPrice;
update.NewPrice = newPrice;
update.Trend = multiplier > 0 ? "Up" : (multiplier == 0 ? "Firm" : "Down");
update.Action = newPrice > item.SellTrigger ? "Sell" : (newPrice < item.BuyTrigger ? "Buy" : "Hold");
update.TotalValue = newPrice * item.NumberOfShares;
updates.Add(update);
// Update the data store
item.LastPrice = newPrice;
} // foreach
// Serialize the data
StringBuilder sb = new StringBuilder();
using (StringWriter wtr = new StringWriter(sb))
{
XmlSerializer serializer = new XmlSerializer(typeof(eBrokerService.UpdateCollection));
serializer.Serialize(wtr, updates);
} // using
// Ship the data back
Updates = sb.ToString();
基本上,每一次更新迴圈,對於每一隻股票將有20%的機率被修改。假如該股票的價格將被修改,它有一半的機率會上升,有一半的機率會下降。將改變的值是:有75%的機率是當前每股價格的1%,有15%的機率是當前每股價格的2%,有7%的機率是當前每股價格的3%,有3%的機率是當前每股價格的4%。對於每一次迴圈,所有被監視的股票都會被更新,即使它的價格沒有變化。將被髮送回宿主進行顯示的資料是一個XML字串,它包含有各只的股票程式碼、當前價格、根據所買的該只股票數計算出來的總市值、趨勢(上升還是下降)以及是否有要進行買或賣的建議。買賣建議會顯示出一個醒目的標誌(紅或綠),你已經在圖10-5中見過。
31.現在向外部事件處理程式中新增程式碼。首先定位到SellStock事件處理程式,新增下面的程式碼:
// Reduce the number of shares for the given ticker.
try
{
// Find this ticker.
eBrokerService.Ticker item = _items[_tickerToSell];
if (item != null)
{
// Reduce the number of shares.
item.NumberOfShares = item.NumberOfShares - _sharesToSell >= 0 ?
item.NumberOfShares - _sharesToSell : 0;
}
}
catch
{
// Do nothingwe just won't have sold any.
}
32.找到BuyStock事件處理程式,新增下面的程式碼:
// Increase the number of shares for the given ticker.
try
{
// Find this ticker.
eBrokerService.Ticker item = _items[_tickerToBuy];
if (item != null)
{
// Increase the number of shares.
item.NumberOfShares += _sharesToBuy;
}
}
catch
{
// Do nothingwe just won't have purchased any.
}
33.接下來是RemoveTicker,找到它並插入下面的程式碼:
// Remove the given ticker from the watch.
try
{
// Deserialize
eBrokerService.Ticker ticker = null;
using (StringReader rdr = new StringReader(_tickerToRemove))
{
XmlSerializer serializer = new XmlSerializer(typeof(eBrokerService.Ticker));
ticker = (eBrokerService.Ticker)serializer.Deserialize(rdr);
}
// Find this ticker.
if (_items.ContainsKey(ticker.Symbol))
{
// Remove it.
_items.Remove(ticker.Symbol);
}
}
catch
{
// Do nothingwe just won't have removed it.
}
34.最後是AddTicker,插入下面的程式碼:
try
{
// Deserialize
eBrokerService.Ticker ticker = null;
using (StringReader rdr = new StringReader(_tickerToAdd))
{
XmlSerializer serializer = new XmlSerializer(typeof(eBrokerService.Ticker));
ticker = (eBrokerService.Ticker)serializer.Deserialize(rdr);
}
// Add the item if not already existing.
if (!_items.ContainsKey(ticker.Symbol))
{
// Add it.
_items.Add(ticker.Symbol, ticker);
}
}
catch
{
// Do nothingwe just won't have added it.
}
35.假如你現在對本工作流進行編譯,不會出現編譯錯誤。
現在,工作流完成了,我們需要回到我們關注的本地通訊服務和宿主的結合上來。因為我們已經在第8章詳細介紹過這方面的內容,因此我在這裡不再整個進行重新介紹。假如你開啟本例中相關的檔案,你會看到這些程式碼和第8章中看過的很相似。
注意:我在第8章中提到過下面的內容,但它是一個重要的問題,您對這個問題的認識應該得到加強:如果你在工作流和宿主應用程式中對物件或者物件的集合進行了共用的話,執行中就會有風險,這牽涉到多執行緒資料訪問的問題,因為工作流和宿主將共享對同一物件的引用。假如你的應用程式存在這個問題,當在工作流和宿主之間傳遞它們時,你就可考慮對這些物件進行克隆(在你的資料類中實現ICloneable介面),或者使用序列化技術。對於本應用程式,我選擇了XML序列化。
但我想談談聯結器類BrokerDataConnector中的一些程式碼。IWFBroker介面因為包含了事件,因此和我們在第8章中看到的示例的介面不同。因為聯結器類必須實現該介面(在本例中,BrokerDataConnector實現了IWFBroker),因此該聯結器也必須處理這些事件。但是,事件的實現和清單10-1中看到的一樣,沒有特別之處。假如你對該清單從頭一直看到尾,你將看到通常的事件實現和你或許親自寫過的事件實現非常相像。
清單10-1 BrokerDataConnector.cs的完整程式碼
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using System.Data;
namespace eBrokerService
{
public sealed class BrokerDataConnector : IWFBroker
{
private string _dataValue = null;
private static WorkflowBrokerDataService _service = null;
private static object _syncLock = new object();
public static WorkflowBrokerDataService BrokerDataService
{
get { return _service; }
set
{
if (value != null)
{
lock (_syncLock)
{
// Re-verify the service isn't null
// now that we're locked
if (value != null)
{
_service = value;
} // if
else
{
throw new InvalidOperationException("You must provide a service instance.");
} // else
} // lock
} // if
else
{
throw new InvalidOperationException("You must provide a service instance.");
} // else
}
}
public string MarketData
{
get { return _dataValue; }
}
// Workflow to host communication method
public void MarketUpdate(string xmlMarketValues)
{
// Assign the field for later recall
_dataValue = xmlMarketValues;
// Raise the event to trigger host read
_service.RaiseMarketUpdatedEvent();
}
// Host to workflow events
public event EventHandler<TickerActionEventArgs> AddTicker;
public event EventHandler<TickerActionEventArgs> RemoveTicker;
public event EventHandler<SharesActionEventArgs> BuyStock;
public event EventHandler<SharesActionEventArgs> SellStock;
public event EventHandler<StopActionEventArgs> Stop;
public void RaiseAddTicker(Guid instanceID, string tickerXML)
{
if (AddTicker != null)
{
// Fire event
AddTicker(null, new TickerActionEventArgs(instanceID, tickerXML));
} // if
}
public void RaiseRemoveTicker(Guid instanceID, string tickerXML)
{
if (RemoveTicker != null)
{
// Fire event
RemoveTicker(null, new TickerActionEventArgs(instanceID, tickerXML));
} // if
}
public void RaiseBuyStock(Guid instanceID, string symbol, Int32 numShares)
{
if (BuyStock != null)
{
// Fire event
BuyStock(null, new SharesActionEventArgs(instanceID, symbol, numShares));
} // if
}
public void RaiseSellStock(Guid instanceID, string symbol, Int32 numShares)
{
if (SellStock != null)
{
// Fire event
SellStock(null, new SharesActionEventArgs(instanceID, symbol, numShares));
} // if
}
public void RaiseStop(Guid instanceID)
{
if (Stop != null)
{
// Fire event
Stop(null, new StopActionEventArgs(instanceID));
} // if
}
}
}
當宿主執行上面這些“raise”方法來觸發基於使用者輸入的各種事件時,工作流就會執行聯結器的MarketUpdate方法。第8章描述了該工作流用來呼叫MarketUpdate方法的機制。為了看看宿主怎樣呼叫一個用來和工作流進行互動的事件(在事件引數中可根據需要攜帶相應的資料),我們來看看下面的程式碼段。這些程式碼用來在點選Quit按鈕時退出應用程式。
private void cmdQuit_Click(object sender, EventArgs e)
{
// Stop the processing
// Remove from workflow
eBrokerService.BrokerDataConnector dataConnector =
(eBrokerService.BrokerDataConnector)_workflowRuntime.GetService(
typeof(eBrokerService.BrokerDataConnector));
dataConnector.RaiseStop(_workflowInstance.InstanceId);
// Just quit
Application.Exit();
}
為了觸發傳送資料到工作流中的這些事件,你首先需要使用工作流執行時的GetService方法獲取聯結器。注意該服務需要為它指明恰當的聯結器型別,這樣才能去使用它的那些“raise”方法。一旦得到該服務後,你就可簡單地呼叫對應的“raise”方法,為它指定要傳送的必要的資料資訊去生成對應的event arguments就可以了。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Workflow.Runtime;
namespace eBroker
{
public partial class Form1 : Form
{
// Our workflow runtime instance
WorkflowRuntime _workflowRuntime = null;
// Currently executing workflow instance (we'll only have
// one).
WorkflowInstance _workflowInstance = null;
// User interface thread synchronization
protected static object _syncLock = new object();
// Ticker collection
eBrokerService.Tickers _tickers = new eBrokerService.Tickers();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// Load the companies to check
LoadFromSettings();
// Create an instance of the workflow runtime
_workflowRuntime = WorkflowFactory.GetWorkflowRuntime();
_workflowRuntime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(workflowRuntime_WorkflowTerminated);
_workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(workflowRuntime_WorkflowCompleted);
// Process the request, starting by creating the parameters
Dictionary<string, object> parms = new Dictionary<string, object>();
parms.Add("TickersXML", (string)Properties.Settings.Default["Tickers"]);
parms.Add("PollInterval", TimeSpan.FromSeconds((Int32)Properties.Settings.Default["PollInterval"]));
// Create instance.
_workflowInstance = _workflowRuntime.CreateWorkflow(typeof(eBrokerFlow.Workflow1), parms);
// Hook returned data event
eBrokerService.WorkflowBrokerDataService dataService = eBrokerService.WorkflowBrokerDataService.CreateDataService(_workflowInstance.InstanceId, _workflowRuntime);
dataService.MarketUpdated += new EventHandler<eBrokerService.MarketUpdateEventArgs>(dataService_MarketUpdated);
// Start instance.
_workflowInstance.Start();
}
void workflowRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
{
// Clear instance (for application termination purposes)
_workflowInstance = null;
}
void workflowRuntime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
{
// Clear instance (for application termination purposes)
_workflowInstance = null;
// Some error...
MessageBox.Show(String.Format("The brokerage workflow was terminated! Error: {0}", e.Exception.Message));
}
void dataService_MarketUpdated(object sender, eBrokerService.MarketUpdateEventArgs e)
{
IAsyncResult result = this.BeginInvoke(
new EventHandler(
delegate
{
// Retrieve connection. Note we could simply cast the sender as
// our data service, but we'll instead be sure to retrieve
// the data meant for this particular workflow instance.
eBrokerService.WorkflowBrokerDataService dataService = eBrokerService.WorkflowBrokerDataService.GetRegisteredWorkflowDataService();
// Read the market update data
string marketUpdatesXml = dataService.Read();
eBrokerService.UpdateCollection marketUpdates = null;
using (StringReader rdr = new StringReader(marketUpdatesXml))
{
XmlSerializer serializer = new XmlSerializer(typeof(eBrokerService.UpdateCollection));
marketUpdates = (eBrokerService.UpdateCollection)serializer.Deserialize(rdr);
} // using
if (marketUpdates != null)
{
// Save any selected item
Int32 selectedItem = lvMarket.SelectedIndices.Count > 0 ? lvMarket.SelectedIndices[0] : -1;
string selectedSymbol = selectedItem >= 0 ? lvMarket.Items[selectedItem].SubItems[1].Text : String.Empty;
// Bind the values to the list
lvMarket.SuspendLayout();
lvMarket.Items.Clear();
ListViewItem lvi = null;
foreach (eBrokerService.Update update in marketUpdates)
{
// Create the string array
string[] items = new string[5];
items[0] = String.Empty;
items[1] = update.Symbol;
items[2] = update.NewPrice.ToString("C");
items[3] = update.Trend;
items[4] = update.TotalValue.ToString("C");
// Create the list item
lvi = new ListViewItem(items);
// Maintain any selection
lvi.Selected = (update.Symbol == selectedSymbol);
// Check buy/sell triggers. If triggered, toss
// a flag into the list. If the stock hits zero,
// put up the warning.
if (update.Action == "Buy")
{
// Inject buy flag
lvi.ImageIndex = 1;
} // if
else if (update.Action == "Sell")
{
// Inject sell flag
lvi.ImageIndex = 2;
} // else if
else if (update.NewPrice <= 0.0m)
{
// Inject warning flag
lvi.ImageIndex = 0;
} // else if
// Add to the list
lvMarket.Items.Add(lvi);
} // for
lvMarket.ResumeLayout();
} // if
} // delegate
), null, null
); // BeginInvoke
this.EndInvoke(result);
}
private void cmdQuit_Click(object sender, EventArgs e)
{
// Stop the processing
// Remove from workflow
eBrokerService.BrokerDataConnector dataConnector =
(eBrokerService.BrokerDataConnector)_workflowRuntime.GetService(
typeof(eBrokerService.BrokerDataConnector));
dataConnector.RaiseStop(_workflowInstance.InstanceId);
// Just quit...
Application.Exit();
}
private void cmdAdd_Click(object sender, EventArgs e)
{
FormAddTicker dlg = new FormAddTicker();
if (dlg.ShowDialog() == DialogResult.OK)
{
lock (_syncLock)
{
// Add the item
lvTickers.Items.Add(dlg.Item);
// Create the ticker item
eBrokerService.Ticker ticker = new eBrokerService.Ticker();
ticker.Symbol = dlg.Item.SubItems[1].Text;
ticker.Company = dlg.Item.SubItems[2].Text;
ticker.BuyTrigger = decimal.Parse(dlg.Item.SubItems[3].Text.Substring(1));
ticker.SellTrigger = decimal.Parse(dlg.Item.SubItems[4].Text.Substring(1));
ticker.NumberOfShares = Int32.Parse(dlg.Item.SubItems[5].Text);
dlg.Item.Tag = ticker;
// Resize the list of tickers array
eBrokerService.Ticker[] newList = new eBrokerService.Ticker[_tickers.Items.Length + 1];
_tickers.Items.CopyTo(newList, 0);
newList[newList.Length - 1] = ticker;
_tickers.Items = newList;
// Persist the added value into settings
SaveToSettings();
// Set up the market list
string[] vals = new string[4];
vals[0] = String.Empty;
vals[1] = String.Copy(dlg.Item.SubItems[1].Text);
vals[2] = "No data";
vals[3] = "No data";
ListViewItem lvi = new ListViewItem(vals);
lvi.Tag = ticker;
lvMarket.Items.Add(lvi);
// Serialize
StringBuilder sb = new StringBuilder();
using (StringWriter wtr = new StringWriter(sb))
{
XmlSerializer serializer = new XmlSerializer(typeof(eBrokerService.Ticker));
serializer.Serialize(wtr, ticker);
} // using
// Add to workflow
eBrokerService.BrokerDataConnector dataConnector = (eBrokerService.BrokerDataConnector)_workflowRuntime.GetService(typeof(eBrokerService.BrokerDataConnector));
dataConnector.RaiseAddTicker(_workflowInstance.InstanceId, sb.ToString());
} // lock
} // if
}
private void cmdRemove_Click(object sender, EventArgs e)
{
// Remove the selected ticker
if (lvTickers.SelectedIndices.Count > 0)
{
lock (_syncLock)
{
// Remove each selected item
lvTickers.SuspendLayout();
lvMarket.SuspendLayout();
for (Int32 i = lvTickers.SelectedIndices.Count - 1; i >= 0; i--)
{
// Remove this item from the holdings list
ListViewItem lviTicker = lvTickers.Items[lvTickers.SelectedIndices[i]];
eBrokerService.Ticker tickerToRemove = lviTicker.Tag as eBrokerService.Ticker;
lvTickers.Items.Remove(lviTicker);
// Remove this item from the collection
eBrokerService.Ticker[] newTickerList = new eBrokerService.Ticker[_tickers.Items.Length - 1];
for (Int32 j = 0, k = 0; j < _tickers.Items.Length; j++)
{
// Check this item...if the symbol matches the one to be
// removed, don't copy it over
if (_tickers.Items[j] == tickerToRemove)
{
// Skip this one
continue;
} // if
// Copy this one over
newTickerList[k++] = _tickers.Items[j];
} // for
_tickers.Items = newTickerList;
// Remove the item from the market list
foreach (ListViewItem lviMarket in lvMarket.Items)
{
// Check this item
if (lviMarket.SubItems[1].Text == lviTicker.SubItems[1].Text)
{
// Found it...remove it
lvMarket.Items.Remove(lviMarket);
break;
} // if
} // foreach
// Serialize
StringBuilder sb = new StringBuilder();
using (StringWriter wtr = new StringWriter(sb))
{
XmlSerializer serializer = new XmlSerializer(typeof(eBrokerService.Ticker));
serializer.Serialize(wtr, tickerToRemove);
} // using
// Remove from workflow
eBrokerService.BrokerDataConnector dataConnector = (eBrokerService.BrokerDataConnector)_workflowRuntime.GetService(typeof(eBrokerService.BrokerDataConnector));
dataConnector.RaiseRemoveTicker(_workflowInstance.InstanceId, sb.ToString());
} // for
lvMarket.ResumeLayout();
lvTickers.ResumeLayout();
// Persist the remaining values into settings
SaveToSettings();
} // lock
} // if
}
private void cmdBuy_Click(object sender, EventArgs e)
{
FormNumShares dlg = new FormNumShares();
dlg.MaxShares = DetermineMaxShares(lvMarket.Items[lvMarket.SelectedIndices[0]].SubItems[1].Text);
if (dlg.ShowDialog() == DialogResult.OK)
{
// Update the number of shares held in the UI
Int32 totalShares = 0;
foreach (ListViewItem lvi in lvTickers.Items)
{
// Check the ticker...
if (lvi.SubItems[1].Text == lvMarket.Items[lvMarket.SelectedIndices[0]].SubItems[1].Text)
{
// Update the number of shares
totalShares = Int32.Parse(lvi.SubItems[5].Text) + dlg.NumberShares;
lvi.SubItems[5].Text = totalShares.ToString();
break;
} // if
} // foreach
// Update the local data
for (Int32 i = 0; i < _tickers.Items.Length; i++)
{
// Check this item...if the symbol matches the one to be
// removed, don't copy it over
if (_tickers.Items[i].Symbol == lvMarket.Items[lvMarket.SelectedIndices[0]].SubItems[1].Text)
{
// Assign this one
_tickers.Items[i].NumberOfShares = totalShares;
break;
} // if
} // for
// Save to settings
SaveToSettings();
// Buy the requested number of shares
eBrokerService.BrokerDataConnector dataConnector = (eBrokerService.BrokerDataConnector)_workflowRuntime.GetService(typeof(eBrokerService.BrokerDataConnector));
dataConnector.RaiseBuyStock(_workflowInstance.InstanceId, lvMarket.Items[lvMarket.SelectedIndices[0]].SubItems[1].Text, dlg.NumberShares);
} // if
}
private void cmdSell_Click(object sender, EventArgs e)
{
FormNumShares dlg = new FormNumShares();
dlg.MaxShares = DetermineMaxShares(lvMarket.Items[lvMarket.SelectedIndices[0]].SubItems[1].Text);
if (dlg.ShowDialog() == DialogResult.OK)
{
// Update the number of shares held in the UI
Int32 totalShares = 0;
foreach (ListViewItem lvi in lvTickers.Items)
{
// Check the ticker...
if (lvi.SubItems[1].Text == lvMarket.Items[lvMarket.SelectedIndices[0]].SubItems[1].Text)
{
// Update the number of shares
totalShares = Int32.Parse(lvi.SubItems[5].Text) - dlg.NumberShares;
lvi.SubItems[5].Text = totalShares.ToString();
break;
} // if
} // foreach
// Update the local data
for (Int32 i = 0; i < _tickers.Items.Length; i++)
{
// Check this item...if the symbol matches the one to be
// removed, don't copy it over
if (_tickers.Items[i].Symbol == lvMarket.Items[lvMarket.SelectedIndices[0]].SubItems[1].Text)
{
// Assign this one
_tickers.Items[i].NumberOfShares = totalShares;
break;
} // if
} // for
// Save to settings
SaveToSettings();
// Sell the requested number of shares
eBrokerService.BrokerDataConnector dataConnector = (eBrokerService.BrokerDataConnector)_workflowRuntime.GetService(typeof(eBrokerService.BrokerDataConnector));
dataConnector.RaiseSellStock(_workflowInstance.InstanceId, lvMarket.Items[lvMarket.SelectedIndices[0]].SubItems[1].Text, dlg.NumberShares);
} // if
}
protected void LoadFromSettings()
{
lvTickers.Items.Clear();
string tickerXml = (string)Properties.Settings.Default["Tickers"];
using (StringReader rdr = new StringReader(tickerXml))
{
XmlSerializer serializer = new XmlSerializer(typeof(eBrokerService.Tickers));
_tickers = (eBrokerService.Tickers)serializer.Deserialize(rdr);
} // using
ListViewItem lvi = null;
foreach (eBrokerService.Ticker ticker in _tickers.Items)
{
// Load this company
string[] vals = new string[6];
vals[0] = String.Empty;
vals[1] = ticker.Symbol;
vals[2] = ticker.Company;
vals[3] = ticker.BuyTrigger.ToString("C");
vals[4] = ticker.SellTrigger.ToString("C");
vals[5] = ticker.NumberOfShares.ToString();
lvi = new ListViewItem(vals);
lvi.Tag = ticker;
lvTickers.Items.Add(lvi);
// Set up the market list
vals = new string[4];
vals[0] = String.Empty;
vals[1] = String.Copy(ticker.Symbol);
vals[2] = "No data";
vals[3] = "No data";
lvi = new ListViewItem(vals);
lvMarket.Items.Add(lvi);
} // foreach
}
protected void SaveToSettings()
{
StringBuilder sb = new StringBuilder();
using (StringWriter wtr = new StringWriter(sb))
{
XmlSerializer serializer = new XmlSerializer(typeof(eBrokerService.Tickers));
serializer.Serialize(wtr, _tickers);
} // using
// Save the settings
Properties.Settings.Default["Tickers"] = sb.ToString();
Properties.Settings.Default.Save();
}
private void lvTickers_SelectedIndexChanged(object sender, EventArgs e)
{
// If we have a selected item in the ticker list, enable
// the remove button. Disable if not.
if (lvTickers.SelectedIndices.Count > 0 && lvTickers.SelectedIndices[0] != -1)
{
// Enable
cmdRemove.Enabled = true;
} // if
else
{
// Disable
cmdRemove.Enabled = false;
} // else
}
private void MarketSelectionChanged(object sender, EventArgs e)
{
// If we have a selected item in the market list, enable
// the buy/sell buttons. Disable if not.
if (lvMarket.SelectedIndices.Count > 0 && lvMarket.SelectedIndices[0] != -1)
{
// Enable
cmdBuy.Enabled = true;
cmdSell.Enabled = true;
} // if
else
{
// Disable
cmdBuy.Enabled = false;
cmdSell.Enabled = false;
} // else
}
protected Int32 DetermineMaxShares(string ticker)
{
// Search the tickers list for the given ticker value
Int32 retVal = 0;
foreach (ListViewItem lvi in lvTickers.Items)
{
// Check this item
if (lvi.SubItems[1].Text == ticker)
{
// Found it...pull the shares
retVal = Int32.Parse(lvi.SubItems[5].Text);
break;
} // if
} // foreach
return retVal;
}
}
}
namespace eBroker
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
this.label1 = new System.Windows.Forms.Label();
this.lvTickers = new System.Windows.Forms.ListView();
this.columnHeader1 = new System.Windows.Forms.ColumnHeader();
this.columnHeader2 = new System.Windows.Forms.ColumnHeader();
this.columnHeader3 = new System.Windows.Forms.ColumnHeader();
this.columnHeader7 = new System.Windows.Forms.ColumnHeader();
this.columnHeader8 = new System.Windows.Forms.ColumnHeader();
this.columnHeader9 = new System.Windows.Forms.ColumnHeader();
this.ilIcons = new System.Windows.Forms.ImageList(this.components);
this.cmdAdd = new System.Windows.Forms.Button();
this.cmdRemove = new System.Windows.Forms.Button();
this.label2 = new System.Windows.Forms.Label();
this.lvMarket = new System.Windows.Forms.ListView();
this.columnHeader4 = new System.Windows.Forms.ColumnHeader();
this.columnHeader5 = new System.Windows.Forms.ColumnHeader();
this.columnHeader6 = new System.Windows.Forms.ColumnHeader();
this.columnHeader10 = new System.Windows.Forms.ColumnHeader();
this.columnHeader11 = new System.Windows.Forms.ColumnHeader();
this.cmdBuy = new System.Windows.Forms.Button();
this.cmdSell = new System.Windows.Forms.Button();
this.label3 = new System.Windows.Forms.Label();
this.cmdQuit = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(12, 9);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(74, 13);
this.label1.TabIndex = 0;
this.label1.Text = "Ticker values:";
//
// lvTickers
//
this.lvTickers.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.lvTickers.AutoArrange = false;
this.lvTickers.BackColor = System.Drawing.Color.Cornsilk;
this.lvTickers.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.columnHeader1,
this.columnHeader2,
this.columnHeader3,
this.columnHeader7,
this.columnHeader8,
this.columnHeader9});
this.lvTickers.FullRowSelect = true;
this.lvTickers.GridLines = true;
this.lvTickers.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
this.lvTickers.HideSelection = false;
this.lvTickers.Location = new System.Drawing.Point(15, 25);
this.lvTickers.MultiSelect = false;
this.lvTickers.Name = "lvTickers";
this.lvTickers.Size = new System.Drawing.Size(470, 97);
this.lvTickers.SmallImageList = this.ilIcons;
this.lvTickers.TabIndex = 1;
this.lvTickers.UseCompatibleStateImageBehavior = false;
this.lvTickers.View = System.Windows.Forms.View.Details;
this.lvTickers.SelectedIndexChanged += new System.EventHandler(this.lvTickers_SelectedIndexChanged);
//
// columnHeader1
//
this.columnHeader1.Text = "";
this.columnHeader1.Width = 20;
//
// columnHeader2
//
this.columnHeader2.Text = "Ticker";
this.columnHeader2.Width = 50;
//
// columnHeader3
//
this.columnHeader3.Text = "Company";
this.columnHeader3.Width = 140;
//
// columnHeader7
//
this.columnHeader7.Text = "Buy Trigger";
this.columnHeader7.Width = 70;
//
// columnHeader8
//
this.columnHeader8.Text = "Sell Trigger";
this.columnHeader8.Width = 70;
//
// columnHeader9
//
this.columnHeader9.Text = "Shares";
//
// ilIcons
//
this.ilIcons.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("ilIcons.ImageStream")));
this.ilIcons.TransparentColor = System.Drawing.Color.Magenta;
this.ilIcons.Images.SetKeyName(0, "Warning.bmp");
this.ilIcons.Images.SetKeyName(1, "Flag_green.bmp");
this.ilIcons.Images.SetKeyName(2, "Flag_red.bmp");
//
// cmdAdd
//
this.cmdAdd.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.cmdAdd.Location = new System.Drawing.Point(410, 128);
this.cmdAdd.Name = "cmdAdd";
this.cmdAdd.Size = new System.Drawing.Size(75, 23);
this.cmdAdd.TabIndex = 2;
this.cmdAdd.Text = "Add";
this.cmdAdd.UseVisualStyleBackColor = true;
this.cmdAdd.Click += new System.EventHandler(this.cmdAdd_Click);
//
// cmdRemove
//
this.cmdRemove.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.cmdRemove.Enabled = false;
this.cmdRemove.Location = new System.Drawing.Point(329, 128);
this.cmdRemove.Name = "cmdRemove";
this.cmdRemove.Size = new System.Drawing.Size(75, 23);
this.cmdRemove.TabIndex = 3;
this.cmdRemove.Text = "Remove";
this.cmdRemove.UseVisualStyleBackColor = true;
this.cmdRemove.Click += new System.EventHandler(this.cmdRemove_Click);
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(12, 152);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(43, 13);
this.label2.TabIndex = 4;
this.label2.Text = "Market:";
//
// lvMarket
//
this.lvMarket.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.lvMarket.AutoArrange = false;
this.lvMarket.BackColor = System.Drawing.Color.AliceBlue;
this.lvMarket.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.columnHeader4,
this.columnHeader5,
this.columnHeader6,
this.columnHeader10,
this.columnHeader11});
this.lvMarket.FullRowSelect = true;
this.lvMarket.GridLines = true;
this.lvMarket.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
this.lvMarket.HideSelection = false;
this.lvMarket.Location = new System.Drawing.Point(12, 168);
this.lvMarket.MultiSelect = false;
this.lvMarket.Name = "lvMarket";
this.lvMarket.Size = new System.Drawing.Size(473, 97);
this.lvMarket.SmallImageList = this.ilIcons;
this.lvMarket.TabIndex = 5;
this.lvMarket.UseCompatibleStateImageBehavior = false;
this.lvMarket.View = System.Windows.Forms.View.Details;
this.lvMarket.SelectedIndexChanged += new System.EventHandler(this.MarketSelectionChanged);
//
// columnHeader4
//
this.columnHeader4.Text = "";
this.columnHeader4.Width = 20;
//
// columnHeader5
//
this.columnHeader5.Text = "Ticker";
this.columnHeader5.Width = 100;
//
// columnHeader6
//
this.columnHeader6.Text = "Current Price";
this.columnHeader6.Width = 100;
//
// columnHeader10
//
this.columnHeader10.Text = "Trend";
this.columnHeader10.Width = 100;
//
// columnHeader11
//
this.columnHeader11.Text = "Total Value";
this.columnHeader11.Width = 100;
//
// cmdBuy
//
this.cmdBuy.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.cmdBuy.Enabled = false;
this.cmdBuy.Location = new System.Drawing.Point(410, 271);
this.cmdBuy.Name = "cmdBuy";
this.cmdBuy.Size = new System.Drawing.Size(75, 23);
this.cmdBuy.TabIndex = 6;
this.cmdBuy.Text = "Buy!";
this.cmdBuy.UseVisualStyleBackColor = true;
this.cmdBuy.Click += new System.EventHandler(this.cmdBuy_Click);
//
// cmdSell
//
this.cmdSell.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.cmdSell.Enabled = false;
this.cmdSell.Location = new System.Drawing.Point(329, 271);
this.cmdSell.Name = "cmdSell";
this.cmdSell.Size = new System.Drawing.Size(75, 23);
this.cmdSell.TabIndex = 7;
this.cmdSell.Text = "Sell!";
this.cmdSell.UseVisualStyleBackColor = true;
this.cmdSell.Click += new System.EventHandler(this.cmdSell_Click);
//
// label3
//
this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.label3.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
this.label3.Location = new System.Drawing.Point(12, 297);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(473, 2);
this.label3.TabIndex = 8;
//
// cmdQuit
//
this.cmdQuit.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.cmdQuit.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.cmdQuit.Location = new System.Drawing.Point(410, 310);
this.cmdQuit.Name = "cmdQuit";
this.cmdQuit.Size = new System.Drawing.Size(75, 23);
this.cmdQuit.TabIndex = 9;
this.cmdQuit.Text = "Quit";
this.cmdQuit.UseVisualStyleBackColor = true;
this.cmdQuit.Click += new System.EventHandler(this.cmdQuit_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.cmdQuit;
this.ClientSize = new System.Drawing.Size(497, 345);
this.Controls.Add(this.cmdQuit);
this.Controls.Add(this.label3);
this.Controls.Add(this.cmdSell);
this.Controls.Add(this.cmdBuy);
this.Controls.Add(this.lvMarket);
this.Controls.Add(this.label2);
this.Controls.Add(this.cmdRemove);
this.Controls.Add(this.cmdAdd);
this.Controls.Add(this.lvTickers);
this.Controls.Add(this.label1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "Form1";
this.Text = "eBroker";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.ListView lvTickers;
private System.Windows.Forms.ColumnHeader columnHeader1;
private System.Windows.Forms.ColumnHeader columnHeader2;
private System.Windows.Forms.ColumnHeader columnHeader3;
private System.Windows.Forms.Button cmdAdd;
private System.Windows.Forms.Button cmdRemove;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.ListView lvMarket;
private System.Windows.Forms.ColumnHeader columnHeader4;
private System.Windows.Forms.ColumnHeader columnHeader5;
private System.Windows.Forms.ColumnHeader columnHeader6;
private System.Windows.Forms.Button cmdBuy;
private System.Windows.Forms.Button cmdSell;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Button cmdQuit;
private System.Windows.Forms.ColumnHeader columnHeader7;
private System.Windows.Forms.ColumnHeader columnHeader8;
private System.Windows.Forms.ImageList ilIcons;
private System.Windows.Forms.ColumnHeader columnHeader9;
private System.Windows.Forms.ColumnHeader columnHeader10;
private System.Windows.Forms.ColumnHeader columnHeader11;
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace eBroker
{
public partial class FormAddTicker : Form
{
public FormAddTicker()
{
InitializeComponent();
}
protected ListViewItem _lvi = null;
public ListViewItem Item
{
get { return _lvi; }
}
private void cmdOK_Click(object sender, EventArgs e)
{
if (ValidateChildren())
{
// Pull values
string[] vals = new string[6];
vals[0] = String.Empty;
vals[1] = tbTicker.Text;
vals[2] = tbCompany.Text;
vals[3] = decimal.Parse(tbBuy.Text).ToString("C");
vals[4] = decimal.Parse(tbSell.Text).ToString("C");
vals[5] = tbShares.Text;
_lvi = new ListViewItem(vals);
// Close
DialogResult = DialogResult.OK;
} // if
else
{
// Display message
MessageBox.Show("You must provide values for all fields.", "Add Ticker", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
// Find the first offending textbox and assign
// the focus there...
if (tbTicker.Text.Length == 0) tbTicker.Focus();
else if (tbCompany.Text.Length == 0) tbCompany.Focus();
else if (tbBuy.Text.Length == 0) tbBuy.Focus();
else tbSell.Focus();
}
}
private void cmdCancel_Click(object sender, EventArgs e)
{
// Close
DialogResult = DialogResult.Cancel;
}
private void HandleKeyPress(object sender, KeyPressEventArgs e)
{
// Crude auto-formatter
e.Handled = true;
if (Char.IsDigit((char)e.KeyChar) || e.KeyChar == '.')
{
// Allow digits and one and only one decimal point. Allow
// only two characters to right of the decimal point.
TextBox tb = sender as TextBox;
Int32 idx = tb.Text.IndexOf('.');
if (idx != -1)
{
// Can't have two decimal points...
if (e.KeyChar != '.')
{
// We have a decimal point...
if (tb.Text.Length <= idx + 2)
{
// We can accept this one...
e.Handled = false;
} // if
} // if
} // if
else
{
// The shares textbox can't accept a decimal...
if (!(sender == tbShares && e.KeyChar == '.'))
{
// We can accept this one too...
e.Handled = false;
} // if
} // else
} // if
else if (Char.IsControl((char)e.KeyChar))
{
// All all control charcters to pass through...
e.Handled = false;
} // else if
}
private void ValidationHandler(object sender, CancelEventArgs e)
{
// Check text length for each textbox...none should be
// empty...
if (tbTicker.Text.Length == 0 || tbCompany.Text.Length == 0 ||
tbBuy.Text.Length == 0 || tbSell.Text.Length == 0)
{
e.Cancel = true;
} // if
}
}
}
namespace eBroker
{
partial class FormAddTicker
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.tbTicker = new System.Windows.Forms.TextBox();
this.label2 = new System.Windows.Forms.Label();
this.tbCompany = new System.Windows.Forms.TextBox();
this.label3 = new System.Windows.Forms.Label();
this.tbBuy = new System.Windows.Forms.TextBox();
this.label4 = new System.Windows.Forms.Label();
this.tbSell = new System.Windows.Forms.TextBox();
this.cmdOK = new System.Windows.Forms.Button();
this.cmdCancel = new System.Windows.Forms.Button();
this.tbShares = new System.Windows.Forms.TextBox();
this.label5 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(12, 15);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(69, 13);
this.label1.TabIndex = 0;
this.label1.Text = "Ticker value:";
//
// tbTicker
//
this.tbTicker.CausesValidation = false;
this.tbTicker.CharacterCasing = System.Windows.Forms.CharacterCasing.Upper;
this.tbTicker.Location = new System.Drawing.Point(96, 12);
this.tbTicker.MaxLength = 4;
this.tbTicker.Name = "tbTicker";
this.tbTicker.Size = new System.Drawing.Size(100, 20);
this.tbTicker.TabIndex = 1;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(12, 41);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(54, 13);
this.label2.TabIndex = 2;
this.label2.Text = "Company:";
//
// tbCompany
//
this.tbCompany.CausesValidation = false;
this.tbCompany.Location = new System.Drawing.Point(96, 38);
this.tbCompany.MaxLength = 64;
this.tbCompany.Name = "tbCompany";
this.tbCompany.Size = new System.Drawing.Size(100, 20);
this.tbCompany.TabIndex = 3;
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(12, 67);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(57, 13);
this.label3.TabIndex = 4;
this.label3.Text = "Buy value:";
//
// tbBuy
//
this.tbBuy.CausesValidation = false;
this.tbBuy.Location = new System.Drawing.Point(96, 64);
this.tbBuy.MaxLength = 7;
this.tbBuy.Name = "tbBuy";
this.tbBuy.Size = new System.Drawing.Size(100, 20);
this.tbBuy.TabIndex = 5;
this.tbBuy.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.HandleKeyPress);
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(12, 93);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(56, 13);
this.label4.TabIndex = 6;
this.label4.Text = "Sell value:";
//
// tbSell
//
this.tbSell.CausesValidation = false;
this.tbSell.Location = new System.Drawing.Point(96, 90);
this.tbSell.MaxLength = 7;
this.tbSell.Name = "tbSell";
this.tbSell.Size = new System.Drawing.Size(100, 20);
this.tbSell.TabIndex = 7;
this.tbSell.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.HandleKeyPress);
//
// cmdOK
//
this.cmdOK.Location = new System.Drawing.Point(108, 142);
this.cmdOK.Name = "cmdOK";
this.cmdOK.Size = new System.Drawing.Size(75, 23);
this.cmdOK.TabIndex = 10;
this.cmdOK.Text = "OK";
this.cmdOK.UseVisualStyleBackColor = true;
this.cmdOK.Click += new System.EventHandler(this.cmdOK_Click);
this.cmdOK.Validating += new System.ComponentModel.CancelEventHandler(this.ValidationHandler);
//
// cmdCancel
//
this.cmdCancel.CausesValidation = false;
this.cmdCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.cmdCancel.Location = new System.Drawing.Point(27, 142);
this.cmdCancel.Name = "cmdCancel";
this.cmdCancel.Size = new System.Drawing.Size(75, 23);
this.cmdCancel.TabIndex = 11;
this.cmdCancel.Text = "Cancel";
this.cmdCancel.UseVisualStyleBackColor = true;
this.cmdCancel.Click += new System.EventHandler(this.cmdCancel_Click);
//
// tbShares
//
this.tbShares.CausesValidation = false;
this.tbShares.Location = new System.Drawing.Point(96, 116);
this.tbShares.MaxLength = 7;
this.tbShares.Name = "tbShares";
this.tbShares.Size = new System.Drawing.Size(100, 20);
this.tbShares.TabIndex = 9;
this.tbShares.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.HandleKeyPress);
//
// label5
//
this.label5.AutoSize = true;
this.label5.Location = new System.Drawing.Point(12, 119);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(81, 13);
this.label5.TabIndex = 8;
this.label5.Text = "Number shares:";
//
// FormAddTicker
//
this.AcceptButton = this.cmdOK;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.cmdCancel;
this.CausesValidation = false;
this.ClientSize = new System.Drawing.Size(211, 176);
this.Controls.Add(this.tbShares);
this.Controls.Add(this.label5);
this.Controls.Add(this.cmdCancel);
this.Controls.Add(this.cmdOK);
this.Controls.Add(this.tbSell);
this.Controls.Add(this.label4);
this.Controls.Add(this.tbBuy);
this.Controls.Add(this.label3);
this.Controls.Add(this.tbCompany);
this.Controls.Add(this.label2);
this.Controls.Add(this.tbTicker);
this.Controls.Add(this.label1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FormAddTicker";
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Add Ticker";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox tbTicker;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.TextBox tbCompany;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.TextBox tbBuy;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.TextBox tbSell;
private System.Windows.Forms.Button cmdOK;
private System.Windows.Forms.Button cmdCancel;
private System.Windows.Forms.TextBox tbShares;
private System.Windows.Forms.Label label5;
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace eBroker
{
public partial class FormNumShares : Form
{
public FormNumShares()
{
InitializeComponent();
}
protected Int32 _max = -1;
public Int32 MaxShares
{
get { return _max; }
set { _max = value; }
}
protected Int32 _num = 0;
public Int32 NumberShares
{
get { return _num; }
set
{
// Check value
if (value < 0) throw new ArgumentOutOfRangeException("The number of shares must be greater than or equal to zero.");
// Save value
_num = value;
}
}
private void FormNumShares_Load(object sender, EventArgs e)
{
// We have to have a valid maximum number of shares to
// consider...error if it wasn't set...
if (MaxShares < 0) throw new InvalidOperationException("You must specify a maximum number of shares for this transaction.");
// Copy over the shares
tbShares.Text = NumberShares.ToString();
}
private void cmdOK_Click(object sender, EventArgs e)
{
if (ValidateChildren())
{
// Pull value (we know it'll convert since we filtered
// the input keystrokes).
NumberShares = Int32.Parse(tbShares.Text);
// If they're asking for too many, return max
if (NumberShares > NumberShares) NumberShares = NumberShares;
// Close
DialogResult = DialogResult.OK;
} // if
else
{
// Display message
MessageBox.Show("You must the number of shares for this transaction.", "Shares", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
// Set focus...
tbShares.Focus();
} // else
}
private void cmdCancel_Click(object sender, EventArgs e)
{
// Close
DialogResult = DialogResult.Cancel;
}
private void HandleKeyPress(object sender, KeyPressEventArgs e)
{
// Crude auto-formatter
e.Handled = true;
if (Char.IsDigit((char)e.KeyChar) || Char.IsControl((char)e.KeyChar))
{
// Allow...
e.Handled = false;
} // if
}
private void ValidationHandler(object sender, CancelEventArgs e)
{
// Check text length for each textbox...none should be
// empty...
if (tbShares.Text.Length == 0)
{
e.Cancel = true;
} // if
}
}
}
namespace eBroker
{
partial class FormNumShares
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.tbShares = new System.Windows.Forms.TextBox();
this.cmdOK = new System.Windows.Forms.Button();
this.cmdCancel = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(13, 13);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(93, 13);
this.label1.TabIndex = 0;
this.label1.Text = "Number of shares:";
//
// tbShares
//
this.tbShares.Location = new System.Drawing.Point(112, 10);
this.tbShares.MaxLength = 7;
this.tbShares.Name = "tbShares";
this.tbShares.Size = new System.Drawing.Size(100, 20);
this.tbShares.TabIndex = 1;
this.tbShares.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.HandleKeyPress);
//
// cmdOK
//
this.cmdOK.Location = new System.Drawing.Point(115, 36);
this.cmdOK.Name = "cmdOK";
this.cmdOK.Size = new System.Drawing.Size(75, 23);
this.cmdOK.TabIndex = 2;
this.cmdOK.Text = "OK";
this.cmdOK.UseVisualStyleBackColor = true;
this.cmdOK.Click += new System.EventHandler(this.cmdOK_Click);
this.cmdOK.Validating += new System.ComponentModel.CancelEventHandler(this.ValidationHandler);
//
// cmdCancel
//
this.cmdCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.cmdCancel.Location = new System.Drawing.Point(34, 36);
this.cmdCancel.Name = "cmdCancel";
this.cmdCancel.Size = new System.Drawing.Size(75, 23);
this.cmdCancel.TabIndex = 3;
this.cmdCancel.Text = "Cancel";
this.cmdCancel.UseVisualStyleBackColor = true;
this.cmdCancel.Click += new System.EventHandler(this.cmdCancel_Click);
//
// FormNumShares
//
this.AcceptButton = this.cmdOK;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.cmdCancel;
this.ClientSize = new System.Drawing.Size(225, 72);
this.Controls.Add(this.cmdCancel);
this.Controls.Add(this.cmdOK);
this.Controls.Add(this.tbShares);
this.Controls.Add(this.label1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FormNumShares";
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Number of Shares?";
this.Load += new System.EventHandler(this.FormNumShares_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox tbShares;
private System.Windows.Forms.Button cmdOK;
private System.Windows.Forms.Button cmdCancel;
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Runtime;
namespace eBroker
{
public static class WorkflowFactory
{
// Singleton instance of the workflow runtime
private static WorkflowRuntime _workflowRuntime = null;
// Lock (sync) object
private static object _syncRoot = new object();
// Factory method
public static WorkflowRuntime GetWorkflowRuntime()
{
// Lock execution thread in case of multi-threaded
// (concurrent) access.
lock (_syncRoot)
{
// Check for startup condition
if (null == _workflowRuntime)
{
// Provide for shutdown
AppDomain.CurrentDomain.ProcessExit += new EventHandler(StopWorkflowRuntime);
AppDomain.CurrentDomain.DomainUnload += new EventHandler(StopWorkflowRuntime);
// Not started, so create instance
_workflowRuntime = new WorkflowRuntime();
// Start the runtime
_workflowRuntime.StartRuntime();
} // if
// Return singleton instance
return _workflowRuntime;
} // lock
}
// Shutdown method
static void StopWorkflowRuntime(object sender, EventArgs e)
{
if (_workflowRuntime != null)
{
if (_workflowRuntime.IsStarted)
{
try
{
// Stop the runtime
_workflowRuntime.StopRuntime();
}
catch (ObjectDisposedException)
{
// Already disposed of, so ignore...
} // catch
} // if
} // if
}
}
}
//------------------------------------------------------------------------------
// <auto-generated>
// 此程式碼由工具生成。
// 執行庫版本:2.0.50727.1433
//
// 對此檔案的更改可能會導致不正確的行為,並且如果
// 重新生成程式碼,這些更改將會丟失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace eBrokerFlow {
using System;
using System.ComponentModel;
using System.Workflow.Activities;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.ComponentModel.Compiler;
[ToolboxItemAttribute(typeof(ActivityToolboxItem))]
public partial class AddTicker : HandleExternalEventActivity {
public static DependencyProperty TickerXMLProperty = DependencyProperty.Register("TickerXML", typeof(string), typeof(AddTicker));
public AddTicker() {
base.InterfaceType = typeof(eBrokerService.IWFBroker);
base.EventName = "AddTicker";
}
[BrowsableAttribute(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public override System.Type InterfaceType {
get {
return base.InterfaceType;
}
set {
throw new InvalidOperationException("Cannot set InterfaceType on a derived HandleExternalEventActivity.");
}
}
[BrowsableAttribute(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public override string EventName {
get {
return base.EventName;
}
set {
throw new InvalidOperationException("Cannot set EventName on a derived HandleExternalEventActivity.");
}
}
[ValidationOptionAttribute(ValidationOption.Required)]
public string TickerXML {
get {
return ((string)(this.GetValue(AddTicker.TickerXMLProperty)));
}
set {
this.SetValue(AddTicker.TickerXMLProperty, value);
}
}
protected override void OnInvoked(System.EventArgs e) {
eBrokerService.TickerActionEventArgs castedE = ((eBrokerService.TickerActionEventArgs)(e));
this.TickerXML = ((string)(castedE.TickerXML));
}
}
[ToolboxItemAttribute(typeof(ActivityToolboxItem))]
public partial class RemoveTicker : HandleExternalEventActivity {
public static DependencyProperty TickerXMLProperty = DependencyProperty.Register("TickerXML", typeof(string), typeof(RemoveTicker));
public RemoveTicker() {
base.InterfaceType = typeof(eBrokerService.IWFBroker);
base.EventName = "RemoveTicker";
}
[BrowsableAttribute(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public override System.Type InterfaceType {
get {
return base.InterfaceType;
}
set {
throw new InvalidOperationException("Cannot set InterfaceType on a derived HandleExternalEventActivity.");
}
}
[BrowsableAttribute(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public override string EventName {
get {
return base.EventName;
}
set {
throw new InvalidOperationException("Cannot set EventName on a derived HandleExternalEventActivity.");
}
}
[ValidationOptionAttribute(ValidationOption.Required)]
public string TickerXML {
get {
return ((string)(this.GetValue(RemoveTicker.TickerXMLProperty)));
}
set {
this.SetValue(RemoveTicker.TickerXMLProperty, value);
}
}
protected override void OnInvoked(System.EventArgs e) {
eBrokerService.TickerActionEventArgs castedE = ((eBrokerService.TickerActionEventArgs)(e));
this.TickerXML = ((string)(castedE.TickerXML));
}
}
[ToolboxItemAttribute(typeof(ActivityToolboxItem))]
public partial class BuyStock : HandleExternalEventActivity {
public static DependencyProperty SymbolProperty = DependencyProperty.Register("Symbol", typeof(string), typeof(BuyStock));
public static DependencyProperty NumberOfSharesProperty = DependencyProperty.Register("NumberOfShares", typeof(int), typeof(BuyStock));
public BuyStock() {
base.InterfaceType = typeof(eBrokerService.IWFBroker);
base.EventName = "BuyStock";
}
[BrowsableAttribute(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public override System.Type InterfaceType {
get {
return base.InterfaceType;
}
set {
throw new InvalidOperationException("Cannot set InterfaceType on a derived HandleExternalEventActivity.");
}
}
[BrowsableAttribute(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public override string EventName {
get {
return base.EventName;
}
set {
throw new InvalidOperationException("Cannot set EventName on a derived HandleExternalEventActivity.");
}
}
[ValidationOptionAttribute(ValidationOption.Required)]
public string Symbol {
get {
return ((string)(this.GetValue(BuyStock.SymbolProperty)));
}
set {
this.SetValue(BuyStock.SymbolProperty, value);
}
}
[ValidationOptionAttribute(ValidationOption.Required)]
public int NumberOfShares {
get {
return ((int)(this.GetValue(BuyStock.NumberOfSharesProperty)));
}
set {
this.SetValue(BuyStock.NumberOfSharesProperty, value);
}
}
protected override void OnInvoked(System.EventArgs e) {
eBrokerService.SharesActionEventArgs castedE = ((eBrokerService.SharesActionEventArgs)(e));
this.Symbol = ((string)(castedE.Symbol));
this.NumberOfShares = ((int)(castedE.NumberOfShares));
}
}
[ToolboxItemAttribute(typeof(ActivityToolboxItem))]
public partial class SellStock : HandleExternalEventActivity {
public static DependencyProperty SymbolProperty = DependencyProperty.Register("Symbol", typeof(string), typeof(SellStock));
public static DependencyProperty NumberOfSharesProperty = DependencyProperty.Register("NumberOfShares", typeof(int), typeof(SellStock));
public SellStock() {
base.InterfaceType = typeof(eBrokerService.IWFBroker);
base.EventName = "SellStock";
}
[BrowsableAttribute(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public override System.Type InterfaceType {
get {
return base.InterfaceType;
}
set {
throw new InvalidOperationException("Cannot set InterfaceType on a derived HandleExternalEventActivity.");
}
}
[BrowsableAttribute(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public override string EventName {
get {
return base.EventName;
}
set {
throw new InvalidOperationException("Cannot set EventName on a derived HandleExternalEventActivity.");
}
}
[ValidationOptionAttribute(ValidationOption.Required)]
public string Symbol {
get {
return ((string)(this.GetValue(SellStock.SymbolProperty)));
}
set {
this.SetValue(SellStock.SymbolProperty, value);
}
}
[ValidationOptionAttribute(ValidationOption.Required)]
public int NumberOfShares {
get {
return ((int)(this.GetValue(SellStock.NumberOfSharesProperty)));
}
set {
this.SetValue(SellStock.NumberOfSharesProperty, value);
}
}
protected override void OnInvoked(System.EventArgs e) {
eBrokerService.SharesActionEventArgs castedE = ((eBrokerService.SharesActionEventArgs)(e));
this.Symbol = ((string)(castedE.Symbol));
this.NumberOfShares = ((int)(castedE.NumberOfShares));
}
}
[ToolboxItemAttribute(typeof(ActivityToolboxItem))]
public partial class Stop : HandleExternalEventActivity {
public Stop() {
base.InterfaceType = typeof(eBrokerService.IWFBroker);
base.EventName = "Stop";
}
[BrowsableAttribute(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public override System.Type InterfaceType {
get {
return base.InterfaceType;
}
set {
throw new InvalidOperationException("Cannot set InterfaceType on a derived HandleExternalEventActivity.");
}
}
[BrowsableAttribute(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public override string EventName {
get {
return base.EventName;
}
set {
throw new InvalidOperationException("Cannot set EventName on a derived HandleExternalEventActivity.");
}
}
}
}
//------------------------------------------------------------------------------
// <auto-generated>
// 此程式碼由工具生成。
// 執行庫版本:2.0.50727.1433
//
// 對此檔案的更改可能會導致不正確的行為,並且如果
// 重新生成程式碼,這些更改將會丟失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace eBrokerFlow {
using System;
using System.ComponentModel;
using System.Workflow.Activities;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.ComponentModel.Compiler;
[ToolboxItemAttribute(typeof(ActivityToolboxItem))]
public partial class MarketUpdate : CallExternalMethodActivity {
public static DependencyProperty xmlMarketValuesProperty = DependencyProperty.Register("xmlMarketValues", typeof(string), typeof(MarketUpdate));
public MarketUpdate() {
base.InterfaceType = typeof(eBrokerService.IWFBroker);
base.MethodName = "MarketUpdate";
}
[BrowsableAttribute(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public override System.Type InterfaceType {
get {
return base.InterfaceType;
}
set {
throw new InvalidOperationException("Cannot set InterfaceType on a derived CallExternalMethodActivity.");
}
}
[BrowsableAttribute(false)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden)]
public override string MethodName {
get {
return base.MethodName;
}
set {
throw new InvalidOperationException("Cannot set MethodName on a derived CallExternalMethodActivity.");
}
}
[ValidationOptionAttribute(ValidationOption.Required)]
public string xmlMarketValues {
get {
return ((string)(this.GetValue(MarketUpdate.xmlMarketValuesProperty)));
}
set {
this.SetValue(MarketUpdate.xmlMarketValuesProperty, value);
}
}
protected override void OnMethodInvoking(System.EventArgs e) {
this.ParameterBindings["xmlMarketValues"].Value = this.xmlMarketValues;
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace eBrokerFlow
{
internal sealed class TickerItem
{
private string _ticker = String.Empty;
private decimal _buy = 0m;
private decimal _sell = 0m;
private Int32 _shares = 0;
private decimal _last = 0m;
public TickerItem(string ticker, decimal buyTrigger, decimal sellTrigger, Int32 shares)
{
// Persist values
this._ticker = ticker;
this._buy = buyTrigger;
this._sell = sellTrigger;
this._shares = shares;
}
public TickerItem(TickersTicker ticker)
{
// Persist values
this._ticker = ticker.Value;
this._buy = ticker.Buy;
this._sell = ticker.Sell;
this._shares = ticker.Shares;
}
public string Ticker
{
get { return _ticker; }
}
public decimal BuyTrigger
{
get { return _buy; }
}
public decimal SellTrigger
{
get { return _sell; }
}
public Int32 NumberOfShares
{
get { return _shares; }
set { _shares = value; }
}
public decimal LastPrice
{
get { return _last; }
set { _last = value; }
}
}
}
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using System.Collections.Generic;
using System.Xml;
using System.Text;
using System.Threading;
using System.Xml.Serialization;
using System.IO;
namespace eBrokerFlow
{
public sealed partial class Workflow1 : SequentialWorkflowActivity
{
public Workflow1()
{
InitializeComponent();
}
private Dictionary<string, eBrokerService.Ticker> _items =
new Dictionary<string, eBrokerService.Ticker>();
private string _tickersXML = null;
public string TickersXML
{
get { return _tickersXML; }
set { _tickersXML = value; }
}
private TimeSpan _interval = TimeSpan.FromSeconds(7);
public TimeSpan PollInterval
{
get { return _interval; }
set { _interval = value; }
}
private void Initialize(object sender, EventArgs e)
{
// Establish the market update timeout
updateDelay.TimeoutDuration = PollInterval;
// Stuff the known ticker values into the dictionary
// for later recall when updating market conditions.
eBrokerService.Tickers tickers = null;
using (StringReader rdr = new StringReader(TickersXML))
{
XmlSerializer serializer =
new XmlSerializer(typeof(eBrokerService.Tickers));
tickers = (eBrokerService.Tickers)serializer.Deserialize(rdr);
}
foreach (eBrokerService.Ticker ticker in tickers.Items)
{
// Add the ticker to the dictionary
_items.Add(ticker.Symbol, ticker);
}
}
private void TestContinue(object sender, ConditionalEventArgs e)
{
// Continue forever...
e.Result = true;
}
private void updateMarketValues(object sender, EventArgs e)
{
// Iterate over each item in the dictionary and decide
// what it's current value should be. Normally we'd call
// some external service with each of our watch values,
// but for demo purposes we'll just use random values.
Random rand = new Random(DateTime.Now.Millisecond);
eBrokerService.UpdateCollection updates = new eBrokerService.UpdateCollection();
foreach (string key in _items.Keys)
{
// Locate the item
eBrokerService.Ticker item = _items[key];
// If we're starting out, we have no current value,
// so place the value at half the distance between the
// buy and sell triggers.
if (item.LastPrice <= 0.0m)
{
// Assign a price...
decimal delta = (item.SellTrigger - item.BuyTrigger) / 2.0m;
// The last price must be a positive value, so add
// the delta to the smaller value.
if (delta >= 0.0m)
{
// Add delta to buy trigger value
item.LastPrice = item.BuyTrigger + delta;
} // if
else
{
// Reverse it and add to the sell trigger
// value
item.LastPrice = item.SellTrigger + delta;
} // else
} // if
// Set up the simulation
decimal newPrice = item.LastPrice;
decimal onePercent = item.LastPrice * 0.1m;
Int32 multiplier = 0; // no change
// We'll now roll some dice. First roll: does the
// market value change? 0-79, no. 80-99, yes.
if (rand.Next(0, 99) >= 80)
{
// Yes, update the price. Next roll: will the
// value increase or decrease? 0-49, increase.
// 50-99, decrease
multiplier = 1;
if (rand.Next(0, 99) >= 50)
{
// Decrease the price.
multiplier = -1;
} // if
// Next roll, by how much? We'll calculate it
// as a percentage of the current share value.
// 0-74, .1% change. 75-89, .2% change. 90-97,
// .3% change. And 98-99, .4% change.
Int32 roll = rand.Next(0, 99);
if (roll < 75)
{
// 1% change
newPrice = item.LastPrice + (onePercent * multiplier * 0.1m);
} // if
else if (roll < 90)
{
// 2% change
newPrice = item.LastPrice + (onePercent * multiplier * 0.2m);
} // else if
else if (roll < 98)
{
// 3% change
newPrice = item.LastPrice + (onePercent * multiplier * 0.3m);
} // else if
else
{
// 4% change
newPrice = item.LastPrice + (onePercent * multiplier * 0.4m);
} // else if
} // if
else
{
// No change in price
newPrice = item.LastPrice;
} // else
// Now create the update for this ticker
eBrokerService.Update update = new eBrokerService.Update();
update.Symbol = item.Symbol;
update.LastPrice = item.LastPrice;
update.NewPrice = newPrice;
update.Trend = multiplier > 0 ? "Up" : (multiplier == 0 ? "Firm" : "Down");
update.Action = newPrice > item.SellTrigger ? "Sell" : (newPrice < item.BuyTrigger ? "Buy" : "Hold");
update.TotalValue = newPrice * item.NumberOfShares;
updates.Add(update);
// Update the data store
item.LastPrice = newPrice;
} // foreach
// Serialize the data
StringBuilder sb = new StringBuilder();
using (StringWriter wtr = new StringWriter(sb))
{
XmlSerializer serializer = new XmlSerializer(typeof(eBrokerService.UpdateCollection));
serializer.Serialize(wtr, updates);
} // using
// Ship the data back...
Updates = sb.ToString();
}
public static DependencyProperty UpdatesProperty = DependencyProperty.Register("Updates", typeof(System.String), typeof(eBrokerFlow.Workflow1));
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
[BrowsableAttribute(true)]
[CategoryAttribute("雜項")]
public String Updates
{
get
{
return ((string)(base.GetValue(eBrokerFlow.Workflow1.UpdatesProperty)));
}
set
{
base.SetValue(eBrokerFlow.Workflow1.UpdatesProperty, value);
}
}
public Int32 _sharesToSell = default(System.Int32);
public String _tickerToSell = default(System.String);
private void SellStock(object sender, EventArgs e)
{
// Reduce the number of shares for the given ticker.
try
{
// Find this ticker.
eBrokerService.Ticker item = _items[_tickerToSell];
if (item != null)
{
// Reduce the number of shares.
item.NumberOfShares = item.NumberOfShares - _sharesToSell >= 0 ?
item.NumberOfShares - _sharesToSell : 0;
}
}
catch
{
// Do nothing...we just won't have sold any.
}
}
public Int32 _sharesToBuy = default(System.Int32);
public String _tickerToBuy = default(System.String);
private void BuyStock(object sender, EventArgs e)
{
// Increase the number of shares for the given ticker.
try
{
// Find this ticker.
eBrokerService.Ticker item = _items[_tickerToBuy];
if (item != null)
{
// Increase the number of shares.
item.NumberOfShares += _sharesToBuy;
}
}
catch
{
// Do nothing...we just won't have purchased any.
}
}
public String _tickerToRemove = default(System.String);
private void RemoveTicker(object sender, EventArgs e)
{
// Remove the given ticker from the watch.
try
{
// Deserialize
eBrokerService.Ticker ticker = null;
using (StringReader rdr = new StringReader(_tickerToRemove))
{
XmlSerializer serializer = new XmlSerializer(typeof(eBrokerService.Ticker));
ticker = (eBrokerService.Ticker)serializer.Deserialize(rdr);
}
// Find this ticker.
if (_items.ContainsKey(ticker.Symbol))
{
// Remove it.
_items.Remove(ticker.Symbol);
}
}
catch
{
// Do nothing...we just won't have removed it.
}
}
public String _tickerToAdd = default(System.String);
private void AddTicker(object sender, EventArgs e)
{
try
{
// Deserialize
eBrokerService.Ticker ticker = null;
using (StringReader rdr = new StringReader(_tickerToAdd))
{
XmlSerializer serializer = new XmlSerializer(typeof(eBrokerService.Ticker));
ticker = (eBrokerService.Ticker)serializer.Deserialize(rdr);
}
// Add the item if not already existing.
if (!_items.ContainsKey(ticker.Symbol))
{
// Add it.
_items.Add(ticker.Symbol, ticker);
}
}
catch
{
// Do nothing...we just won't have added it.
}
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using System.Data;
namespace eBrokerService
{
public sealed class BrokerDataConnector : IWFBroker
{
private string _dataValue = null;
private static WorkflowBrokerDataService _service = null;
private static object _syncLock = new object();
public static WorkflowBrokerDataService BrokerDataService
{
get { return _service; }
set
{
if (value != null)
{
lock (_syncLock)
{
// Re-verify the service isn't null
// now that we're locked...
if (value != null)
{
_service = value;
} // if
else
{
throw new InvalidOperationException("You must provide a service instance.");
} // else
} // lock
} // if
else
{
throw new InvalidOperationException("You must provide a service instance.");
} // else
}
}
public string MarketData
{
get { return _dataValue; }
}
// Workflow to host communication method
public void MarketUpdate(string xmlMarketValues)
{
// Assign the field for later recall
_dataValue = xmlMarketValues;
// Raise the event to trigger host read
_service.RaiseMarketUpdatedEvent();
}
// Host to workflow events
public event EventHandler<TickerActionEventArgs> AddTicker;
public event EventHandler<TickerActionEventArgs> RemoveTicker;
public event EventHandler<SharesActionEventArgs> BuyStock;
public event EventHandler<SharesActionEventArgs> SellStock;
public event EventHandler<StopActionEventArgs> Stop;
public void RaiseAddTicker(Guid instanceID, string tickerXML)
{
if (AddTicker != null)
{
// Fire event
AddTicker(null, new TickerActionEventArgs(instanceID, tickerXML));
} // if
}
public void RaiseRemoveTicker(Guid instanceID, string tickerXML)
{
if (RemoveTicker != null)
{
// Fire event
RemoveTicker(null, new TickerActionEventArgs(instanceID, tickerXML));
} // if
}
public void RaiseBuyStock(Guid instanceID, string symbol, Int32 numShares)
{
if (BuyStock != null)
{
// Fire event
BuyStock(null, new SharesActionEventArgs(instanceID, symbol, numShares));
} // if
}
public void RaiseSellStock(Guid instanceID, string symbol, Int32 numShares)
{
if (SellStock != null)
{
// Fire event
SellStock(null, new SharesActionEventArgs(instanceID, symbol, numShares));
} // if
}
public void RaiseStop(Guid instanceID)
{
if (Stop != null)
{
// Fire event
Stop(null, new StopActionEventArgs(instanceID));
} // if
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.ComponentModel;
using System.Workflow.Activities;
namespace eBrokerService
{
[ExternalDataExchange]
public interface IWFBroker
{
void MarketUpdate(string xmlMarketValues);
event EventHandler<TickerActionEventArgs> AddTicker;
event EventHandler<TickerActionEventArgs> RemoveTicker;
event EventHandler<SharesActionEventArgs> BuyStock;
event EventHandler<SharesActionEventArgs> SellStock;
event EventHandler<StopActionEventArgs> Stop;
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.ComponentModel;
using System.Workflow.Activities;
namespace eBrokerService
{
[Serializable]
public class MarketUpdateEventArgs : ExternalDataEventArgs
{
public MarketUpdateEventArgs(Guid instanceId)
: base(instanceId)
{
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.ComponentModel;
using System.Workflow.Activities;
namespace eBrokerService
{
[Serializable]
public class SharesActionEventArgs : ExternalDataEventArgs
{
private string _symbol = String.Empty;
private Int32 _shares = 0;
public SharesActionEventArgs(Guid instanceId, string symbol, Int32 shares)
: base(instanceId)
{
// Can't have negative shares!
if (shares < 0) throw new ArgumentOutOfRangeException("shares", "The number of shares to be sold must be equal to or greater than zero.");
// Persist values
this._symbol = symbol;
this._shares = shares;
}
public string Symbol
{
get { return _symbol; }
}
public Int32 NumberOfShares
{
get { return _shares; }
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.ComponentModel;
using System.Workflow.Activities;
namespace eBrokerService
{
[Serializable]
public class StopActionEventArgs : ExternalDataEventArgs
{
public StopActionEventArgs(Guid instanceId)
: base(instanceId)
{
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.ComponentModel;
using System.Workflow.Activities;
namespace eBrokerService
{
[Serializable]
public class TickerActionEventArgs : ExternalDataEventArgs
{
private string _tickerXML = String.Empty;
public TickerActionEventArgs(Guid instanceId, string tickerXML)
: base(instanceId)
{
// Persist value
this._tickerXML = tickerXML;
}
public String TickerXML
{
get { return _tickerXML; }
}
}
}
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:2.0.50727.42
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System.Xml.Serialization;
//
// This source code was auto-generated by xsd, Version=2.0.50727.42.
//
namespace eBrokerFlow
{
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Tickers
{
private TickersTicker[] itemsField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("Ticker", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public TickersTicker[] Items
{
get
{
return this.itemsField;
}
set
{
this.itemsField = value;
}
}
}
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.42")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class TickersTicker
{
private string valueField;
private string companyField;
private string buyField;
private string sellField;
private string sharesField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string Value
{
get
{
return this.valueField;
}
set
{
this.valueField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string Company
{
get
{
return this.companyField;
}
set
{
this.companyField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string Buy
{
get
{
return this.buyField;
}
set
{
this.buyField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string Sell
{
get
{
return this.sellField;
}
set
{
this.sellField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string Shares
{
get
{
return this.sharesField;
}
set
{
this.sharesField = value;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Serialization;
namespace eBrokerService
{
[Serializable]
[XmlTypeAttribute(AnonymousType = true)]
[XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Tickers
{
private Ticker[] _tickers = new Ticker[0];
[XmlElementAttribute("Ticker", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public Ticker[] Items
{
get { return _tickers; }
set { _tickers = value; }
}
}
[Serializable]
[XmlTypeAttribute(AnonymousType = true)]
public partial class Ticker
{
private string _company = String.Empty;
private string _symbol = String.Empty;
private decimal _buy = 0m;
private decimal _sell = 0m;
private Int32 _shares = 0;
private decimal _last = 0m;
public Ticker()
{
}
public Ticker(string company, string symbol, decimal buyTrigger, decimal sellTrigger, Int32 shares)
{
// Persist values
this._company = company;
this._symbol = symbol;
this._buy = buyTrigger;
this._sell = sellTrigger;
this._shares = shares;
}
[XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string Company
{
get { return _company; }
set { _company = value; }
}
[XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string Symbol
{
get { return _symbol; }
set { _symbol = value; }
}
[XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public decimal BuyTrigger
{
get { return _buy; }
set { _buy = value; }
}
[XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public decimal SellTrigger
{
get { return _sell; }
set { _sell = value; }
}
[XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public Int32 NumberOfShares
{
get { return _shares; }
set { _shares = value; }
}
[XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public decimal LastPrice
{
get { return _last; }
set { _last = value; }
}
}
}
using System;
using System.Collections.Generic;
namespace eBrokerService
{
[Serializable]
public class UpdateCollection : List<Update>
{
}
[Serializable]
public partial class Update
{
private string _symbol = String.Empty;
private decimal _lastPrice = 0m;
private decimal _newPrice = 0m;
private string _trend = String.Empty;
private string _action = String.Empty;
private decimal _totalValue = 0m;
public string Symbol
{
get { return _symbol; }
set { _symbol = value; }
}
public decimal LastPrice
{
get { return _lastPrice; }
set { _lastPrice = value; }
}
public decimal NewPrice
{
get { return _newPrice; }
set { _newPrice = value; }
}
public string Trend
{
get { return _trend; }
set { _trend = value; }
}
public string Action
{
get { return _action; }
set { _action = value; }
}
public decimal TotalValue
{
get { return _totalValue; }
set { _totalValue = value; }
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using System.Data;
namespace eBrokerService
{
[Serializable]
public class WorkflowBrokerDataService
{
protected static WorkflowRuntime _workflowRuntime = null;
protected static ExternalDataExchangeService _dataExchangeService = null;
protected static BrokerDataConnector _dataConnector = null;
protected static object _syncRoot = new object();
public event EventHandler<MarketUpdateEventArgs> MarketUpdated;
private Guid _instanceID = Guid.Empty;
public Guid InstanceID
{
get { return _instanceID; }
set { _instanceID = value; }
}
public static WorkflowBrokerDataService CreateDataService(Guid instanceID, WorkflowRuntime workflowRuntime)
{
lock (_syncRoot)
{
// If we're just starting, save a copy of the workflow runtime reference
if (_workflowRuntime == null)
{
_workflowRuntime = workflowRuntime;
} // if
// If we're just starting, plug in ExternalDataExchange service
if (_dataExchangeService == null)
{
_dataExchangeService = new ExternalDataExchangeService();
_workflowRuntime.AddService(_dataExchangeService);
} // if
// Check to see if we have already added this data exchange service
BrokerDataConnector dataConnector = (BrokerDataConnector)workflowRuntime.GetService(typeof(BrokerDataConnector));
if (dataConnector == null)
{
_dataConnector = new BrokerDataConnector();
_dataExchangeService.AddService(_dataConnector);
} // if
else
{
_dataConnector = dataConnector;
} // else
// Pull the service instance we registered with the connection object
WorkflowBrokerDataService workflowDataService = BrokerDataConnector.BrokerDataService;
if (workflowDataService == null)
{
workflowDataService = new WorkflowBrokerDataService(instanceID);
BrokerDataConnector.BrokerDataService = workflowDataService;
} // if
return workflowDataService;
} // lock
}
public static WorkflowBrokerDataService GetRegisteredWorkflowDataService()
{
lock (_syncRoot)
{
WorkflowBrokerDataService workflowDataService = BrokerDataConnector.BrokerDataService;
if (workflowDataService == null)
{
throw new Exception("Error configuring data service...service cannot be null.");
} // if
return workflowDataService;
} // lock
}
private WorkflowBrokerDataService(Guid instanceID)
{
_instanceID = instanceID;
BrokerDataConnector.BrokerDataService = this;
}
~WorkflowBrokerDataService()
{
// Clean up
_workflowRuntime = null;
_dataExchangeService = null;
_dataConnector = null;
}
public string Read()
{
return _dataConnector.MarketData;
}
public void RaiseMarketUpdatedEvent()
{
if (_workflowRuntime == null)
_workflowRuntime = new WorkflowRuntime();
_workflowRuntime.GetWorkflow(_instanceID); // loads persisted workflow instances
if (MarketUpdated != null)
{
MarketUpdated(this, new MarketUpdateEventArgs(_instanceID));
} // if
}
}
}