vba - 寻求结合如何自定义 UDF 的插入函数向导和使 UDF 操作其他单元格

标签 vba excel debugging

这个问题可能对许多 VBA 程序员有用。它涉及实现两个有用的独立任务并使它们同时工作。

第一个任务是为 UDF 制作 Excel 函数工具提示。虽然似乎还没有找到明确的解决方案,但目前我对自定义插入函数向导的解决方案感到满意。您可以在此处找到有关实现自定义插入函数向导的方法的主题:How to put a tooltip on a user-defined function 当我说插入函数向导时,我指的是这个窗口:

Insert Function Wizard

如果您也对有关寻求实现函数工具提示的明确解决方案的主题感兴趣,您可以访问此处:The quest for the Excel custom function tooltip

第二个任务是在调用 UDF 的不同单元中进行写入。我在这里找到了一个很好的解决方案:I don't want my Excel Add-In to return an array (instead I need a UDF to change other cells)

现在,问题来了:尝试同时执行这两个任务时,Excel 崩溃。我想让这两个任务在调用单个函数时工作,防止 excel 崩溃。我想对 UDF 使用自定义的插入函数向导,该向导可以在调用它的不同单元格中写入。我想这样做的原因是我正在编写一个带有带有多个输入参数的函数的插件(因此用户需要输入参数的工具提示)并且我需要在与它们被调用的单元格不同的单元格中写入(因为我不想坚持使用宏。我想要一个函数驱动的插件,而不是按钮驱动的插件)。对于熟悉 Bloomberg Excel API 的人来说,这几乎就是函数 BDH() 的作用。

我编写了两个模块作为解决该问题的指南。第一个构建了一个需要在运行 main 函数之前运行的 displayParameters() 函数。它通过函数驱动的方式执行自定义插入函数向导的任务。第二个函数是名为 sumTwoNumbers 的主函数,它执行两个数字的总和并将结果显示在与调用该函数的单元格不同的单元格中。当您尝试使用插入函数向导 ( ctr + A ) 运行第二个函数 ( sumTwoNumbers() ) 时,在自定义该函数后(运行 displayParameters() 后),Excel 将崩溃。

模块 1:

Option Explicit

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 displayParameters() As Variant

' This is a UDF that returns true when the volscore file is created 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.

    displayParameters = "Success"

    '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_displayParameters)

End Function

Public Sub AfterUDFRoutine1_displayParameters()

' 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_displayParameters", , False
      On Error GoTo 0
   End If

   ' Schedule timer
   mApplicationTimerTime = Now
   Application.OnTime mApplicationTimerTime, "AfterUDFRoutine2_displayParameters"

End Sub

Public Sub AfterUDFRoutine2_displayParameters()

' 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 sumTwoNumbersArgumentsDescription(1 To 2) As String

    sumTwoNumbersArgumentsDescription(1) = "Write the first number of a Sum"
    sumTwoNumbersArgumentsDescription(2) = "Write the second number of a Sum"

    Application.MacroOptions Macro:="sumTwoNumbers", _
                         ArgumentDescriptions:=sumTwoNumbersArgumentsDescription
                         'Description:="describre the ivol function"

    MsgBox ("The formal parameters and instance of actual parameters are now successfully displayed at the Insert Function Dialog Box.")

End Sub

模块 2:

Option Explicit

'This global variable is the way I found of passing the output of sumTwoNumbers into the
'function "AfterUDFRoutine2"
Dim outputGlobal As Variant

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

'Non-Volatile Function2
Public Function sumTwoNumbers(Optional ByVal param1 As Integer _
                    , Optional ByVal param2 As Integer _
                     ) As Variant

    sumTwoNumbers = param1 + param2
    outputGlobal = sumTwoNumbers
    sumTwoNumbers = "Success"

    '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_sumTwoNumbers)

End Function

Public Sub AfterUDFRoutine1_sumTwoNumbers()

' 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_sumTwoNumbers", , False
      On Error GoTo 0
   End If

   ' Schedule timer
   mApplicationTimerTime = Now
   Application.OnTime mApplicationTimerTime, "AfterUDFRoutine2_sumTwoNumbers"

End Sub

Public Sub AfterUDFRoutine2_sumTwoNumbers()

' 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).

'Write the output to the sheet
Dim dest
Set dest = ActiveCell.Offset(0, 1)
dest.Value = outputGlobal
Set outputGlobal = Nothing

End Sub

到目前为止的进展

我发现每次您填写输入参数后,插入函数向导都会在后台运行您的函数,并在向导中看到的“=”后面输出结果。因此,如果在运行参数数量不足的函数时触发错误,则在向导中提供输入后也会触发该错误。如果在使用该数量的输入运行函数时显示消息框,则向导前面将显示一个消息框。但是,当您使用经过修改的 UDF 以便将输出写入调用它的不同单元格时,并且在通过向导填充输入时不会触发错误,Excel 将中断。我的猜测是,发生这种情况是因为您的函数在后台运行,触发 AfterUDFRoutine1_sumTwoNumbers(),然后触发 AfterUDFRoutine2_sumTwoNumbers()。当 AfterUDFRoutine2_sumTwoNumbers() 最终尝试在 Excel 电子表格中写入并且向导已打开时,Excel 会中断,因为在向导打开的情况下无法在单元格中写入。解决此问题的一个明显的解决方案是找到一种方法,使“插入函数向导”在提供每个输入参数后停止在后台运行该函数,并使其等到单击“确定”后运行该函数。

最佳答案

上面的问题太长了。我可以通过询问如何在能够修改其他单元格的 UDF 中使用“插入函数向导”来总结它。我一直坚持 UDF 的概念,它可以修改我所知道的其他单元格,并且没有跳出框框思考。可以执行此操作的 UDF 代码,如下所述:I don't want my Excel Add-In to return an array (instead I need a UDF to change other cells) ,当用户尝试使用“插入函数向导”时会破坏 Excel。此问题的解决方法是创建一个自动调整数组输出大小的 UDF,而不是创建修改外部单元的 UDF。我找到了这个插件:https://colinlegg.wordpress.com/2014/08/25/self-extending-udfs-part-1/它给出了一个名为“=rxlRESIZE()”的函数的实现,该函数可以调整数组输出的大小。如果您在代码中编写该函数,并在返回数组输出之前立即应用该函数,那么只要您的输出是数组,您就有一个可以写入其他单元格的 UDF。最后,这可以防止 Excel 在使用“插入函数向导”时崩溃。为了自定义插入函数向导,我仍然使用从这篇文章中获得的实现:How to put a tooltip on a user-defined function

关于vba - 寻求结合如何自定义 UDF 的插入函数向导和使 UDF 操作其他单元格,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33137386/

相关文章:

升级到 Office 2013 后 Excel 宏不起作用

sql - ACE.OLEDB.12 的替代方案,用于查询 Excel 数据表

c# - 如何使用进程 ID 获取 Excel 实例或 Excel 实例 CLSID?

excel - 使用 Excel vba 计算给定的进出时间所花费的总小时数

javascript - Chrome/Firefox 一次在所有 javascript onclick 事件上设置断点

iphone - 在 iPhone 上的 Xcode 调试器中检查 NSObject 的引用计数?

wordpress - wordpress js 文件位于哪里?

vba - 如何协调不同模块中的err.number,VBA中的类模块

excel - VBA 函数返回到赋值语句上的调用例程

c# - 我们可以用 C# 为 Excel 编写宏吗