VBA - 在 Evaluate() 中包含自己的函数

VBA - Including own function within Evaluate()

在VBA中,我习惯用Evaluate()计算一列的值,这是另一列的函数,为了不使用循环。例如,要评估第 2 列,其值是第 1 列值的指数,我会写(这里仅限于前 10 行):

Dim rng1 As Range
Dim rng2 As Range

Set rng1 = Worksheets("Sheet1").Range(Cells(1,1),Cells(10,1))
Set rng2 = Worksheets("Sheet1").Range(Cells(1,2),Cells(10,2))

rng2 = Evaluate("EXP(" & rng1.Address & ")")

我现在想以同样的方式处理我自己的功能。为了进行测试,我创建了一个函数,该函数 returns 输入的指数值:

Function TestExpo(alpha) As Double
    TestExpo = EXP(alpha)
End Function

并且我按照之前使用 Evaluate() 所述的相同方式进行操作:

rng2 = Evaluate("TestExpo(" & rng1.Address & ")")

我没有收到错误,但 Excel sheet 上什么也没发生。我试图通过在函数名称中输入错误来触发错误,例如:

rng2 = Evaluate("TestEx(" & rng1.Address & ")")

我实际上在 sheet 上的 rng2 的每个单元格中都遇到了“名称”错误。因此,调用函数“TestExpo”起作用。我还尝试使用实数而不是 rng1.Address:

rng2 = Evaluate("TestExpo(" & 2 & ")")

并且我在 sheet 上的 rng2 的所有单元格中得到了正确的 exp(2) 值,因此调用该函数并取回单元格中的值也是如此。

我试图以不同的方式指定输入和输出的类型。例如:

Function TestExpo(alpha) ' No error, but no values on the sheet
Function TestExpo(alpha) As Range ' No error, but no values on the sheet
Function TestExpo(alpha) As Variant ' No error, but no values on the sheet
Function TestExpo(alpha As Range) ' No error, but no values on the sheet
Function TestExpo(alpha As Variant) ' No error, but no values on the sheet
Function TestExpo(alpha As Double) ' Value error for each cell of the range rng2

除了最后一次使用 alpha As Double 进行的测试外,我没有收到任何错误,但 Excel sheet.

上也没有任何值

我运行没主意了...我试图找到VBA的EXP()函数的代码,看看如何指定输入和输出,认为如果我在函数“TestExpo”中使用相同的类型和参数,它可能会工作,但我找不到 EXP() 函数的代码,它可能不是 public.

有没有人遇到过类似情况并解决了?

亲切的问候 泽维尔


更新:

感谢您的贡献!我继续前进!

当我使用时,正如@FaneDuru所建议的那样

rng2 = Evaluate("""=TestExpo(" & rng1.Address & ")""")

我在每个单元格中都遇到了值错误。检查工作中的单元格sheet,我可以看到每个单元格都归因于公式:

"=@TestExpo($A:$A)"

我比较写作

rng2 = Evaluate("""=EXP(" & rng1.Address & ")""")

哪个有效并将公式归因于每个单元格

"=EXP(@$A:$A)"

所以 @ 出现在不同的位置。我手动将公式“=@TestExpo($A$1:$A$35)”更正为“=TestExpo(@$A$1:$A$35)”,结果成功了!

所以问题是@的位置问题,我不明白为什么@在使用函数EXP时默认放在括号内的范围前面,而在我自己的函数中却在函数名前面被使用了。

然而,手动更正单元格中的公式并不方便。受@FaneDuru 提议的启发,我通过以下方式在范围名称后添加 .Cells(1,1).Address(0,0) 来更正我的代码:

rng2 = Evaluate("""=TestExpo(" & rng1.Cells(1,1).Address(0,0) & ")""")

这有效!

现在,第一个单元格中的公式如下所示:

"=@TestExpo(A1)"

单元格地址适应每一行(第2行公式为“=@TestExpo(A2)”,第3行“=@TestExpo(A3)”等)

@ 仍然出现在函数名前面,但是括号内有一个单元格名称而不是范围,这使得公式有效。

现在我不得不承认我并不真正理解代码语法是如何工作的。事实上,添加 .Cells(1,1).Address(0,0),我预计 rng2 的所有单元格都会引用范围 rng1 的单元格(1,1),但公式却适应了每个单元格以正确的方式。

所以,我最初的问题已经解决了:

rng2 = Evaluate("""=TestExpo(" & rng1.Cells(1,1).Address(0,0) & ")""")

或者更简单:

rng2 = "=TestExpo(" & rng1.Cells(1,1).Address(0,0) & ")"

不过,我很想了解代码语法的工作原理——即如果代码明确引用 rng1.Cells(1,1).Address(0,0) 中的 cell(1,1) , rng2 的所有单元格怎么可能不是引用单元格(1,1)而是引用正确的单元格(单元格(2,1)、单元格(3,1)等)?

而且我也想明白为什么@在使用函数EXP时默认放在范围前面,而在使用我自己的函数时@默认放在函数名前面。有人有想法吗?

要求值的函数必须是字符串(双引号之间)...

请试试

rng2 = Evaluate("""=TestEx(" & rng1.Address & ")""")

已编辑:

请尝试下一个(已测试)示例:

Function myFuncX(x As Long) As Long
   myFuncX = x + x * 2
End Function

测试子对其进行评估。当然,你一定觉得“J2:J4”长值合适...

Sub testEvaluateCustFunc()
    Dim rng As Range, rng2 As Range
    Set rng = Range("I2:I4"): Set rng2 = Range("J2:J4")
    rng.Value = Application.Evaluate("""=myFuncX(" & rng2.cells(1, 1).Address(0, 0) & ")""")
End Sub

已编辑:

请尝试了解下一次测试会发生什么Sub。该方法能够计算公式、名称、范围、对象及其属性,返回对象、值或数组:

Sub testEvaluate()
  Dim rng As Range, rng2 As Range, testArr As Variant
  Set rng = Range("I2:I4"): Set rng2 = Range("J2:J4")

  'Application.Evaluate can simple be replaced by using []:
  testArr = [J2:J4]: Debug.Print Join(Application.Transpose(testArr), "||") 'Transpose is necessary to convert in 1D array
                                                                                                 'able to be shown in Immediate Window (IW)...
  testArr = Application.Evaluate("Index(" & rng2.Address(0, 0) & ",)")  'in this way an array of the range values is built
  Debug.Print Join(Application.Transpose(testArr), ",")                       'here the array can be visualized in IW
  testArr = Evaluate("Row(1:5)")                         'A nice way of making an array of numbers using the Row property
  Debug.Print Join(Application.Transpose(testArr), "|")  'See the resulted array in IW
  'the next line evaluates the function for all rng range, incrementing the function argument addresses:
  'I did not see your function and I used this way ONLY TO USE THE FIRST CELL IN THE RANGE TO BE INCREMENTED
  rng.Value = Application.Evaluate("""=myFuncX(" & rng2.cells(1, 1).Address(0, 0) & ")""")
  Debug.Print rng2.Address, rng2.Address(0, 0) ' See here the difference between absolute and relative address
                                                               'Only a relative address/reference can be incremented!
  'trying the absolute reference, will produce a wrong result, the formula using only the first cell of the range (absolute address):
  rng.Value = Application.Evaluate("""=myFuncX(" & rng2.cells(1, 1).Address & ")""")
  testArr = rng.Value
  Debug.Print Join(Application.Transpose(testArr), " ") 'the function is using only the first range cell
  'Now, the same thing using the first cell addres. Because I know what parametes the function uses...
  rng.Value = Application.Evaluate("""=myFuncX(" & Range("J2").Address(0, 0) & ")""")
  testArr = rng.Value
  Debug.Print Join(Application.Transpose(testArr), "|") 'if using the absolute address, the result will allways refer only
                                                                        'the first range cell, not being able to increment an absolute ref...
End Sub

如果您的函数应该接受许多单元格的范围,它可以像我最初建议的那样工作。例如,使用下一个函数:

Function myFunc2(rng As Range) As Long
   myFunc2 = rng.cells.count * 2 + Len(rng.cells(1, 1).Value)
End Function

您可以在测试中使用下一个结构 Sub:

Sub testmyFunc2()
    Dim rng3 As Range: Set rng3 = Range("F2:F4")
    Dim rng2 As Range: Set rng2 = Range("J2:J4")
    Debug.Print myFunc2(Range("J2:J4"))
    rng3.Value = Evaluate("""=myFunc2(" & rng2.Address & ")""")
End Sub

我希望现在更清楚了...如果还有什么不清楚的地方,请不要犹豫,要求澄清。但是准时,在特定方面。 Evaluate 方法在使用方法方面非常复杂...

不幸的是,我不认为你可以用这种方式做你想做的事。 我认为您的示例没有达到您的期望。

rng2 = Evaluate("EXP(" & rng1.Address & ")")

将 return 值数组,但只会将第一个值输入到 rng2 中的每个单元格中。 所以,即使你可以模仿 EXP 正在做的事情,它仍然不会做你想要的。

但是,如果您仍想模仿使用 EXP 函数获得的(可能不正确的)功能:

EXP 和您的 UDF 之间的区别在于 EXP 函数可以处理和 return 数组,而您的 UDF 不能。

这是一个非常快速的功能,可以(并且应该)进行显着改进,但它可能会给您一个起点。

Public Function test(a As Variant) As Variant
    Dim arr() As Double
    If IsNumeric(a) Then
        ReDim arr(1 To 1)
        arr(1) = Exp(a)
    Else
        ReDim arr(1 To a.Count)
        Dim i As Long
        For i = 1 To a.Count
            arr(i) = Exp(a(i))
        Next i
    End If
    test = arr
End Function

更新:

显然,工作表公式中使用的 EXP 函数与 VBA 版本之间似乎也存在差异。

VBA 版本只接受一个单元格或值,而工作表版本能够接受 range/array 个值。 通常,您可以使用 Application.WorksheetFunction 访问工作表版本,但不幸的是,这似乎不适用于 EXP