【问题标题】:Making Excel functions affect 'other' cells使 Excel 函数影响“其他”单元格
【发布时间】:2010-09-17 02:33:59
【问题描述】:

假设我创建了一个 Sub(不是函数),其生命任务是获取活动单元格(即选择)并将相邻单元格设置为某个值。这很好用。

当您尝试将该 Sub 转换为函数并尝试从电子表格中对其进行评估(即将其公式设置为“=MyFunction()”)时,Excel 会因为您试图影响非活动单元格,并简单地强制函数返回 #VALUE 而不会触及相邻的单元格。

是否可以关闭这种保护行为?如果没有,有什么好办法绕过它?如果可能的话,我正在寻找一个称职的开发人员可以在 1-2 周内完成的工作。

问候, 艾伦。

注意:我使用的是 2002,所以我更喜欢适用于该版本的解决方案。话虽如此,如果未来的版本让这变得更容易,我也想知道。

【问题讨论】:

标签: excel formula vba


【解决方案1】:

我使用的是 Excel 2007,但它不起作用。 Excel 提到它会创建一个循环引用。我认为您不能从函数中更改其他单元格,而只能返回一个值。

这是一种函数式编程,没有副作用。如果您可以只更改函数内的其他单元格(从工作表中使用),那么 Excel 就无法知道单元格更改时的顺序以及重新计算的内容。

This article 还包含大量有关 Excel 如何重新计算的信息。但它从来没有说其他细胞是冷冻的。

我不知道你想做什么,但是,你为什么不在相邻的单元格中放置另一个函数,它将第一个单元格作为参数?

例子:

Public Function Bar(r As Range) As Integer
  If r.Value = 2 Then
    Bar = 0
  Else
    Bar = 128
  End If
End Function

【讨论】:

  • 1) 这是可行的。我见过可以做到这一点的插件,例如 Bloomberg 插件。 2)我正在尝试以编程方式设置相邻单元格的值。
【解决方案2】:

根据How to Create Custom User Defined Excel Functions

UDF 的局限性

  • 不能在单元格(或范围)以外的单元格中放置值 公式。换句话说,UDF 是 旨在用作“公式”,而不是 必然是“宏”。

所以,看来做不到了。

【讨论】:

    【解决方案3】:

    做不到,这是有道理的,因为:

    • 调用工作表函数时,包含该函数的单元格不一定是活动单元格。所以你无法可靠地找到相邻的单元格。

    • 当 Excel 重新计算工作表时,它需要维护单元格之间的依赖关系。所以它不能让工作表函数随意修改其他单元格。

    您可以做的最好的事情之一是:

    • 处理 SheetChange 事件。如果包含您的函数的单元格发生变化,请修改相邻的单元格。

    • 在相邻单元格中放一个工作表函数,返回你想要的值。

    更新

    关于评论:“我希望这个函数在‘空白’电子表格上工作,所以我不能真正依赖可能尚不存在但需要调用此函数的电子表格的 SelectionChange 事件” :

    • 能否将函数放入 XLA 插件中?那么您的 XLA 加载项可以处理在该 Excel 实例中打开的所有工作簿的 Application SheetChange (*) 事件吗?

    关于评论:“不过,如果您将 Excel 保持在 CalculationMode = xlManual 并仅填写值,您应该没问题”

    • 即使 CalculationMode 为 xlManual,Excel 也需要维护单元格之间引用的依赖关系树,以便它可以按正确的顺序进行计算。如果其中一个函数可以更新任意单元格,这将打乱顺序。这大概就是 Excel 施加此限制的原因。

    (*) 我最初在上面写了 SelectionChange,现在更正了 - 当然,正确的事件是 Workbook 或 Application 对象的 SheetChange,或 Worksheet 对象的 Change。

    更新 2 AlanR's post 上的一些评论描述了如何“有点”使用计时器使其工作:

    • 尚不清楚计时器函数(“Woohoo”)如何知道要更新哪些单元格。您没有任何信息表明哪个单元格包含触发计时器的公式。

    • 如果公式存在于多个单元格中(在相同或不同的工作簿中),则在重新计算期间将多次调用 UDF,覆盖 timerId。结果,您将无法可靠地销毁计时器,并会泄漏 Windows 资源。

    【讨论】:

    • 我希望这个函数在“空白”电子表格上工作,所以我不能真正依赖可能尚不存在但需要调用此函数的电子表格的 SelectionChange 事件。
    • 我确实明白您所说的,如果您尝试在相邻单元格中输入公式导致循环引用等,重新计算电子表格可能会导致严重问题。不过,如果您将 Excel 保持在 CalculationMode = xlManual 并仅填写值,则应该没问题。
    • 再次感谢乔。我想你会被我的最新帖子逗乐的 :)
    • 乔,这些都是好点。我这有点超出了问题的范围,但我正在创建(然后销毁)范围名称来指示 Woohoo 函数将输出放在哪里。
    • 关于压倒Excel的计时器——你是对的!我遇到了这个。一旦我保存了所有活动 TimerIds 的运行列表(一个简单的集合),我就让 Woohoo 函数销毁所有活动的计时器。已经 24 小时了,电子表格运行得非常顺利:)。绝对是雷区!
    【解决方案4】:

    感谢大家的回复。有可能做到这一点!有点。我说“有点”是因为从技术上讲,“功能”不会影响它周围的细胞。然而,实际上,没有用户能分辨出其中的区别。

    诀窍是使用 Win32 API 来启动计时器,一旦它启动,您就可以对任何单元执行您想要的操作并关闭计时器。

    现在我不是 COM 线程如何工作的专家(虽然我知道 VBA 是单单元线程),但要小心你的 Timer 与 Excel 进程一起跑掉并导致它崩溃。这真的不是我建议的所有其他电子表格的解决方案。

    只需使用以下内容制作一个模块:

    Option Explicit
    
    Declare Function SetTimer Lib "user32" (ByVal HWnd As Long, _
      ByVal IDEvent As Long, ByVal mSec As Long, _
      ByVal CallFunc As Long) As Long
    
    Declare Function KillTimer Lib "user32" (ByVal HWnd As Long, _
      ByVal timerId As Long) As Long
    
    Private timerId As Long
    
    Private wb As Workbook
    Private rangeName As String
    Private blnFinished As Boolean
    
    Public Sub RunTimer()
    
        timerId = SetTimer(0, 0, 10, AddressOf Woohoo)
    
    
    End Sub
    
    
    Public Sub Woohoo()
    
        Dim i As Integer
    
    '    For i = 0 To ThisWorkbook.Names.Count - 1
    '        ThisWorkbook.Names(i).Delete
    '    Next
    
         ThisWorkbook.Worksheets("Sheet1").Range("D8").Value = "Woohoo"
    
         KillTimer 0, timerId
    
    End Sub
    

    【讨论】:

    • 上面的“10”表示在调用 RunTimer 后 10 毫秒启动 Woohoo sub。 RunTimer 应该是你的函数调用的最后一件事。
    • 这很好。我会将 KillTimer 放在“Woohoo”的开头,以保证即使引发错误,计时器也会被销毁。但是你怎么知道哪个单元格包含你的函数,以便找到相邻的单元格?它不一定是活动单元格。
    • 对此进行了更多思考,并在我的原始帖子中添加了一些评论。
    【解决方案5】:

    虽然您不能在 Excel 中执行此操作,但可以在 Resolver One 中执行此操作(尽管这样做仍然很奇怪)。

    这是一个电子表格,允许您在 Python 中定义自定义函数,然后您可以从网格中的单元格公式调用这些函数。

    作为您所问问题的一个示例,您可能想要定义一个 safeDivide 函数(而不是引发 ZeroDivisionError)通过着色分母单元格并在旁边放置一条错误消息来告诉您问题它。你可以这样定义:

    def safeDivide(numerator, cellRange):
        if not isinstance(cellRange, CellRange):
            raise ValueError('denominator must be a cell range')
        denominator = cellRange.Value
        if denominator == 0:
            cell = cellRange.TopLeft
            cell.BackColor = Color.Red
            cell.Offset(1, 0).Value = 'Tried to divide by zero'
            return 0
        return numerator / denominator
    

    还有一个额外的问题:传递单元格的函数只会传递单元格值,因此为了解决这个问题,我们坚持传递一个单元格单元格范围作为分母。

    如果您尝试使用不太适合 Excel 的电子表格执行不寻常的操作,或者您有兴趣使用 Python 的强大功能来处理电子表格数据,那么值得一看 Resolver One。

    【讨论】:

      【解决方案6】:

      这是一个有效的简单 VBA 解决方法。对于此示例,打开一个新的 Excel 工作簿并将以下代码复制到 Sheet1 的代码区域(不是 ThisWorkbook 或 VBA Module)。然后进入Sheet1 并将某些内容放入工作表左上角的单元格之一。如果你输入一个数字并回车,那么右边的单元格将被更新为数字的 4 倍,并且单元格背景将变为浅蓝色。任何其他值都会导致下一个单元格被清除。代码如下:

      Dim busy As Boolean
      Private Sub Worksheet_Change(ByVal Target As Range)
        If busy Then Exit Sub
        busy = True
        If Target.Row <= 10 And Target.Column <= 10 Then
          With Target.Offset(0, 1)
            If IsNumeric(Target) Then
              .Value = Target * 4
              .Interior.Color = RGB(212, 212, 255)
            Else
              .Value = Empty
              .Interior.ColorIndex = xlColorIndexNone
            End If
          End With
        End If
        busy = False
      End Sub
      

      子例程捕获工作表中的所有单元格更改事件。如果行和列都

      【讨论】:

        猜你喜欢
        • 2019-04-11
        • 2016-06-17
        • 1970-01-01
        • 2014-09-06
        • 2019-07-21
        • 2022-10-25
        • 1970-01-01
        • 2023-03-31
        • 2014-12-01
        相关资源
        最近更新 更多