【问题标题】:Macro launching when a cell value changes due to a formula not by the user当单元格值由于不是用户的公式而发生变化时启动宏
【发布时间】:2015-02-17 13:42:26
【问题描述】:

我希望我的宏在包含公式的单元格中的值发生变化时启动。 即用户正在修改另一个单元格,从而改变了相关单元格的值。

我注意到,使用该语句(在此处找到)仅在用户修改单元格本身时才有效,但在单元格自动更改时无效 - 由于上面指定的公式。

Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A20")) Is Nothing Then ...

有什么想法吗??

我尝试按照这个问题“automatically execute an Excel macro on a cell change”的答案进行操作,但没有成功...

提前致谢:)

【问题讨论】:

    标签: vba excel


    【解决方案1】:

    一种可能的解决方法来自这样一个事实,即要更改一个值,用户需要先更改选择。所以我会:

    1) 在 WS 源代码模块之上声明一个名为“oldValue”的全局变量:

    Dim oldValue As Variant
    

    2) 在用户输入任何内容之前注册公式的旧值(假设它在 Range("A4") 中,我让你适应其他人):

    Private Sub Worksheet_SelectionChange(ByVal Target As Range)
        oldValue = Range("A4")
    End Sub
    

    3) 检查更改是否影响了 Change 事件中的公式:

    Private Sub Worksheet_Change(ByVal Target As Range)
        If Range("A4") <> oldValue Then
            MsgBox "User action has affected your formula"
        End If
    End Sub
    

    我已经用一个简单的总和进行了测试,我可以在没有任何提示的情况下编写不涉及的单元格,但是如果我触摸总和中涉及的单元格之一,MsgBox 就会出现。我让您适应多种情况,用于用户添加/删除行(在这种情况下,我建议命名包含您要跟踪的公式的范围)和工作表引用。

    编辑 我想一次完成,而不是通过 2 个过程,可以吗?问题是我的宏涉及一个包含多个单元格的范围,因此很难存储 10 个单元格的旧值。

    如果范围彼此相邻,则可以使用集合代替变量:

    Dim oldValues As New Collection
    Private Sub Worksheet_SelectionChange(ByVal Target As Range)
        For j = 1 To 10
            oldValues.Add Range("A" & j).Value
        Next j
    End Sub
    Private Sub Worksheet_Change(ByVal Target As Range)
        For j = 1 To 10
            If Range("A" & j).Value <> oldValues(j) Then
                MsgBox "The value of Range(A" & j & ") has changed"
            End If
        Next j
    End Sub
    

    当然,如果范围彼此不接近,您可以将它们存储在 SelectionChange 事件中,如下所示:

    oldValues.Add Range("A1").Value
    oldValues.Add Range("B7").Value
    '...
    

    如果您这样做了一次,只有 10 个范围,它应该是您问题的合理解决方案。

    【讨论】:

    • 我想一次完成,而不是通过2个过程,可以吗?问题是我的宏涉及一个包含多个单元格的范围,因此很难存储 10 个单元格的旧值...
    • 范围是否相邻?例如,范围(“A1:A10”)?附: 10(十)个细胞听起来很难?? :)
    • 附言。如果要在更改发生之前注册范围的值,则需要使用两个宏。它们是两个独立的事件,不能同时进行。
    • 是的,它们彼此相邻,我已经找到了一种方法,但它很长,如果你有更大范围的单元格怎么办?顺便说一句,没必要脾气暴躁,我是 VBA 新手,我不是程序员,如果我是专家,我不会问问题。无论如何感谢您的帮助。
    • @CynthiaKreidy,我不是脾气暴躁,我是在讽刺,因为你可能会发现自己需要存储超过 10 个变量;)​​我已经对我的答案进行了编辑,现在应该没事的。只要范围是连续的,你甚至可以用 3 行代码记录其中的 100000 个。
    【解决方案2】:

    您说:“我希望我的宏在包含公式的单元格中的值发生变化时启动...”

    如果在重新计算包含公式的单元格时运行您的代码(这不是您所要求的),一种解决方案可能是创建一个 VBA 函数,该函数仅返回传递给的值它,当重新计算公式时,plus 会做任何你想做的事情......

    Public Function Hook(ByVal vValue As Variant) As Variant
        Hook = vValue
    
        ' Add your code here...
    End Function
    

    ...然后在调用此函数时“包装”您的公式。例如,如果您感兴趣的公式是=A1+1,则将其更改为=Hook(A1+1),并且每当重新计算A1+1 时(例如,当A1 中的值变化)。但是,重新计算 A1+1 可能会产生相同的结果并仍然调用 Hook 函数(例如,如果用户在 A1 中重新输入相同的值)。

    【讨论】:

    • 布赖恩这主意不错! +1。但是,这将在任何“重新计算”事件(例如打开工作簿、计算操作等)中调用。你有什么想法来管理这个并完成你的答案吗?
    【解决方案3】:

    你可以试试这个:

    首先,在模块代码中声明一个公共变量

    Public r As Range, myVal '<~~ Place it in Module
    

    其次,在Workbook_Open事件中初始化你的变量。

    Private Sub Workbook_Open()
        Set r = Sheet1.Range("C2:C3") '<~~ Change to your actual sheet and range
        myVal = Application.Transpose(r)
    End Sub
    

    最后,设置您的 Worksheet_Calculate 事件。

    Private Sub Worksheet_Calculate()
        On Error GoTo halt
        With Application
            .EnableEvents = False
            If Join(myVal) <> Join(.Transpose(r)) Then
                MsgBox "Something changed in your range"
                '~~> You put your cool stuff here
            End If
            myVal = .Transpose(r)
    forward:
            .EnableEvents = True
        End With
        Exit Sub
    halt:
        MsgBox "Error " & Err.Number & ": " & Err.Description
        Resume forward
    End Sub
    

    当 C2:C3 中的值发生变化时,以上将触发事件。
    不是很整洁,但可以检测目标范围的变化。 HTH。

    【讨论】:

      【解决方案4】:

      像 Matteo 描述的那样声明一个模块级变量绝对是一种好方法。

      Brian 的答案是在正确的轨道上将所有代码保存在同一个地方,但它缺少一个关键部分:Application.Caller

      在单个单元格调用的函数中使用时,Application.Caller 将返回该单元格的 Range 对象。这样,您可以在调用函数时将旧值存储在函数本身中,然后在计算完新值后,您可以将其与旧值进行比较,并根据需要运行更多代码。

      编辑:Application.Caller 的优点是解决方案本身可以扩展,并且无论目标单元格如何排列(即连续与否)都不会改变。

      【讨论】:

      • 你的回答让我很感兴趣,所以我试了一下。但是,我发现在函数中引用单元格的当前值(通过Application.Caller.Value)创建了一个循环引用,这是有道理的。我发现了一些可能的替代方案here 的讨论,但它们要么脆弱,要么复杂。我建议的简单方法的“误报”成本需要很高才能证明这些替代方案的合理性。
      • 哈!我不知道:您可以毫无问题地引用 Application.Caller,但是使用 Application.Caller.Value 获取它的值是触发循环引用的原因。确实,似乎没有很好的解决方法,所以我的回答不成立。在这种情况下,我会选择 Matteo 的答案并声明一个模块级变量。也许不像您希望的那样“简单”,但我们必须克服系统的限制:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-06
      • 2015-12-05
      • 1970-01-01
      • 2012-03-07
      相关资源
      最近更新 更多