上文介紹了Excel中的UDF函數,本文介紹一下同樣重要的RTD函數。從Excel 2002開始,Excel引入了一種新的查看和更新實時數據的機制,即real-time data簡稱RTD函數,他是一種Push-Pull的方式,及在需要更新數據的時候,RTD給Excel Push一個消息說要更新數據,Excel在收到消息後主動拉取Pull新的數據。RTD函數最開始的用途在於更新實時變化的數據,比如股票實時行情數據,實時天氣預報數據,球隊比賽得分數據等等。
在過去,要實現這些功能,需要依賴一些其他諸如Dynamic Data Exchange(DDE)技術來訪問實時數據資源,但DDE和標准的Excel函數樣式有很大的不同,並且並不是為Excel獲取實時數據而設計的,缺乏健壯性,並且效率不高,RTD的引入解決了這些問題。
本文首先介紹RTD的一些常用的使用場景,RTD函數的基本結構,注意事項,最後演示如何通過RTD函數來實現從Google Financial API中獲取實時行情數據。
RTD函數很有用,如果您遇到以下情況,那麼您應當考慮使用RTD函數了:
Excel RTD函數是一個實現了IRtdServer接口的Com組件,Excel通過該Com組件與實時數據進行交互。要實現RTD 函數,必須要實現IRtdServer這一接口,該接口位於 Microsoft.Office.Interop.Excel命名空間中,跳轉到定義,可以看到該接口的內部:
[()] [(4160)] { [(11)] ConnectData(TopicID, Strings, GetNewValues); [(13)] DisconnectData(TopicID); [(14)] Heartbeat(); [(12)] RefreshData(TopicCount); [(10)] ServerStart(CallbackObject); [(15)] ServerTerminate(); }
其中ServerStart參數中有類型為IRTDUpdateEvent接口,該接口的實現為:
[()] [(4160)] { [(11)] HeartbeatInterval { ; ; } [(12)] Disconnect(); [(10)] UpdateNotify(); }
巨硬的接口注釋寫的很清楚,不過這裡還是逐條解釋一下,先看IRtdServer接口,這裡依照一般的執行順序講解:
上面的接口介紹完了,現在介紹下IRTDUpdateEvent接口,該接口比較重要的一個方法是UpdateNotify方法。該方法想Excel發出一個通知,提示有新數據需要更新,這是Excel就會調用RefreshData方法,從中讀取到更新後的數據。需要注意的是調用UpdateNofity方法必須要在Excel主線程中進行。
上面簡單講解了RTD函數的基本原理,接下來演示如何通過Excel RTD來實現從Google Fiancial API中獲取實時行情並刷新數據,Google Financal API,提供了世界各大交易所的實時行情數據,其支持的市場及時效性在其官網上有說明,其使用方法可以參考這篇文章,這裡不細談。其主要思路如下,首先定義好請求的參數,我們的實時行情請求參數有兩個,一個是股票代碼,一個是指標的名稱。因為我們請求的是http,而不是注冊事件回調的方式,所以需要在RTD中使用timer控件去主動拉去請求,然後請求處理完成之後,將結果存儲到對象中,然後調用UpdateNotify方法通知Excel來更新。RTD函數其實是一個注冊為Com組件的類庫,所以我們首先創建一個名為YYGoogleFinancialRTD的類庫:
3.2 創建RTD
如何創建RTD函數是本文的重點,這裡我們先新建一個名為FinancialRtd的類,然後讓他實現IRtdServer接口。利用VS的自動完成,實現其五個方法。和使用.NET 編寫UDF 一樣,我們需要在類名稱上加一些自定義屬性。這四個屬性中,與UDF相比多了一個ProgID屬性,該屬性唯一標識改RTD函數。
[()] [()] [(.AutoDual)] [()] : { xlRTDUpdate; tmrTimer; <> stockDatas; gfinancial = ; }
在實現五個方法之前,我們需要定義一些局部變量,第一個變量是xlRTDUpdate對象,該對象用來保存ServerStart中傳進來的對象的引用,以方便後面調用該對象的UpdateNotify方法。Timer控件用來定時的從http接口中獲取實時行情數據,List<RealStockData>對象用來保存所有的請求及其返回值。Gfinancial對象用來獲取從Google Financial API中獲取的實時行情。由於當前不再交易時間,所以我在收盤價格的基礎上加了一個Random來演示實時變化。這個在下載的代碼中您可以看到。這裡不多講。定義好這些變量之後我們就可以開始編寫代碼了。
第一個要實現的方法是 ServerStart方法。在該方法中,我們將CallbackObject對象保存到之前定義好的局部變量中,供日後調用該對象的UpdateNotify方法。然後,我們初始化了Timer對象,將其設置為2s去獲取一次實時行情數據。最後返回1表示RTD服務正常開啟。timer的Elapsed方法我們後面介紹。
ServerStart(CallbackObject) { xlRTDUpdate = CallbackObject; gfinancial = (); tmrTimer = (); tmrTimer.Interval = 2000; tmrTimer.Elapsed += tmrTimer_Elapsed; 1; }
第二個要實現的重要的方法是ConnectData方法:
ConnectData(TopicID, Strings, GetNewValues) { GetNewValues = ; strStockCode = Strings.GetValue(0).ToString().ToLower(); strIndex = Strings.GetValue(1).ToString(); { (stockDatas == ) { stockDatas = <>(); } temp = (); temp.StockCode = strStockCode; temp.Index = strIndex; gfinancial.GetRealStock(temp); (temp.Value != ) { stockDatas.Add(temp); } } { ; } (!tmrTimer.Enabled) { tmrTimer.Start(); } (i = 0; i < stockDatas.Count; i++) { (stockDatas[i].StockCode.Equals(strStockCode, .OrdinalIgnoreCase) && stockDatas[i].Index.ToString().Equals(strIndex, .OrdinalIgnoreCase)) { (stockDatas[i].TopicId == -1) { stockDatas[i].TopicId = TopicID; } stockDatas[i].Value; } } ; }
在該方法中,我們首先解析傳進來的參數,根據之前的約定,第一個參數為股票代碼,第二個參數為指標名稱。然後我們實例化了一個RealStockData對象,並給該對象的相關StockCode和Index賦值,然後去為該請求去請求一次實時行情,並將值存儲到對象的Value屬性中。然後將該請求加載到List集合對象中。最後循環判斷是否已經存在,如果已經存在,則直接返回值,否則將該次請求的Excel為其分配的TopicID存到該次對象的TopicId對象中。以便後面刷新時使用。最後如果輸入的參數條件不滿足要求,提示用戶請求格式不正確。每一個單元格的請求僅執行該方法一次。後面就通過刷新機制實現更新了。
為了連續性,現在介紹timer的Elapse方法,該方法的實現如下:
tmrTimer_Elapsed(sender, e) { gfinancial.GetRealStock(stockDatas); xlRTDUpdate.UpdateNotify(); }
該方法很簡單,第一句去講我們之前保存的List集合的所有請求那過去請求實時行情,並將其返回值,保存到各元素的Value中,然後調用UpdateNotify方法通知Excel新的數據值已經獲取到了,新數據的值就存在stockDatas的各元素的Value屬性中。這時Excel收到UpdateNotify方法之後,就回去調用RefreshData方法,該方法的實現如下:
RefreshData(TopicCount) { [,] rets = [2, stockDatas.Count]; counter = 0; (data stockDatas) { (data.TopicId != -1) { rets[0, counter] = data.TopicId; rets[1, counter] = data.Value; } counter++; } TopicCount = stockDatas.Count; rets; }
該方法很簡單,首先我們創建了一個二維數組,第二維的大小即為所有有效請求的個數,這裡幾位stockDatas元素的個數。
然後遍歷所有的請求,填充該二維數組,第一維值為TopicID,Excel會通過該ID去更新對應的單元格,第二維為單元格的值,即將該值填充到對應的TopicID中。另外我們還要通知Excel我們要請求刷新的單元格的個數,這個個數當然就是所有請求的個數啦。最後我們返回這個二位數組。
至此,最重要的幾個方法介紹完了。
和ConnectData方法對應的DisconnectData方法在我們在Excel中刪除我們之前輸入的RTD函數時觸發,方法實現如下,操作就是從我們的所有請求集合中移除該TopicID對應的請求。
DisconnectData(TopicID) { (i = stockDatas.Count - 1; i > 0; i--) { (stockDatas[i].TopicId == TopicID) { stockDatas.RemoveAt(i); } } ((stockDatas == || stockDatas.Count == 0) && tmrTimer.Enabled) { tmrTimer.Stop(); } }
Heartbeat方法僅僅返回1,表示我們的RTD還在運行中。
Heartbeat() { 1; }
最後和ServerStart方法對應的方法ServerTerminate在關閉該RTD工作表時觸發,該方法的主要用來釋放資源,其實現如下:
ServerTerminate() { xlRTDUpdate = ; (tmrTimer.Enabled) { tmrTimer.Stop(); } tmrTimer.Elapsed -= (tmrTimer_Elapsed); tmrTimer.Dispose(); }
至此,我們的函數編寫完了。
完成之後,和UDF函數一樣,我們需要將該類庫注冊為Com組件,在Visual Studio編譯時勾選注冊為Com組件即可,提醒您的是,在Windows 7 及以上系統上,注冊Com組件涉及到要往注冊表些東西,所以需要當前的Visual Studio以管理員權限運行。
本文介紹了Excel中一類非常重要的函數,及RTD函數,他是Excel提供的一種Push-Pull機制的數據更新機制,在一些對數據實時性要求較高的場合,比如直播比賽得分,實時天氣預報,交易所實時行情等方面用的很多。並且通過RTD的這種機制我們可以在此基礎上實現異步的UDF函數,即當請求發進來的時候,我們暫不處理,記下TopicID,然後放到處理隊列中,待處理完成後,調用UpdateNotify方法,然後刷新已經處理完的TopicID的單元格。下文將會介紹Excel中異步自定義函數,異步的UDF函數能夠極大地提高Excel插件的用戶體驗,能夠提高系統的可擴展性和穩定性。
本文所有的代碼點擊此處下載,希望本文對您了解Excel中的RTD函數有所幫助。