寻求组合如何自定义UDF的插入函数向导并使UDF操纵其他单元格

时间:2015-10-14 23:29:37

标签: excel vba excel-vba 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使用自定义的插入函数向导,该向导可以写入调用它的不同单元格。我想要这样做的原因是我正在编写一个带有多个输入参数的函数的AddIn(因此用户需要输入参数的工具提示)并且我需要在不同于它们被调用的单元格的单元格中写入(因为我不想坚持使用宏。我想要一个功能驱动的AddIn而不是一个按钮驱动的AddIn。)对于熟悉Bloomberg Excel API的人来说,这几乎就是BDH()所做的功能。

我写了两个模块作为问题的指导原则。第一个构建一个displayParameters()函数,需要在运行main函数之前运行。它通过函数驱动的方式执行自定义插入函数向导的任务。第二个函数是名为sumTwoNumbers的main函数,它执行两个数字的和,并将结果显示在与调用函数的单元格不同的单元格中。当您尝试使用插入函数向导(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会中断,因为您无法在向导打开的情况下写入单元格。这个问题的一个显而易见的解决方案是找到一种方法,使得插入函数向导在提供每个输入参数后停止在后台运行该函数,并使其等待,直到您单击“确定”后运行该函数。

1 个答案:

答案 0 :(得分:0)

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