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

我一直在努力处理VBA问题,我会尽可能地彻底地解释它。

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

我想要达到的行为如下:

  • 如果服务器被禁用,并且在单元格中input了对我的函数的引用,我希望单元格显示为Disabled
  • 如果服务器被禁用 ,但function已被启用(并因此显示一个值的单元格)中input一个单元格,我希望该单元格保持显示该值
  • 如果服务器启用 ,我希望单元格显示Loading

听起来很容易。 这是一个非function代码的例子:

 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服务器不知道这些值,因为它也没有保留它的历史,或多或less出于同样的原因。

我在想,可能有某种方法可以退出这个强制它不改变显示值的函数,但到目前为止,我一直找不到这样的东西。

任何想法如何解决这个非常感谢!

谢谢,谢

编辑:
由于受欢迎的需求,为什么我想要做所有这些一些额外的信息:正如我所说,function将被称为成千上万的单元格和RTD服务器需要检索相当多的信息。 这在networking和CPU上可能相当困难。 为了允许用户自己决定是否要在他的机器上装载这个负载,他或她可以禁止来自服务器的更新。 在这种情况下,他或她应该仍然能够使用当前字段中的值来计算表单,但不会将更新推送到这些表单中。 一旦需要新的数据,服务器可以启用,字段将被更新。

再一次,因为我们在这里谈的是相当多的数据,所以我宁愿不把它存储在表单的某个地方。 另外,即使工作簿已closures并再次加载,数据也应该可用。

不同的scheme=新的答案。

我发现了一些困难的事情,你可能会觉得有用:

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

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

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

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

返回实际值,相当于在单元格中input“3.1418”。 这是一个重要的区别; 在第一种情况下,细胞继续参与RTD馈送,在第二种情况下,它只是获得恒定的值。 这可能是一个解决scheme。

2. MS VSTO使它看起来好像写一个Office Addin是一块蛋糕,直到你实际上试图build立一个工业,可分配的解决scheme。 为安装程序获取所有权限和权限是一个噩梦,如果您有支持多个版本的Excel的明智想法,则它会成倍地恶化。 几年来我一直在使用Addin Express 。 它隐藏了所有这些MS nastiness,让我专注于编码我的插件。 他们的支持也是一stream的,值得一看。 (不,我不附属或类似的东西)。

3.请注意,Excel可以随时调用Connect / RefreshData / RTD,即使您处于某种事情的中间 – 在幕后进行了一些微妙的多任务处理。 您需要使用适当的Synclock块来修饰代码,以保护您的数据结构。

4.当你接收到数据(大概是在一个单独的线程上asynchronous),你绝对必须在你被调用的线程上(通过Excel)callbackExcel。 如果你不这样做,它会运行良好一段时间,然后你会开始变得神秘,无法解决的崩溃,更糟的是,在后台孤立的Excels。 以下是相关代码的示例:

  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 

我做了一些显然类似于你在这个插件中要求的东西。 在那里,我asynchronous地从Google获取地理编码数据,并通过由UDF遮蔽的RTD提供它。 由于GoogleMaps的通话非常昂贵,我尝试了101种方式和几个月的夜晚,以保持小区中的价值,就像您正在尝试的那样,没有成功。 我没有计时,但我的直觉是,像“Application.Caller.Value”这样的Excel调用比字典查找慢了一个数量级。

最后,我创build了一个caching组件,该组件保存并重新载入已经在Workbook OnSave中实时创build的非常隐藏的电子表格中获取的值。 数据存储在Dictionary(string,myQuery)中,每个myQuery保存所有相关的信息。

它运行良好,满足离线工作的要求,即使是20,000+配方,也是瞬间出现的。

HTH。


编辑:出于好奇,我testing了我的预感,称Excel比字典查找要贵得多。 事实certificate,预感不仅是正确的,而且是惊人的。

 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 

在一个10'000元素字典(整数,整数)中查找一个值比从Excel中获取单元格值快11'000倍

QED

也许…尝试使你的UDF包装函数是非易失性的,这样它将不会被调用,除非它的一个参数改变。

这可能是一个问题,当你启用服务器,你将不得不诱使Excel再次调用你的UDF,这取决于你想要做什么。

也许可以解释你正在尝试实现的完整function?

你可以尝试Application.Caller.Text
这样做的缺点是从渲染层返回格式化的值作为文本,但似乎避免了循环引用问题。
注意:我没有在所有可能的情况下testing这个黑客行为…