我不希望我的 Excel 加载项返回数组(相反,我需要 UDF 来更改其他单元格)

I don't want my Excel Add-In to return an array (instead I need a UDF to change other cells)

本文关键字:UDF 单元格 其他 相反 我的 希望 Excel 加载项 数组 返回      更新时间:2023-10-16

我创建了一个Excel外接程序,这个外接程序的一个函数,让我们说New_Years目前需要2年的时间,并在这2年之间的每个新年日作为Excel中的数组输出。因此,New_Years(2000,2002)将返回2000年1月1日、2001年1月1日和2002年1月1日的最后一个单元格。

问题是我必须知道在那个时间里有3个日期,选择3个单元格,在最上面的单元格中输入我的公式,然后按Ctrl + Shift + Enter填充数组。

我使用XLW版本5将我的c++代码转换为.xll文件。我真的很希望,如果有某种方法,我可以用我的公式填写一个方形,Excel会在需要的时候用适当的日期填写下面的方形。有人知道这是可能的吗?还是不可能?

多谢!

这实际上是可能的,尽管很复杂。我重新发布了Kevin Jones aka Zorvek的这段魔法,因为它位于EE付费墙后面(如果有人有访问权限,请附上链接)

虽然Excel严格禁止UDF更改任何单元格,工作表,或工作簿属性时,有一种方法可以影响此类更改UDF是使用Windows计时器和应用程序调用的。OnTime定时器序列。Windows计时器必须在UDF中使用,因为Excel忽略任何应用程序。OnTime在UDF内调用。但是,因为Windows定时器有限制(Excel会立即退出,如果一个Windows计时器尝试运行VBA代码,如果正在编辑单元格或对话框打开),它仅用于调度应用程序。定时一个安全的计时器,Excel只允许在单元格被激活时触发未编辑且未打开对话框

下面的示例代码演示了如何从启动Windows计时器在UDF中,如何使用计时器例程启动一个应用程序。OnTime定时器,以及如何传递只有已知的信息将UDF传递给随后的定时器执行的例程。下面的代码必须是

Private Declare Function SetTimer Lib "user32" ( _
      ByVal HWnd As Long, _
      ByVal nIDEvent As Long, _
      ByVal uElapse As Long, _
      ByVal lpTimerFunc As Long _
   ) As Long
Private Declare Function KillTimer Lib "user32" ( _
      ByVal HWnd As Long, _
      ByVal nIDEvent As Long _
   ) As Long
Private mCalculatedCells As Collection
Private mWindowsTimerID As Long
Private mApplicationTimerTime As Date
Public Function AddTwoNumbers( _
      ByVal Value1 As Double, _
      ByVal Value2 As Double _
   ) As Double
' This is a UDF that returns the sum of two numbers and starts a windows timer
' that starts a second Appliction.OnTime timer that performs activities not
' allowed in a UDF. Do not make this UDF volatile, pass any volatile functions
' to it, or pass any cells containing volatile formulas/functions or
' uncontrolled looping will start.
   AddTwoNumbers = Value1 + Value2
   ' Cache the caller's reference so it can be dealt with in a non-UDF routine
   If mCalculatedCells Is Nothing Then Set mCalculatedCells = New Collection
   On Error Resume Next
   mCalculatedCells.Add Application.Caller, Application.Caller.Address
   On Error GoTo 0
   ' Setting/resetting the timer should be the last action taken in the UDF
   If mWindowsTimerID <> 0 Then KillTimer 0&, mWindowsTimerID
   mWindowsTimerID = SetTimer(0&, 0&, 1, AddressOf AfterUDFRoutine1)
End Function
Public Sub AfterUDFRoutine1()
' This is the first of two timer routines. This one is called by the Windows
' timer. Since a Windows timer cannot run code if a cell is being edited or a
' dialog is open this routine schedules a second safe timer using
' Application.OnTime which is ignored in a UDF.
   ' Stop the Windows timer
   On Error Resume Next
   KillTimer 0&, mWindowsTimerID
   On Error GoTo 0
   mWindowsTimerID = 0
   ' Cancel any previous OnTime timers
   If mApplicationTimerTime <> 0 Then
      On Error Resume Next
      Application.OnTime mApplicationTimerTime, "AfterUDFRoutine2", , False
      On Error GoTo 0
   End If
   ' Schedule timer
   mApplicationTimerTime = Now
   Application.OnTime mApplicationTimerTime, "AfterUDFRoutine2"
End Sub
Public Sub AfterUDFRoutine2()
' This is the second of two timer routines. Because this timer routine is
' triggered by Application.OnTime it is safe, i.e., Excel will not allow the
' timer to fire unless the environment is safe (no open model dialogs or cell
' being edited).
   Dim Cell As Range
   ' Do tasks not allowed in a UDF...
   Application.ScreenUpdating = False
   Application.Calculation = xlCalculationManual
   Do While mCalculatedCells.Count > 0
      Set Cell = mCalculatedCells(1)
      mCalculatedCells.Remove 1
      Cell.Offset(0, 1).Value = Cell.Value
   Loop
   Application.Calculation = xlCalculationAutomatic
   Application.ScreenUpdating = True
   End Sub

多亏了本页早些时候的文章,我才能够创建一种易于使用的模块。


''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' POST UDF UPDATING MODULE                                   v.9.2020 cdf
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Since Excel won't allow UDFs to update any other cells,
'   an API timer is used to trigger a post UDF subroutine.
'
' This acts like a recalculate.  Your code should recalculate the entire
'   sheet.  Tried to get it to work with a specific range, but, with all
'   the different update variations ({Enter}, {Tab}, {Backspace}, {Delete},
'   mouse click in the middle of an update, etc.), no luck. Any ideas?
'
' Originally, before the slight tweak, the code was found here:
'   https://stackoverflow.com/questions/8520732/
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'                             HOW TO USE
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' Create a module (in the Modules folder) and copy this entire document 
'   into it.
'
'
' Code this code into your UDF's subroutine in your module (in Modules
'   folder) (should be close to the end of the code):
'
'     ' Use Post UDF Timer to update other cells.
'     Call TimerModulesName.SetAPITimer("UDFModule.PostUDFRoutine")
'
'
' Edit the code names:
'
'     Change TimerModulesName to the module you copied this document into.
'            UDFModule        to the module your post UDF subroutine is in.
'            PostUDFRoutine   to your post UDF subroutine.
'
'     Example: Module4.SetAPITimer("Module1.UpdateMyCells")
'
'
#If VBA7 Then
Private Declare PtrSafe Function SetTimer Lib "user32" ( _
      ByVal hwnd As Long, _
      ByVal nIDEvent As Long, _
      ByVal uElapse As Long, _
      ByVal lpTimerFunc As LongLong _
   ) As Long
Private Declare PtrSafe Function KillTimer Lib "user32" ( _
      ByVal hwnd As Long, _
      ByVal nIDEvent As Long _
   ) As Long
   
#Else
Private Declare Function SetTimer Lib "user32" ( _
      ByVal HWnd As Long, _
      ByVal nIDEvent As Long, _
      ByVal uElapse As Long, _
      ByVal lpTimerFunc As Long _
   ) As Long
Private Declare Function KillTimer Lib "user32" ( _
      ByVal HWnd As Long, _
      ByVal nIDEvent As Long _
   ) As Long
   
#End If
Private mCalculatedCells As Collection
Private mWindowsTimerID As Long
Private mApplicationTimerTime As Date
Private mRoutine As String
Public Sub SetAPITimer(sRoutine As String)
    
' Starts a windows timer that starts a second Appliction.OnTime
' timer that performs activities not allowed in a UDF. Do
' not make this UDF volatile, pass any volatile functions
' to it, or pass any cells containing volatile
' formulas/functions or uncontrolled looping will start.
    ' Cache the caller's reference so it can be dealt with in a non-UDF routine
    If mCalculatedCells Is Nothing Then Set mCalculatedCells = New Collection
    On Error Resume Next
    mCalculatedCells.Add Application.Caller, Application.Caller.Address
    On Error GoTo 0
    ' Setting/resetting the timer should be the last action taken in the UDF
    If mWindowsTimerID  0 Then KillTimer 0&, mWindowsTimerID
    mWindowsTimerID = SetTimer(0&, 0&, 1, AddressOf AfterUDFRoutine1)
    ' Set Post UDF module and routine
    mRoutine = sRoutine
End Sub
Private Sub AfterUDFRoutine1()
' This is the first of two timer routines. This one is called by the Windows
' timer. Since a Windows timer cannot run code if a cell is being edited or a
' dialog is open this routine schedules a second safe timer using
' Application.OnTime which is ignored in a UDF.
   ' Stop the Windows timer
   On Error Resume Next
   KillTimer 0&, mWindowsTimerID
   On Error GoTo 0
   mWindowsTimerID = 0
   ' Cancel any previous OnTime timers
   If mApplicationTimerTime  0 Then
      On Error Resume Next
      Application.OnTime mApplicationTimerTime, "AfterUDFRoutine2", , False
      On Error GoTo 0
   End If
   ' Schedule timer
   mApplicationTimerTime = Now
   Application.OnTime mApplicationTimerTime, "AfterUDFRoutine2"
End Sub
Private Sub AfterUDFRoutine2()
' This is the second of two timer routines. Because this timer routine is
' triggered by Application.OnTime it is safe, i.e., Excel will not allow the
' timer to fire unless the environment is safe (no open model dialogs or cell
' being edited).
    ' Do tasks not allowed in a UDF... (post UDF code)
    Application.Run mRoutine
End Sub