简体   繁体   中英

excel vba preserve original value if UDF formula fails

In individual cells, I have an assigned formula that references a UDF:

=getValueFromWorkbook("OtherWorkbook", 10)

The getValueFromWorkbook UDF roughly does something like...

Function getValueFromWorkbook(workbookName As String, identifier As Integer) As Variant
  ' some magic is done to get the `worksheetName` and `cellRange`
  ' but for simplicity, we'll just stub them here...
  Dim worksheetName As String: worksheetName = "SomeWorkSheet"
  Dim cellRange As String: cellRange = "A1"
  getValueFromWorkbook = Workbooks("" & workbookName & ".xlsx").Worksheets(worksheetName).Range(cellRange).Value
End Function

This works great and as long as OtherWorkbook.xlsx is an open workbook, the cell gets the correct value and the world is happy.

Now, if I close the OtherWorkbook.xlsx workbook, things continue to work and the cell values are still reflected.

However, if I delete a row or perform some other action that causes Excel to recalculate all of the cell values, the UDF fails (because the referenced workbook is no longer open), resulting in a dreaded #VALUE! .

Ideally, I'd like this to preserve the original (stale) value rather than returning an error, but I haven't figured out a way to do this yet.

What I've tried...

Function getValueFromWorkbook(...) As Variant
  ...
  On Error Resume Next
    getValueFromWorkbook = ...
  If Err.Number <> 0 Then
    Err.Clear
    getValueFromWorkbook = Application.Caller.Value
  End If
End Function

But this results in a circular reference error:

Cell references in the formula refer to the formula's result, creating a circular reference.

However, if I change Application.Caller.Value to Application.Caller.Text , this somewhat works, but it returns as text and I lose the original value formatting.

So, long story short, is there a way to preserve the original linked value rather than returning a garbage #VALUE! ?

PS I'm pretty new to VBA so there might be something obvious I'm missing here.

You can simply use Application.Caller.Text rather than Application.Caller.Value to return the original text of a cell. The one drawback to this is that the cell's value, no matter the type , will be cast as a string. Depending on other cells/formulas (ie, if you're expecting the "OtherWorkbook" to return a number rather than a string) that reference the Application.Caller cell, you might have to update them with a =VALUE() .

This will work as long as you do not try to update the actual cell with the value in it. As soon as you enter a cell, the Application.Caller turns into a circular reference and you will just get 0 back; you should be able to delete other rows though.

Function getValueFromWorkbook(...) As Variant
  Dim Org As String
  Org = Application.Caller.Text      

  ...
  On Error Resume Next
    getValueFromWorkbook = ...
  If Err.Number <> 0 Then
    Err.Clear
    getValueFromWorkbook = Org
  End If
End Function

How about something like this:

' Globally available variable to cache the external value.
Public GlobalValue As Variant

Function getValueFromWorkbook(workbookName As String, identifier As Integer) As Variant
    ' some magic is done to get the `worksheetName` and `cellRange`
    ' but for simplicity, we'll just stub them here...
    Dim worksheetName As String: worksheetName = "SomeWorkSheet"
    Dim cellRange As String: cellRange = "A1"

    Dim returnValue As Variant

    ' Attempt to get from the external sheet.
    ' This will fail if it isn't available.
    On Error Resume Next
    returnValue = Workbooks(workbookName & ".xlsx").Worksheets(worksheetName).Range(cellRange).Value

    If Err = 0 Then
        ' No error, we retrieved the value from the external file.
        ' Set it locally for use when this value isn't available.
        GlobalValue = returnValue
    Else
        ' Error - the external sheet wasn't available.
        ' Use the local value.
        returnValue = GlobalValue
    End If

    ' Done with error trapping.
    On Error GoTo 0

    getValueFromWorkbook = returnValue

End Function

The idea is you cache the value locally and can use that as a fallback for when your workbookName isn't available.

I have found a way to do it:

Suppose you put your User Defined Function UDF into cell A1 and in certain conditions want it to keep the previous value without recalculating. Then in such cases make this function return any error value: UDF = CVErr(xlErrNull) and modify your A1 formula in the following way: =IFERROR(UDF(...); A1)

This likely will produce warnings of circular reference - you can get rid of it by enabling circular references in File -> Options -> Formulas and saving file with this setting.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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