4

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

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

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

问候,艾伦。

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

4

6 回答 6

9

它不能完成,这是有道理的,因为:

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

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

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

  • 处理 SheetChange 事件。如果包含您的函数的单元格正在更改,请修改相邻的单元格。

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

更新

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

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

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

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

(*) 我最初在上面写了 SelectionChange,现在更正了 - 当然,正确的事件是工作簿或应用程序对象的 SheetChange,或者工作表对象的 Change。

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

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

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

于 2008-10-14T16:42:33.933 回答
2

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

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

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

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

例子:

Public Function Bar(r As Range) As Integer
  If r.Value = 2 Then
    Bar = 0
  Else
    Bar = 128
  End If
End Function
于 2008-10-14T15:05:41.633 回答
2

根据如何创建自定义用户定义的 Excel 函数

UDF 的局限性

  • 不能在包含公式的单元格(或区域)以外的单元格中放置值。换句话说,UDF 旨在用作“公式”,而不一定是“宏”。

所以,看起来是做不到的。

于 2008-10-14T16:09:01.863 回答
1

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

诀窍是使用 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
于 2008-10-16T13:37:02.747 回答
1

虽然您不能在 Excel 中执行此操作,但在Resolver One中是可能的(尽管这仍然是一件很奇怪的事情)。

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

作为您所要求的示例,您可能想要定义一个safeDivide函数(而不是引发 a 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。

于 2008-11-27T17:07:50.727 回答
1

这是一个简单的 VBA 解决方法。对于此示例,打开一个新的 Excel 工作簿并将以下代码复制到Sheet1(不是ThisWorkbookVBA Module)的代码区域中。然后进入Sheet1并将一些东西放入工作表左上角的单元格之一。如果您输入一个数字并按 Enter,则右侧的单元格将更新为该数字的 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

该子例程捕获工作表中的所有单元格更改事件。如果行和列都<= 10,则右侧的单元格设置为更改后的单元格的4倍(如果值为数字);否则右边的单元格被清除。

于 2009-01-06T22:51:40.640 回答