繁体   English   中英

Excel UDF计算应返回“原始”值

[英]Excel UDF calculation should return 'original' value

我一直在努力解决VBA问题,我将尽力对其进行尽可能详尽的解释。

我用我自己的RTD实现创建了一个VSTO插件,可以从Excel工作表中调用它。 为了避免在单元格中使用完整的RTD语法,我创建了一个UDF,该UDF从工作表中隐藏了该API。 我创建的RTD服务器可以通过自定义功能区组件中的按钮启用和禁用。

我要实现的行为如下:

  • 如果服务器已禁用,并且在单元格中输入了对我的功能的引用,我希望该单元格显示“ Disabled
  • 如果服务器被禁用 ,但是启用该功能时已在该单元格中输入了该功能(因此该单元格显示一个值),我希望该单元格继续显示该值
  • 如果服务器已启用 ,我希望单元格显示“ Loading

听起来很容易。 这是非功能性代码的示例:

Public Function RetrieveData(id as Long)
  Dim result as String

  // This returns either 'Disabled' or 'Loading'
  result = Application.Worksheet.Function.RTD("SERVERNAME", "", id)
  RetrieveData = result

  If(result = "Disabled") Then

    // Obviously, this recurses (and fails), so that's not an option
    If(Not IsEmpty(Application.Caller.Value2)) Then

      // So does this
      RetrieveData = Application.Caller.Value2

    End If

  End If
End Function

该函数将在数千个单元格中调用,因此将“原始”值存储在另一个数据结构中将是一个主要的开销,我想避免这种情况。 同样,RTD服务器也不知道这些值,因为出于相同的原因,RTD服务器也不保留其历史记录。

我在想可能有某种方法可以退出该函数,从而迫使它不更改显示的值,但是到目前为止,我一直找不到类似的东西。

任何有关如何解决此问题的想法将不胜感激!

谢谢,车

编辑:
由于需求旺盛,一些其他信息说明了我为什么要执行所有操作:如前所述,该函数将在成千上万个单元中调用,RTD服务器需要检索很多信息。 在网络和CPU上这都可能很难。 为了允许用户自己决定是否要在计算机上进行此负载,他或她可以禁用服务器上的更新。 在这种情况下,他或她仍然应该能够使用当前字段中的值来计算工作表,但不会将任何更新推送到工作表中。 一旦需要新数据,就可以启用服务器,并且将更新字段。

再一次,由于我们在这里谈论的是很多数据,所以我宁愿不将其存储在工作表中的某个位置。 此外,即使工作簿已关闭并再次加载,数据也应该可用。

不同的方式=新的答案。

我发现了一些困难的方法,您可能会发现它们有用:

1.在UDF中,像这样返回RTD调用

' excel equivalent: =RTD("GeodesiX.RTD",,"status","Tokyo")
result = excel.WorksheetFunction.rtd( _
    "GeodesiX.RTD", _
    Nothing, _
    "geocode", _
    request, _
    location)

的行为就像您在单元格中插入了注释函数,而不是RTD返回的值。 换句话说,“结果”是“ RTD函数调用”类型的对象,而不是RTD的答案。 相反,这样做:

' excel equivalent: =RTD("GeodesiX.RTD",,"status","Tokyo")
result = excel.WorksheetFunction.rtd( _
    "GeodesiX.RTD", _
    Nothing, _
    "geocode", _
    request, _
    location).ToDouble ' or ToString or whetever

返回实际值,等效于在单元格中键入“ 3.1418”。 这是一个重要的区别; 在第一种情况下,该单元格继续参与RTD馈送,在第二种情况下,它仅获得恒定值。 这可能是您的解决方案。

2. MS VSTO看起来好像写Office Addin简直就是小菜一碟……直到您实际尝试构建工业的,可分发的解决方案为止。 为安装程序正确获取所有特权和权限是一场噩梦,如果您有一个聪明的主意要支持多个版本的Excel,那将成倍恶化。 我已经使用Addin Express多年了。 它掩盖了所有这些MS的烦恼,让我专注于对插件进行编码。 他们的支持也是一流的,值得一看。 (不,我没有隶属关系或类似的东西)。

3.请注意,即使您处于中间状态,Excel也可以并且随时会调用Connect / RefreshData / RTD-幕后有一些微妙的多任务处理。 您需要使用适当的Synclock块来修饰代码,以保护数据结构。

4.接收数据时(大概在一个单独的线程上异步),您绝对必须在最初被您调用(由Excel)的线程上回调Excel。 如果不这样做,它将在一段时间内正常工作,然后您将开始在后台遇到神秘的,无法解决的崩溃以及更糟糕的孤立Excel。 这是执行此操作的相关代码的示例:

    Imports System.Threading
    ...
    Private _Context As SynchronizationContext = Nothing
    ...
    Sub New
      _Context = SynchronizationContext.Current
      If _Context Is Nothing Then
         _Context = New SynchronizationContext ' try valiantly to continue    
      End If
    ...
    Private Delegate Sub CallBackDelegate(ByVal GeodesicCompleted)

    Private Sub GeodesicComplete(ByVal query As Query) _
        Handles geodesic.Completed ' Called by asynchronous thread

        Dim cbd As New CallBackDelegate(AddressOf GeodesicCompleted)

        _Context.Post(Function() cbd.DynamicInvoke(query), Nothing)
    End Sub
    Private Sub GeodesicCompleted(ByVal query As Query)

        SyncLock query

            If query.Status = "OK" Then

                Select Case query.Type

                    Case Geodesics.Query.QueryType.Directions
                        GeodesicCompletedTravel(query)

                    Case Geodesics.Query.QueryType.Geocode
                        GeodesicCompletedGeocode(query)

                End Select
            End If

            ' If it's not resolved, it stays "queued", 
            ' so as never to enter the queue again in this session
            query.Queued = Not query.Resolved

        End SyncLock

        For Each topic As AddinExpress.RTD.ADXRTDTopic In query.Topics
            AddinExpress.RTD.ADXRTDServerModule.CurrentInstance.UpdateTopic(topic)
        Next

    End Sub

5.我所做的事情显然类似于您在此插件中的要求。 在那里,我异步地从Google提取了地理编码数据,并将其与由UDF屏蔽的RTD一起提供。 由于致电GoogleMaps的费用非常昂贵,因此我尝试了101种方法和几个月的晚上,以将值保留在单元格中,就像您尝试的那样,但没有成功。 我没有计时,但是我的直觉是对Excel的调用(例如“ Application.Caller.Value”)比字典查找慢一个数量级。

最后,我创建了一个缓存组件,该组件将保存并重新加载已经从非常隐藏的电子表格中获取的值,该电子表格是我在Workbook OnSave中即时创建的。 数据存储在Dictionary(字符串,myQuery)中,其中每个myQuery都保存所有相关信息。

它运行良好,可以满足脱机工作的需求,甚至对于瞬间出现的20'000多个公式也是如此。

HTH。


编辑:出于好奇,我测试了自己的直觉,即调用Excel比进行字典查找要昂贵得多。 事实证明,预感不仅正确,而且令人恐惧。

Public Sub TimeTest()
    Dim sw As New Stopwatch
    Dim row As Integer
    Dim val As Object
    Dim sheet As Microsoft.Office.Interop.Excel.Worksheet
    Dim dict As New Dictionary(Of Integer, Integer)

    Const iterations As Integer = 100000
    Const elements As Integer = 10000

    For i = 1 To elements + 1
        dict.Add(i, i)
    Next
    sheet = _ExcelWorkbook.ActiveSheet

    sw.Reset()
    sw.Start()
    For i As Integer = 1 To iterations
        row = 1 + Rnd() * elements
    Next
    sw.Stop()
    Debug.WriteLine("Empty loop     " & (sw.ElapsedMilliseconds * 1000) / iterations & " uS")

    sw.Reset()
    sw.Start()
    For i As Integer = 1 To iterations
        row = 1 + Rnd() * elements
        val = sheet.Cells(row, 1).value
    Next
    sw.Stop()
    Debug.WriteLine("Get cell value " & (sw.ElapsedMilliseconds * 1000) / iterations & " uS")

    sw.Reset()
    sw.Start()
    For i As Integer = 1 To iterations
        row = 1 + Rnd() * elements
        val = dict(row)
    Next
    sw.Stop()
    Debug.WriteLine("Get dict value " & (sw.ElapsedMilliseconds * 1000) / iterations & " uS")

End Sub

结果:

Empty loop     0.07 uS
Get cell value 899.77 uS
Get dict value 0.15 uS

在1万个元素的Dictionary中查找值(从Integer,Integer)比从Excel中获取单元格值快11000倍

QED

也许...尝试使您的UDF包装函数为非易失性,那样除非其参数之一发生变化,否则它不会被调用。

当启用服务器时,这可能是一个问题,您将不得不诱使Excel再次调用UDF,这取决于您要执行的操作。

也许解释一下您要实现的完整功能?

您可以尝试Application.Caller.Text
这具有将渲染层的格式化值作为文本返回的缺点,但似乎避免了循环引用问题。
注意:我尚未在所有可能的情况下测试此黑客...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM