上文介绍了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函数了:

  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分配的TopicID
    public 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 value
        if (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获取的值是收盘时的行情,为了掩饰效果,我在原值的基础上加了一个随机变量以演示变化,如果在交易时间的话,您可以看到真实的股票行情变化的,相关代码您可以在附件中进行修改。

四 结语

   本文介绍了Excel中一类非常重要的函数,及RTD函数,他是Excel提供的一种Push-Pull机制的数据更新机制,在一些对数据实时性要求较高的场合,比如直播比赛得分,实时天气预报,交易所实时行情等方面用的很多。并且通过RTD的这种机制我们可以在此基础上实现异步的UDF函数,即当请求发进来的时候,我们暂不处理,记下TopicID,然后放到处理队列中,待处理完成后,调用UpdateNotify方法,然后刷新已经处理完的TopicID的单元格。下文将会介绍Excel中异步自定义函数,异步的UDF函数能够极大地提高Excel插件的用户体验,能够提高系统的可扩展性和稳定性。

   本文所有的代码点击此处下载,希望本文对您了解Excel中的RTD函数有所帮助。

浅谈Excel开发:五 Excel RTD函数的更多相关文章

  1. 浅谈WebService开发三(动态调用WebService)转

    在前两讲里,我已经向大家演示了如何使用WebService.同步, 异步调用WebService,而在实际开发过程中,可能会有多个WebService接口供你选择,而在程序执行过程中才决定使用哪一个 ...

  2. 浅谈MFC类CrackMe中消息处理函数查找方法

    最近一个学姐发给我了一份CrackMe希望我解一下,其中涉及到了MFC的消息函数查找的问题,就顺便以此为例谈一下自己使用的消息函数查找的方法.本人萌新,如果有任何错漏与解释不清的地方,欢迎各路大佬指正 ...

  3. 浅谈iOS开发中多语言的字符串排序

    一.前言 在iOS开发中,一个经常的场景是利用tableview展示一组数据,以很多首歌曲为例子.为了便于查找,一般会把这些歌曲按照一定的顺序排列,还会加上索引条以便于快速定位. 由于歌曲名可能有数字 ...

  4. 浅谈 Android 开发文化

    Hello,亲爱的读者朋友们(希望你们是 Android 开发者,或者正在成为 Androider 的路上-)! 质量从用户反馈很清凉然后我们就只能看 CPU 原来的想法是但是事实上不是这些但是我们可 ...

  5. 浅谈iOS开发的协议(protocol)和代理(delegate)

    协议和代理对于一个新手来说确实不讨好理解,也有很多的iOS开发的老手对此是懂非懂的.网上的很多博文只是讲了怎么使用,并没有说的很明白.下面我谈一下我的理解. 1.你要先搞明白,协议和代理为什么会出现, ...

  6. Himi浅谈游戏开发de自学历程!(仅供参考)

    李华明Himi原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/himistudy/382.html ☞ 点击订阅 ☜ 本博客 ...

  7. 浅谈Java开发三层架构

    三层架构,一般来说就是将整个业务应用划分为:表现层(UI).业务逻辑层(BLL).数据访问层(DAL).区分层次的目的即为了“高内聚,低耦合”的思想. 概念简介 1.表现层(UI):简单来说,就是展现 ...

  8. Qt浅谈之四十五QSplitter实现自由伸缩滑动窗口

    一.简介 最近看到一篇Qt实现伸缩滑动的窗口的文章,但其代码不完整.便在此重新书写了完整的代码,并开源出来.窗口的中央有滑动条可以动态改变子窗口的大小,隐藏的按钮可以快速伸缩子窗口.其效果图如下: 二 ...

  9. 浅谈 HTML5plus 开发移动应用

    什么是 HTML5plus DCloud 公司提供的,使用传统 web 技术开发移动应用的解决方案. 增强版的手机浏览器引擎,让HTML5达到原生水平! 产品口号,总会有些夸张的成分,不要在意这些细节 ...

  10. 浅谈Web开发中的定时任务

    曾经做过Windows server下的定时任务的业务,最近又做了一些Linux下使用Crontab做的定时任务的业务,觉得有必要进行一次小结,于是有了如下这篇文章. Windows Server下 ...

随机推荐

  1. 性能:CPU、Memory、耗电量

    1.自动 APP 性能测试需求讨论:https://testerhome.com/topics/3172 2.Android Studio中怎么使用DDMS工具?:http://www.cnblogs ...

  2. 使用 KGDB 调试 Kernel On Red Hat Linux

    1. KGDB 简介         KGDB  提供了一种使用 GDB 调试 Linux 内核的机制.使用 KGDB 可以象调试普通的应用程序那样,在内核中进行设置断点.检查变量值.单步跟踪程序运行 ...

  3. Groovy 转换JSON和生产JSON

    Groovy 类和JSON之间的相互转换,主要在groovy.json包下面 1. JsonSlurper JsonSlurper 这个类用于转换JSON文本或从Groovy 数据结构中读取内容例如m ...

  4. java安装教程

    1.安装中,jdk和jre的安装在不同的目录中 2.环境变量的配置 选择系统变量中 新建以下3个环境变量: JAVA_HOME  jdk安装路径 CLASSPATH  .;%JAVA_HOME%\li ...

  5. sharebutton

    <h1>Share Buttons</h1> <!-- Twitter --> <a href="http://twitter.com/share? ...

  6. jQuery操作Dom、jQuery事件机制、jQuery补充部分

    jQuery操作Dom: 修改属性: //使用attr()方法 //attr(name, value) //name:要修改的属性的属性名 //value:对应的值 //attr方法,如果当前标签有要 ...

  7. oracle的加密和解密

    加密函数 create or replace function encrypt_des(p_text varchar2, p_key varchar2) return varchar2 isv_tex ...

  8. apache虚拟主机配置HTTPS

    win+apache+php的环境下做虚拟主机的https. 1.https用的是443端口,确定防火墙已经开放443了.2.http.conf要加载以下模块: #这两个是用来存放SSLSession ...

  9. UVALive 4329 Ping pong(树状数组)

    题目链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=13895 题意:一条街上住有n个乒乓选手,每个人都有一个技能值,现在 ...

  10. cs程序添加初始化加载

    this.Name = "mysirst"; this.Text = "车辆窗体程序"; this.Load += new System.EventHandle ...