【问题标题】:UDF returns the same value everywhereUDF 在任何地方都返回相同的值
【发布时间】:2016-06-24 22:54:51
【问题描述】:

我正在尝试在 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

【问题讨论】:

  • 那么这里的问题是什么?
  • 我的代码似乎不起作用。它为所有单元格返回相同的值。如果我删除 application.volatile 行,我必须手动按 F2 并输入每个单元格以获得正确答案。我认为原因是该函数无法识别动态范围的变化,但不知道如何修复它。
  • 我对它进行了测试,它并没有在任何地方返回相同的值,它似乎做了它应该做的事情。您的问题是我们不确切知道您想要什么,我们应该参考您的代码以了解您在做什么。但你的代码可能是错误的。
  • 据我所见:1. 你选择一个单元格,触发调用函数 Trial1 的宏并传递一个整数值,第一个问题:这个值是如何固定的?您是对其进行硬编码还是从另一个单元格或用户输入中获取它? 2. 在函数中,您创建一个固定在列 B 上的单元格范围,范围的底部是所选单元格所在的行,顶部是range 是选定单元格的行减去传递给您的函数的整数值加 1。这一切都正确吗?

标签: vba excel with-statement udf


【解决方案1】:

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)

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

【讨论】:

  • 只是想知道你为什么在你回答过的另一个人的问题上悬赏,这很奇怪:-D
  • 我也做了同样的事情here 并且反应很好,足以将另一个标记为“已接受的答案”。这需要更多曝光;它浪费了 2 天,这个话题很好,值得一个很好的答案。 (此外,我有很多代表,没有太多其他关系)
  • 我倾向于单独使用Application.Volatile False 而不是Application.Volatile。我什至不知道您使用的语法会起作用!同时,您关于 ActiveCell 属性的观点是有效的,并且是对这个特殊问题的任何答案中最有用的部分:我几乎没有什么要补充的。
【解决方案2】:

UDF 计算给定数字的移动平均线对我来说有点奇怪。如果要在 Worksheet 中使用此 UDF,我相信您会将其放在现有数据旁边,如果您想更改平均金额范围的大小,您可以手动更新它们?

假设您可以命名一个 Range "MovingAverageSize" 来存储范围的大小以计算平均值,以及现有数据右侧的平均数量,请考虑如下:

  • 范围C2被命名为MovingAverageSize
  • 从 B3 及以下存储的数据
  • 移动平均结果存储在数据右侧 1 列
  • 如果数据小于MovingAverageSizeSUM函数会相应调整
  • 出现任何计算错误,结果为零
  • 每次 MovingAverageSize 改变值时,都会触发 Sub 更新公式(代码放在 Worksheet 对象中,而不是普通的 Module 中)
  • 或者,您可以更改代码以将 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

样本数据和截图

【讨论】:

  • 分析得很透彻;介绍的方法非常有效。
【解决方案3】:

我相信 Application.ActiveCell 不是您应该在这里使用的。 假设“a”是子集的大小并且数据集是右侧的 1 列,Application.ThisCell 会更合适。 此外,我将简单地使用“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

并使用这个数组函数:

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

【讨论】:

【解决方案4】:

UDF 只能在作为参数传递时访问范围。 此外,您应该消除 Application.Volatile,因为 (1) 您的计算是确定性的而不是易失性的,(2) 每当输入范围内的任何单元格发生变化时,Excel 都会自动重新计算您的 UDF,以及 (3) 因为 'volatile' 属性在 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

注意:我在两个 cmets 之后更改了答案,因为我最初没有看到问题是在移动平均线之后(可能是在我回答之后问题发生了变化,或者我最初错过了 UDF 的既定目标)。

【讨论】:

  • 你提出了一些好的观点;尤其是您如何强调这种 UDF 应该将要处理的范围作为参数传递。
  • 您可能需要重新检查您的公式,结果对于子集 > 2 不正确。您还应该删除一个输入范围,一个就足够了。此外,虽然仅将提供的范围与 UDF 一起使用是一种很好的做法,但在某些情况下是必要的,例如,如果此处的目的是计算加权移动平均线。
【解决方案5】:

我相信

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

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

为此,您需要确定发生更改的单元格。该单元格不是由

给出的

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

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

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

我在工作表中进行了尝试,它有效,所以请告诉我你的结果。

【讨论】:

  • 很好的总结,有新的见解和建议的解决方案。
猜你喜欢
  • 2013-04-27
  • 1970-01-01
  • 2014-03-06
  • 1970-01-01
  • 2013-04-08
  • 2013-03-04
  • 1970-01-01
  • 1970-01-01
  • 2020-09-30
相关资源
最近更新 更多