簡體   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