UDF在任何地方都返回相同的值

我正在尝试移动平均代码在vba但以下返回相同的价值无处不在。

Function trial1(a As Integer) As Variant Application.Volatile Dim rng As Range Set rng = Range(Cells(ActiveCell.Row, 2), Cells(ActiveCell.Row - a + 1, 2)) trial1 = (Application.Sum(rng)) * (1 / a) End Function 

ActiveCell属性不属于UDF,因为它改变了 。 有时甚至不在同一张工作表上。

如果您需要在工作表中引用自定义UDF函数所在的单元格,请使用Application.Caller方法。 Range.Parent属性可用于在With … End With语句中显式标识工作表(并避免进一步混淆)。

 Function trial1(a As Integer) As Variant Application.Volatile Dim rng As Range with Application.Caller.Parent Set rng = .Range(.Cells(Application.Caller.Row, 2), _ .Cells(Application.Caller.Row - a + 1, 2)) trial1 = (Application.Sum(rng)) * (1 / a) end with End Function 

你已经应用了Application.Volatile¹方法,但是通过不明确地指定父工作表,允许将范围平均到默认的ActiveSheet属性 。

使用Excel应用程序对象返回SUM函数的结果和一些math计算平均值。 同一个命令可以在工作表的AVERAGE函数中返回,但空白单元格的处理方式不同。

  trial1 = Application.Average(rng) 

¹ 只要整个工作手册中的任何内容发生变化,挥发性函数就会重新计算,而不仅仅是当影响结果的内容发生变化时。

对于UDF来说,计算一个数字的移动平均值对我来说是很奇怪的。 如果在工作表中使用这个UDF,我相信你会把它放在现有数据的旁边,如果你想改变平均数量的范围的大小,你手动更新它们?

假设您可以命名范围“ MovingAverageSize ”来存储范围的大小以计算平均值,以及现有数据右侧的平均值,请考虑以下内容:

  • 范围C2被命名为MovingAverageSize
  • 从B3存储的数据向下
  • 移动平均结果存储在数据右侧的1列
  • 如果数据小于MovingAverageSize ,则SUM函数会相应地进行调整
  • 任何计算错误都会导致结果为零
  • 每次MovingAverageSize更改值时,都会触发一个Sub来更新公式(代码放置在Worksheet对象中而不是普通模块中)
  • 或者,您可以更改代码以将MovingAverage放置到MovingAverageSize的同一列,以便您可以将几个不同的大小相互比较。

工作表对象中的代码:

 Option Explicit Private Sub Worksheet_Change(ByVal Target As Range) If Target.Count = 1 Then If Target.Address = ThisWorkbook.Names("MovingAverageSize").RefersToRange.Address Then UpdateMovingAverage Target End If End Sub Private Sub UpdateMovingAverage(ByRef Target As Range) Dim oRngData As Range, oRng As Range, lSize As Long, lStartRow As Long Debug.Print "UpdateMovingAverage(" & Target.Address & ")" If IsNumeric(Target) Then lSize = CLng(Target.Value) If lSize <= 0 Then MsgBox "Moving Average Window Size cannot be zero or less!", vbExclamation + vbOKOnly Else ' Top Data range is "B3" Set oRngData = Target.Parent.Cells(3, "B") ' <-- Change to match your top data cell lStartRow = oRngData.Row ' Set the Range to last row on the same column Set oRngData = Range(oRngData, Cells(Rows.Count, oRngData.Column).End(xlUp)) Application.EnableEvents = False For Each oRng In oRngData If (oRng.Row - lSize) < lStartRow Then oRng.Offset(0, 1).FormulaR1C1 = "=iferror(sum(R[" & lStartRow - oRng.Row & "]C[-1]:RC[-1])/MovingAverageSize,0)" Else oRng.Offset(0, 1).FormulaR1C1 = "=iferror(sum(R[" & 1 - lSize & "]C[-1]:RC[-1])/MovingAverageSize,0)" End If Next Application.EnableEvents = True Set oRngData = Nothing End If End If End Sub 

示例数据和屏幕截图
样本数据 SampleData2
SampleData3 SampleData4 SampleData5 SampleData6

我相信Application.ActiveCell不是你应该在这里使用的。 Application.ThisCell会更合适,假设“a”是子集的大小,而数据集是右边的1列。 此外,我只是简单地使用“WorksheetFunction.Average”,而不是“Application.Sum”,我会添加“Application.Volatile”,所以无论何时在工作表上发生更新时都会重新计算平均值。

所以你的问题的一个解决scheme是:

 Public Function Trial1(a As Integer) As Variant Application.Volatile Trial1 = WorksheetFunction.Average(Application.ThisCell(1, 2).Resize(a)) End Function 

这里的另一个解决scheme是使用通过Control / Shift / Enterinput的数组公式:

 Public Function MovAvg(dataset As Range, subsetSize As Integer) Dim result(), subset As Range, i As Long ReDim result(1 To dataset.Rows.count, 1 To 1) Set subset = dataset.Resize(subsetSize) For i = 1 To dataset.Rows.count result(i, 1) = WorksheetFunction.Average(subset.offset(i - 1)) Next MovAvg = result End Function 

并使用这个数组函数:

  • select所有结果将被写入的范围(应该是数据集的大小)
  • 键入“= MovAvg(A1:A100,2)”其中A1:A100是数据的来源,2是子集的大小
  • 按下Ctrl + Shift + Enter

当UDF作为parameter passing时,应该只能访问一个范围。 此外,您应该消除Application.Volatile,因为(1)您的计算是确定性的而不是易失性的,(2)每当input范围内的任何单元发生更改时,Excel将自动重新计算您的UDF;(3)在一个UDF可以使一个模型非常缓慢,所以它应该避免不必要的时候。 所以,对于移动平均数,正确的公式是:

 Public Function SpecialMovingAverage(Rng as Excel.Range) As Double Dim denominator as Integer denominator = Rng.Cells.Count if Denominator = 0 then SpecialMovingAverage = 0: exit function ' write your special moving average logic below SpecialMovingAverage = WorksheetFunction.Average(Rng) End Function 

注意:我在两条评论之后改变了答案,因为我最初并没有看到问题出现在移动平均线之后(也许这个问题在我的回答之后发生了变化,或者我最初错过了UDF的陈述目标)。

我相信

  1. 您的trial1()函数位于一个或多个单元格中,作为公式的一部分或由其本身

  2. 您希望每当用户更改工作表上的任何单元格时重新计算这些单元格

为此,您需要确定发生更改的单元格。 这个单元格不是给出的

A. ActiveCell – 因为这是计算开始时光标所在的单元格; 它可能在任何地方,但不是在被更改的单元格上

B. Application.ThisCell – 因为它返回从中调用用户定义的函数的单元,而不是被改变的单元

发生更改的单元格被传递给工作表的更改事件。 该事件由Rangetypes的参数触发 – 范围已更改。 你可以使用这个参数来识别更改的单元格,并将其传递给trial1(),可能通过全局variables(是的,我知道)。

我在一个工作表中试过这个,所以让我知道你的结果。