从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函数了:

  1. 实时数据更新:这是很常见遇到的一种情景,比如说实时行情,实时天气情况,直播的比赛得分情况等。
  2. 一些特殊的场景,比如说证券交易公司的交易系统,这些公司的某些模型会基于实时的行情或者指数进行计算,通过RTD获取这些基础数据的实时数据之后,在此基础上可以进行动态的建模和分析。
  3. 异步的,耗时的数据请求:传统的UDF函数存在的一个问题是,他是同步的,就是说,如果某个函数的执行时间过长,会导致前端界面的卡顿,尤其是在UDF函数访问数据库,WebService,或复杂的计算过程等不确定性及耗时的执行环境时。基于RTD技术可以实现异步的UDF函数,从而带来更好的用户体验。

二 基本结构

Excel RTD函数是一个实现了IRtdServer接口的Com组件,Excel通过该Com组件与实时数据进行交互。要实现RTD 函数,必须要实现IRtdServer这一接口,该接口位于 Microsoft.Office.Interop.Excel命名空间中,跳转到定义,可以看到该接口的内部:

/// <summary>
///  Represents an interface for a real-time data server.
/// </summary>
[Guid("EC0E6191-DB51-11D3-8F3E-00C04F3651B8")]
[TypeLibType(4160)]
public interface IRtdServer2
{/// <summary>/// Adds new topics from a real-time data server. The ConnectData method is called///     when a file is opened that contains real-time data functions or when a user///     types in a new formula which contains the RTD function./// </summary>/// <param name="TopicID">/// Required Integer. A unique value, assigned by Microsoft Excel, which identifies the topic.</param>/// <param name="Strings">Required Object. A single-dimensional array of strings identifying the topic.</param>/// <param name="GetNewValues"> Required Boolean. True to determine if new values are to be acquired.</param>/// <returns></returns>[DispId(11)]dynamic ConnectData(int TopicID, ref Array Strings, ref bool GetNewValues);/// <summary>/// Notifies a real-time data (RTD) server application that a topic is no longer in use./// </summary>/// <param name="TopicID"> Required Integer. A unique value assigned to the topic assigned by Microsoft Excel.</param>[DispId(13)]void DisconnectData(int TopicID);[DispId(14)]int Heartbeat();/// <summary>/// This method is called by Microsoft Excel to get new data./// </summary>/// <param name="TopicCount">TopicCount:///     Required Integer. The RTD server must change the value of the TopicCount///     to the number of elements in the array returned.</param>/// <returns></returns>[DispId(12)]Array RefreshData(ref int TopicCount);/// <summary>/// The ServerStart method is called immediately after a real-time data server///     is instantiated. Negative value or zero indicates failure to start the server;///     positive value indicates success./// </summary>/// <param name="CallbackObject">Required Microsoft.Office.Interop.Excel.IRTDUpdateEvent object. The callback object.</param>/// <returns></returns>[DispId(10)]int ServerStart(IRTDUpdateEvent CallbackObject);/// <summary>/// Terminates the connection to the real-time data server./// </summary>[DispId(15)]void ServerTerminate();
}

其中ServerStart参数中有类型为IRTDUpdateEvent接口,该接口的实现为:

/// <summary>
/// Represents real-time data update events.
/// </summary>
[Guid("A43788C1-D91B-11D3-8F39-00C04F3651B8")]
[TypeLibType(4160)]
public interface IRTDUpdateEvent
{[DispId(11)]int HeartbeatInterval { get; set; }/// <summary>///   Instructs the real-time data server (RTD) to disconnect from the specified///   Microsoft.Office.Interop.Excel.IRTDUpdateEvent object./// </summary>[DispId(12)]void Disconnect();[DispId(10)]void UpdateNotify();
}

巨硬的接口注释写的很清楚,不过这里还是逐条解释一下,先看IRtdServer接口,这里依照一般的执行顺序讲解:

  • ServerStart方法,该方法是在Excel启动后,第一个调用改RTD主题时执行的方法,该方法返回值为1表示启动正常,0或者负表示失败,后面的其他同主题的RTD函数不会被执行。方法唯一的一个参数为IRTDUpdateEvent类型,该对象用来向Excel通知我的新数据准备好了,请求刷新,然后Excel就会去调用RefreshData方法,一般滴,我们会在这个方法类进行一些初始化操作,最常见的就是创建一个IRTDUpdateEvent的局部变量,将该参数赋值给这个局部变量,以方便后面通知Excel刷新数据使用。
  • ConnectData方法,该方法是在当打开一个含有RTD函数的文档,或者当用户往单元格中输入一个RTD函数之后就会执行。该方法第一个参数为TopicID,改ID唯一标识了改单元格内的RTD函数,他是Excel自动分配的,在有些情况下,我们需要定义好自己的数据结构,记录下TopicID,然后在后面可以刷新指定的TopicID的单元格。第二个参数为Array类型的String对象,一般的利用该参数传递RTD中所需要的参数信息。 举个例子来说,如果我们要编写实时行情的RTD函数,那么参数诸如 股票代码,指标名称(开盘价,Open)等,这些以数组的形式传进来。第三个参数为Bool型的GetNewValues,它 用于确定是否总是获取最新数据,如果这个参数传入true,则每次保存Excel文档以后,再次重新打开时,看到的不一定是上次保存时的数据,而是最新的实时数据,这也是一个引用类型的参数。通常如果不设置的话,再次打开包含RTD的表格时,显示的是最后一次请求的数据。
  • Heartbeat 方法, Excel会调用此方法确定RTD Server服务是否可用,0和负数代表不可用,1代表可用,通常直接返回1。
  • RefreshData方法,参数TopicCount表示要更新的主题数量,用于返回给Excel。该方法返回值是一个二维数组,第一维是要更新数据的TopicID,也就是在ConnectData方法中的TopicID,第二维是该TopicID对应的获取到的值。当获取到新的数据的时候,调用ServerStart传进去的IRTDUpdateEvent类型对象的UpdateNotify方法通知Excel有新数据需要更新,这时Excel就会调用RefreshData方法,然后根据该方法返回包含TopicID的二维数据对工作簿中的数据进行更新。
  • DisconnectData方法,该方法与ConnectData对应,TopcID唯一标识这个函数在Excel中的一个应用。当我们从Excel中移除一个主题(删除所有采用相同参数的RTD函数)之后,DisconnectData将被调用,在这里,可以释放对这个主题的监控,并不再为其获取新数据。
  • 当Excel不再需要从RTD Server获取实时数据时被调用。在这里,可以执行一些清理,例如清除缓存,关闭时钟等等。至此,一个RTD Server的生命周期就结束了。

上面的接口介绍完了,现在介绍下IRTDUpdateEvent接口,该接口比较重要的一个方法是UpdateNotify方法。该方法想Excel发出一个通知,提示有新数据需要更新,这是Excel就会调用RefreshData方法,从中读取到更新后的数据。需要注意的是调用UpdateNofity方法必须要在Excel主线程中进行。

三 实例:RTD实时行情

上面简单讲解了RTD函数的基本原理,接下来演示如何通过Excel RTD来实现从Google Fiancial API中获取实时行情并刷新数据,Google Financal API,提供了世界各大交易所的实时行情数据,其支持的市场及时效性在其官网上有说明,其使用方法可以参考这篇文章,这里不细谈。其主要思路如下,首先定义好请求的参数,我们的实时行情请求参数有两个,一个是股票代码,一个是指标的名称。因为我们请求的是http,而不是注册事件回调的方式,所以需要在RTD中使用timer控件去主动拉去请求,然后请求处理完成之后,将结果存储到对象中,然后调用UpdateNotify方法通知Excel来更新。RTD函数其实是一个注册为Com组件的类库,所以我们首先创建一个名为YYGoogleFinancialRTD的类库:

3.1 创建实体类对象

要将我们的请求的结果保存起来,必须创建一个实体类,来保存TopicID,请求的指标,股票代码,以及返回值的信息,这样再刷新的时候可以根据这些信息来刷新。所以添加一个RealStockData类。

class RealStockData
{//该次请求的股票代码public string StockCode{get;set;}//该次请求的股票名称public string Index { get; set; }//该次请求Excel分配的TopicIDpublic int TopicId { get; set; }//该次请求的返回值public object Value{get;set;}
}

3.2 创建RTD

如何创建RTD函数是本文的重点,这里我们先新建一个名为FinancialRtd的类,然后让他实现IRtdServer接口。利用VS的自动完成,实现其五个方法。和使用.NET 编写UDF 一样,我们需要在类名称上加一些自定义属性。这四个属性中,与UDF相比多了一个ProgID属性,该属性唯一标识改RTD函数。

[Guid("0A5F2820-C34E-4EF5-9110-F66B2A7796C0")]
[ProgId("YYRTD.Stock")]
[ClassInterface(ClassInterfaceType.AutoDual)]
[ComVisible(true)]
public class FinancialRtd : IRtdServer
{private IRTDUpdateEvent xlRTDUpdate;private Timer tmrTimer;private List<RealStockData> stockDatas;private GoogleFinancial gfinancial = null;
}

在实现五个方法之前,我们需要定义一些局部变量,第一个变量是xlRTDUpdate对象,该对象用来保存ServerStart中传进来的对象的引用,以方便后面调用该对象的UpdateNotify方法。Timer控件用来定时的从http接口中获取实时行情数据,List<RealStockData>对象用来保存所有的请求及其返回值。Gfinancial对象用来获取从Google Financial API中获取的实时行情。由于当前不再交易时间,所以我在收盘价格的基础上加了一个Random来演示实时变化。这个在下载的代码中您可以看到。这里不多讲。定义好这些变量之后我们就可以开始编写代码了。

第一个要实现的方法是 ServerStart方法。在该方法中,我们将CallbackObject对象保存到之前定义好的局部变量中,供日后调用该对象的UpdateNotify方法。然后,我们初始化了Timer对象,将其设置为2s去获取一次实时行情数据。最后返回1表示RTD服务正常开启。timer的Elapsed方法我们后面介绍。

public int ServerStart(IRTDUpdateEvent CallbackObject)
{xlRTDUpdate = CallbackObject;gfinancial = new GoogleFinancial();tmrTimer = new Timer();tmrTimer.Interval = 2000;tmrTimer.Elapsed += tmrTimer_Elapsed;return 1;
}

第二个要实现的重要的方法是ConnectData方法:

public dynamic ConnectData(int TopicID, ref Array Strings, ref bool GetNewValues)
{//Retrive new values from RTD server when connected.GetNewValues = true;//Get field name from Excel.string strStockCode = Strings.GetValue(0).ToString().ToLower();string strIndex = Strings.GetValue(1).ToString();try{if (stockDatas == null){stockDatas = new List<RealStockData>();}//Get real-time data from data source.RealStockData temp = new RealStockData();temp.StockCode = strStockCode;temp.Index = strIndex;gfinancial.GetRealStock(temp);if (temp.Value != null){stockDatas.Add(temp);}}catch{return "ERROR IN QUOTE.";}//Make sure that the timer is started.if (!tmrTimer.Enabled){tmrTimer.Start();}for (int i = 0; i < stockDatas.Count; i++){//Match the topic valueif (stockDatas[i].StockCode.Equals(strStockCode, StringComparison.OrdinalIgnoreCase) && stockDatas[i].Index.ToString().Equals(strIndex, StringComparison.OrdinalIgnoreCase)){if (stockDatas[i].TopicId == -1){stockDatas[i].TopicId = TopicID;}return stockDatas[i].Value;}}return "Unrecognized requested";
}

在该方法中,我们首先解析传进来的参数,根据之前的约定,第一个参数为股票代码,第二个参数为指标名称。然后我们实例化了一个RealStockData对象,并给该对象的相关StockCode和Index赋值,然后去为该请求去请求一次实时行情,并将值存储到对象的Value属性中。然后将该请求加载到List集合对象中。最后循环判断是否已经存在,如果已经存在,则直接返回值,否则将该次请求的Excel为其分配的TopicID存到该次对象的TopicId对象中。以便后面刷新时使用。最后如果输入的参数条件不满足要求,提示用户请求格式不正确。每一个单元格的请求仅执行该方法一次。后面就通过刷新机制实现更新了。

为了连续性,现在介绍timer的Elapse方法,该方法的实现如下:

private void tmrTimer_Elapsed(object sender, ElapsedEventArgs e)
{gfinancial.GetRealStock(stockDatas);xlRTDUpdate.UpdateNotify();
}

该方法很简单,第一句去讲我们之前保存的List集合的所有请求那过去请求实时行情,并将其返回值,保存到各元素的Value中,然后调用UpdateNotify方法通知Excel新的数据值已经获取到了,新数据的值就存在stockDatas的各元素的Value属性中。这时Excel收到UpdateNotify方法之后,就回去调用RefreshData方法,该方法的实现如下:

public Array RefreshData(ref int TopicCount)
{object[,] rets = new object[2, stockDatas.Count];int counter = 0;foreach (RealStockData data in stockDatas){if (data.TopicId != -1){rets[0, counter] = data.TopicId;rets[1, counter] = data.Value;}counter++;}TopicCount = stockDatas.Count;return rets;
}

该方法很简单,首先我们创建了一个二维数组,第二维的大小即为所有有效请求的个数,这里几位stockDatas元素的个数。

然后遍历所有的请求,填充该二维数组,第一维值为TopicID,Excel会通过该ID去更新对应的单元格,第二维为单元格的值,即将该值填充到对应的TopicID中。另外我们还要通知Excel我们要请求刷新的单元格的个数,这个个数当然就是所有请求的个数啦。最后我们返回这个二位数组。

至此,最重要的几个方法介绍完了。

和ConnectData方法对应的DisconnectData方法在我们在Excel中删除我们之前输入的RTD函数时触发,方法实现如下,操作就是从我们的所有请求集合中移除该TopicID对应的请求。

public void DisconnectData(int TopicID)
{for (int i = stockDatas.Count - 1; i > 0; i--){if (stockDatas[i].TopicId == TopicID){stockDatas.RemoveAt(i);}}if ((stockDatas == null || stockDatas.Count == 0) && tmrTimer.Enabled){tmrTimer.Stop();}
}

Heartbeat方法仅仅返回1,表示我们的RTD还在运行中。

public int Heartbeat()
{return 1;
}

最后和ServerStart方法对应的方法ServerTerminate在关闭该RTD工作表时触发,该方法的主要用来释放资源,其实现如下:

public void ServerTerminate()
{//Clear the RTDUpdateEvent reference.xlRTDUpdate = null;//Make sure the timer is stopped.if (tmrTimer.Enabled){tmrTimer.Stop();}tmrTimer.Elapsed -= new ElapsedEventHandler(tmrTimer_Elapsed);tmrTimer.Dispose();
}

至此,我们的函数编写完了。

完成之后,和UDF函数一样,我们需要将该类库注册为Com组件,在Visual Studio编译时勾选注册为Com组件即可,提醒您的是,在Windows 7 及以上系统上,注册Com组件涉及到要往注册表些东西,所以需要当前的Visual Studio以管理员权限运行。

编译通过之后,我们可以直接打开Excel输入RTD函数了。

在Excel 中使用RTD非常简单,Excel 提供了一个新的工作表函数 RTD,此函数允许通过调用组件对象模型 (COM) 自动化服务器来实现实时数据检索。RTD 工作表函数使用以下语法:

  • =RTD(ProgID, Server, String 1, String 2, ... String n)

第一个变量 ProgID 表示Real-Time Data 服务器(RTD Server)的编程标识符 (ProgID),我们之前在RTD类的自定义属性中指定了ProgID,这里就可以用“YYRTD.Stock”了。Server 变量指示运行RTD Server的计算机的名称;如果RTD Server在本地运行,则可以将此变量设置为空字符串或将其忽略。后面的变量只表示发送到RTD Server的参数;这些参数的每个唯一组合都表示一个“主题”(topic),每个“主题”有一个关联的“主题 ID”(topic id)。这些参数区分大小写。例如,以下内容表示我们请求600000这只股票的最新价,最高价和最低价

  • =RTD("YYRTD.Stock",,"600000", "last")
  • =RTD("YYRTD.Stock",,"600000", "high")
  • =RTD("YYRTD.Stock",,"600000", "low")

我录制了一个动画,效果如下图,两秒钟刷新一次,由于当前不是交易时间,所以通过Google API获取的值是收盘时的行情,为了掩饰效果,我在原值的基础上加了一个随机变量以演示变化,如果在交易时间的话,您可以看到真实的股票行情变化的,相关代码您可以在附件中进行修改。

3.3 一些开发中的问题

生成的COM组件的目标平台必须与所运行他的Excel或者WPS一致,比如你的WPS是32位,那么生成的组件必须也是32位,否则会无法使用。

Excel RTD函数入门相关推荐

  1. 浅谈Excel开发:五 Excel RTD函数

    上文介绍了Excel中的UDF函数,本文介绍一下同样重要的RTD函数.从Excel 2002开始,Excel引入了一种新的查看和更新实时数据的机制,即real-time data简称RTD函数,他是一 ...

  2. Excel数据分析从入门到精通(三)函数进阶-52个Excel函数之清洗类函数

    Excel数据分析从入门到精通(三)函数进阶-52个Excel函数 1.清洗类函数-left 函数含义 函数使用 2.清洗类函数-right 函数含义 函数使用 3.清洗类函数-MID 函数含义 函数 ...

  3. bili—Excel零基础入门进阶到函数

    Excel零基础入门进阶到函数 1. excel入门 2. 表格设计&自动加总函数 3.冻结表格栏&拆分视窗 4. 资料排序 5. 数据筛选 1. excel入门 每个单元格都有自己的 ...

  4. Excel数据分析从入门到精通(五)函数进阶-52个Excel函数之逻辑类函数

    Excel数据分析从入门到精通(五)函数进阶-52个Excel函数之逻辑类函数 1.逻辑类函数-if 函数含义 函数使用 2.逻辑类函数-AND 函数含义 函数使用 3.逻辑类函数-OR 函数含义 函 ...

  5. Excel数据分析从入门到精通(六)函数进阶-52个Excel函数之关联匹配函数

    Excel数据分析从入门到精通(六)函数进阶-52个Excel函数之关联匹配函数 1.关联匹配函数VLOOKUP 函数含义 函数使用 2.关联匹配函数-hlookup 函数含义 函数使用 3.关联匹配 ...

  6. html表格自动分列,函数在excel的功能 excel用函数实现的分列功能

    我是一位教师,想给学生录成绩.可excel中的函数功能使我无法录入100等在工具-选项-常规-函数提示处关闭即可. 函数(function),名称出自数学家李善兰的著作<代数学>.之所以如 ...

  7. Excel数据分析从入门到精通(十四)动态图表和甘特图

    Excel数据分析从入门到精通(十四)动态图表和甘特图 1.动态图表 ①画板准备 ②绘制复选框 ③图形绘制 2.甘特图 ①准备符合要求的数据 ②美化框线 ③绘制堆积条形图 ④甘特图图形转换绘制 ⑤使用 ...

  8. 工作时间看股票:采用Excel RTD技术获取和讯网的实时股票行情及深沪港最新指数...

    本文地址:http://www.cnblogs.com/Charltsing/p/RTD.html QQ:564955427 在Excel里面获取实时数据大概有几种方式: 1.定时器+函数 2.DDE ...

  9. 《Excel 2019 VBA入门与应用(视频教学版)》图书简介

    #好书推荐##好书奇遇季#<Excel 2019 VBA入门与应用(视频教学版)>京东当当天猫都有发售.Excel VBA是Excel数据分析必须掌握的工具,学习本书能够提高Excel完成 ...

最新文章

  1. 在Mac上通过VMware Fushion 15.1配置静态IP虚拟机实录
  2. 使用VB.NET加快代码开发速度
  3. java 中的内部类学习小记
  4. 十一.python面向对象(接口)abstractmethod,ABCMeta
  5. matlab中的方波信号图片_电气信息类专业课程之matlab系统仿真 第十章 DBPSK调制解调器(9)...
  6. [教程指导]索尼官方4.0.3系统一键root方法! [复制链接]
  7. remote addr 取到内网ip_内网穿透工具frp
  8. flutter tab选项卡appbar中的选项卡
  9. 【ROS学习笔记】(四)订阅者Subscriber的实现
  10. 源码目录变了,修改Eclipse配置:工作空间,JDK
  11. 集成电路模拟版图入门-版图基础学习笔记(六)
  12. Java 代理中转下载 url 文件
  13. 二阶偏微分方程matlab解答,二阶椭圆偏微分方程实例求解(附matlab代码).docx
  14. cousera“Neural NetWorks and Deep learning读书笔记
  15. RSA+AES混合加密实例
  16. CAD角度标注命令,标注CAD图纸
  17. php fpm ts nts,如何判断PHP 是ts还是nts版的?
  18. 好书推荐之《达·芬奇密码》 隐私策略(Privacy policy)
  19. 网吹钱伟长写论文“不必参考任何文献”,但这的确不符合学术规范
  20. 模板王-网页模板,网站模板素材免费下

热门文章

  1. Antd组件库的使用
  2. vue.js django_如何使用Django和Vue.js对Web应用程序进行原型制作
  3. 模拟电路6(共射基本放大电路的组成及工作原理)
  4. vue3.0+ts+element-plus多页签应用模板:侧边导航菜单(上)
  5. 站在山顶上看golang的前世今生与未来展望
  6. 小计合计总计表格_总计总计重复的Excel小计
  7. 使用u启动为苹果笔记本重装win7系统教程
  8. js鼠标拖拽移动盒子但只在父框内移动(三种写法)
  9. ssm+bootsrap人力资源考勤系统-JAVA【数据库设计、源码、开题报告】
  10. linux异常(九):Linux虚拟机启动异常/打不开 位-000005.vmdk”或它所依赖的某个快照磁盘 | 做映射时提示:读取卷信息时出错 请选择其他磁盘文件