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

时间:2016-03-10 07:58:10

标签: excel vba excel-vba with-statement 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

5 个答案:

答案 0 :(得分:6)

ActiveCell property不属于UDF,因为会更改。有时,它甚至不在同一个工作表上。

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

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 property

使用Excel Application object返回SUM function的结果和一些数学计算平均值。使用工作表的AVERAGE function可以在一个命令中返回相同的内容,但空白单元格的处理方式会不同。

        trial1 = Application.Average(rng)

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

答案 1 :(得分:3)

我相信Application.ActiveCell不是你应该在这里使用的。 Application.ThisCell更适合假设" a"是子集的大小,数据集是右侧的1列。 而且,我只想使用" WorksheetFunction.Average"而不是" Application.Sum"我会添加" Application.Volatile"因此,只要工作表上发生更新,就会重新计算平均值。

因此,您的问题的一个解决方案是:

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

此处的另一个解决方案是使用通过Control / Shift / Enter输入的数组公式:

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

并使用此数组函数:

  • 选择将写入所有结果的范围(应该是数据集的大小)
  • Type" = MovAvg(A1:A100,2)"其中A1:A100是数据的来源,2是子集的大小
  • 按Ctrl + Shift + Enter

答案 2 :(得分:3)

对于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

示例数据和截图
SampleData SampleData2
SampleData3 SampleData4 SampleData5 SampleData6

答案 3 :(得分:2)

UDF只应在作为参数传递时访问范围。 此外,您应该消除Application.Volatile,因为(1)您的计算是确定性的而且不易变,(2)只要输入范围中的任何单元格发生变化,Excel将自动重新计算您的UDF,以及(3)因为&#39;挥发性&#39; 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的陈述目标)。

答案 4 :(得分:1)

我相信

  1. 您的trial1()函数位于一个或多个单元格中,作为公式的一部分或单独使用

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

  3. 为此,您需要确定发生更改的单元格。

    没有给出这个单元格

    一个。 ActiveCell - 因为这是计算开始时光标所在的单元格;它可能在任何地方,但不在改变的细胞上

    B中。 Application.ThisCell - 因为它返回调用用户定义函数的单元格,而不是更改的单元格

    发生更改的单元格将传递给Worksheet的Change事件。该事件由Range类型的参数触发 - 更改的范围。您可以使用该参数来识别已更改的单元格,并将其传递给trial1(),可能通过全局变量(是的,我知道)。

    我在工作表中尝试了这个并且它有效,所以让我知道你的结果。