假设我创建了一个Sub(不是函数),其生命中的任务是获取活动单元格(即选择)并将相邻单元格设置为某个值。这很好。
当您尝试将该Sub转换为函数并尝试从电子表格中对其进行评估时(即将其公式设置为“= MyFunction()”)Excel会嘲笑您试图影响其值的事实非活动单元格,只需强制函数返回#VALUE而不触及相邻单元格。
是否可以关闭此保护行为?如果没有,有什么好办法绕过它?如果可能的话,我正在寻找有能力的开发人员在1-2周内完成的事情。
此致 艾伦。
注意:我正在使用2002,所以我倾向于使用适用于该版本的解决方案。话虽如此,如果未来的版本使这个变得更容易,我也想了解它。
答案 0 :(得分:9)
这是不可能的,这是有道理的,因为:
调用工作表函数时,包含该函数的单元格不一定是活动单元格。因此,您无法可靠地找到相邻的单元格。
当Excel重新计算工作表时,它需要维护单元格之间的依赖关系。因此,它不允许工作表函数任意修改其他单元格。
你能做的最好的事情之一是:
处理SheetChange事件。如果包含您的功能的单元格正在更改,请修改相邻单元格。
在相邻单元格中放置工作表函数以返回所需的值。
<强>更新强>
关于评论:“我希望此功能能够处理'空白'电子表格,因此我不能真正依赖可能尚不存在的电子表格的SelectionChange事件,但需要调用此函数” :
关于评论:“不过,如果你将Excel保存在CalculationMode = xlManual并只填写值,你应该没问题”
(*)我最初在上面写了SelectionChange,现在更正了 - 当然正确的事件是SheetChange用于工作簿或应用程序对象,或者更改为工作表对象。
更新2 关于AlanR's post的一些评论描述了如何使用计时器“有点”使其工作:
目前尚不清楚定时器功能(“Woohoo”)如何知道要更新的单元格。您没有任何信息表明哪个单元格包含触发计时器的公式。
如果公式存在于多个单元格中(在相同或不同的工作簿中),则在重新计算期间将多次调用UDF,覆盖timerId。因此,您将无法可靠地销毁计时器,并且会泄漏Windows资源。
答案 1 :(得分:2)
我正在使用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
答案 2 :(得分:2)
根据How to Create Custom User Defined Excel Functions:
UDF的限制
- 无法在包含的单元格(或范围)以外的单元格中放置值 公式。换句话说,UDF是 意思是用作“公式”而不是 必然是“宏”。
所以,看起来它无法完成。
答案 3 :(得分:1)
谢谢大家的回复。有可能做到这一点!均田。我说'有点',因为从技术上讲,'功能'不会影响它周围的细胞。然而,实际上,没有用户可以区分它们。
诀窍是使用Win32 API启动一个计时器,一旦它关闭,你就可以对任何单元格做你想做的事情并关掉计时器。
现在我不是COM线程如何工作的专家(虽然我知道VBA是Single Apartment Threaded),但是要小心你的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
答案 4 :(得分:1)
虽然你无法在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。
答案 5 :(得分:1)
这是一个简单的VBA解决方法。对于此示例,请打开一个新的Excel工作簿,并将以下代码复制到Sheet1
(不是ThisWorkbook
或VBA 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
子例程捕获工作表中的所有单元格更改事件。如果行和列都是&lt; = 10,那么如果值是数字,则右边的单元格被设置为已更改单元格的4倍;否则清除右边的单元格。