VBA 中的虚拟范围对象

Virtual Range object in VBA

我有一个宏,我试图用它来爬行工作簿以查找特定用户定义函数 (UDF) 的实例,并检查它找到的每个 UDF 的参数是否满足条件.

根本问题是如何获取 Range 对象(单个单元格)并将 UDF 的参数解析为实际值,即不只是读取 .Formula 字符串,它可能包含实际值和单元格引用的混合,但要识别实际上是单元格引用的参数并解决这些问题。如果我手动执行此操作,则有点像选择 UDF 的每个参数并按 F9 进行计算,是否有意义?

我愿意接受任何稳健的方法来实现最终目标,正如我所说,最终目标是能够解析传递给 UDF 的参数的实际值,显然不是来自 UDF 代码本身。

具体来说,我使用以下方法遍历工作簿中的所有工作表:

For Each ws in ActiveWorkbook.Worksheets

然后我使用搜索功能(超出本期讨论范围)return 一组使用我的 UDF 的单元格,我将其称为

UDF(arg1, arg2)

为了保持简单。

然后我遍历该单元格集合,对于每个单元格,我想基本上将参数连接成一个以“”分隔的字符串。因此,如果特定单元格包含公式

=UDF(A1, "test")

假设单元格 A1 包含“一月”,需要明确的是,我不希望对“A1\test”进行 return 编辑,而是对“January\test”进行编辑。

我想我已经解决了这个问题(可能还有其他更优雅的方法)。 我最终编写了一个辅助函数来尝试解析单元格引用(相同的 sheet、不同的 sheet、命名范围)并使用它(连同 special-character 修剪)来解析所有参数到我的 UDF。本质上它是一个解析器。

代码是(对于参数解析器 - 假设已经解析了 UDF .Formula 字符串以使用逗号分隔或其他方式提取参数):

Private Function resolveCellRef(cellRef As String, cell As Range) As String
    ' This function takes an argument to a UDF that might be a cell reference or Named Range, and tries to resolve the value.
    ' It needs to handle the cases where a value has been passed, and just return the value again.
    ' Created by Rob Baker, 11/02/2022
    Dim pos1 As Integer, tmpStr As String
    Dim ws As Worksheet
    Dim error_raised As Boolean
    
    ' Check if it is already a string argument. If so, trim the quotation marks and return
    If Left(cellRef, 1) = """" Then
        resolveCellRef = Replace(cellRef, """", vbNullString)
        Exit Function
    End If
    ' Check for whether the argument is a cell reference to a foreign sheet
    error_raised = False
    pos1 = InStr(1, cellRef, "!")
    Select Case pos1
    Case 0
        Set ws = cell.Parent                            ' Cell reference must be local to sheet
        tmpStr = cellRef
    Case Else
        tmpStr = Left(cellRef, pos1 - 1)
        If Left(tmpStr, 1) = "'" Then                   ' Trim apostrophes if present
            tmpStr = Left(tmpStr, Len(tmpStr) - 1)
            tmpStr = Right(tmpStr, Len(tmpStr) - 1)
        End If
        Set ws = ActiveWorkbook.Worksheets(tmpStr)      ' Set the ws reference
        tmpStr = Right(cellRef, Len(cellRef) - pos1)    ' This is now the cell reference alone
    End Select
    On Error GoTo errhandler                            ' Use error handling to handle two known issues
    resolveCellRef = ws.Range(tmpStr).Value2
    If error_raised Then resolveCellRef = ActiveWorkbook.Names(cellRef).RefersToRange.Value2
    Exit Function
    
errhandler:
    If Err.Number = 1004 And Not error_raised Then      ' Set the error_raised flag and resume next to try a Named Range
        Err.Clear: error_raised = True
        Resume Next
    ElseIf Err.Number = 1004 And error_raised Then      ' If we get here, then the cellRef isn't a cell reference or Named Range, so just return it as is
        Err.Clear
        resolveCellRef = cellRef
        Resume Next
    End If
End Function

我修改了上面复杂的(但主要是功能性的)答案,使用 Worksheet.Evaluate:

更简单的形式
Private Function resolveCellRef(CellRef As String, cell As Range) As String
    ' This function takes an argument to a UDF that might be a cell reference or Named Range, and tries to resolve the value.
    ' It needs to handle the cases where a value has been passed, and just return the value again.
    ' RMB v1.0.0: whole overhaul of how this function works
    Dim ws As Worksheet
    
    Set ws = cell.Parent
    resolveCellRef = ws.Evaluate(CellRef)
    
End Function

当我意识到这个 Worksheet 方法存在时,真是一个 'well duh' 时刻! 我花了相当多的精力使参数解析器稳健地工作,这实际上非常简单,因为使用 Evaluate 的工作表版本(而不是同名的 Application 方法)自动继承单元格引用、命名范围等的上下文