在 VBA 中以编程方式确定 OnClick 事件的事件处理程序

Posted

技术标签:

【中文标题】在 VBA 中以编程方式确定 OnClick 事件的事件处理程序【英文标题】:Programmatically Determining the Event Handler for an OnClick Event in VBA 【发布时间】:2017-09-20 17:59:54 【问题描述】:

我正在开发一个在 MS Access VBA 中跟踪执行路径的应用程序。但是,我遇到了一个问题。似乎您可以在 VBA 中动态分配处理程序来控制 OnClick 和其他此类事件,但我看不到从这些控件中读取事件的方法。例如:

With Forms("Order Entry").Controls("OK") 
 If .OnClick = "" Then 
 .OnClick = "fnProcessOrder" 
 End If 
End With 

这会将函数“fnProcessOrder”动态分配给“确定”按钮。但是,如果您尝试获取该按钮的值,则会得到:

With Forms("Order Entry").Controls("OK") 
  Debug.Print "My Handler -->" + .OnClick
EndWith

结果:

--> [Event Procedure]

没有进一步的细节。这似乎是微软在此处所述的行为: https://msdn.microsoft.com/en-us/vba/access-vba/articles/commandbutton-onclick-property-access

但我想知道是否还有比这更进一步的方法。

我原以为这些信息会出现在 MSysObjects 和 MSysQueries 表中,但我在那里看不到它。我尝试使用 Database Documenter 提取我的测试数据库,并且我也从生成的文档中获得了该值。

有没有办法找到分配给控件的 VBA 函数?

【问题讨论】:

任何工具都会有限制。考虑"fnProcessOrder" 字符串文字将从部分连接起来的情况,例如.OnClick = "fnProcess" & itemType。 FWIW 我很好奇你在执行路径上的工作,以及你是怎么做的。 Rubberduck 可以在这里使用您的专业知识,因为即将实施的最大挑战之一将是实施我们打算实施的 20 多个“代码路径分析”代码检查。你是在没有符号表和抽象语法树的情况下这样做的吗? 好吧,我打算做一些基本的处理,或者使用 pyparsing、lex 或者其他一些手动的东西。所以,肯定会有一个符号树,我已经开始构建它了。无法读取 .OnClick 属性会导致其他所有问题。我希望我不必编写任何东西来解析二进制文件... 实际分配的值可能不可读或不可逆(例如指向函数的内存指针或内部句柄)。在 OK 按钮旁边添加一个不可见的文本控件,并且每当您动态更改 OnClick 时,也要更新该控件。或者有一个固定的 OnClick 函数,然后读取该文本字段并相应地重定向。 我已经在 Rubberduck 上工作了 3 年的大部分时间,我们的解析器/解析器已经看到了 5 个主要贡献者,我可以向您保证,任何手动操作都注定要失败(对不起!) .行继续,行号/标签,预编译器指令,特殊语法,从函数调用告诉数组访问 - 任何“基本”的东西都将是一个天真的解决方案,除了琐碎的代码和测试用例之外,它会无可救药地崩溃。我保证,你所从事的工作绝非易事。也就是说,我确信有一种方法可以获得按钮的实际 OnClick 值(Rubberduck 需要它!) 我认为我们的一位贡献者一直在努力解决这个问题 - 我会联系他。很确定它确实涉及解析二进制文件。你是用 VBA 写的吗? 【参考方案1】:

实际上,我的代码看起来不正确。如果要将 VBA 函数分配给事件过程,则必须使用类似于以下的语法:.OnClick = "=fnProcess()" 注意文字 =() - 这些是使 Access 识别您想要调用功能。

作为额外的考虑,您可以有一个类模块(我们称之为Observer),其代码类似于:

Private WithEvents ctlInterest As Access.Textbox

Public Sub Init(TargetControl As Access.Control)
  Set ctlInterest = TargetControl
  With ctlInterst
    If .OnClick = "" Then
      .OnClick = "[Event Procedure]"
    End If
  End With
End Sub

Private ctlInterest_OnClick()
  fnProcess()
End Sub

然后你可以像这样部署类......

Private objObserver As Observer

Private Sub Form_Load()
  Set objObserver = New Observer
  objObserver.Init(Me.txtInterest)
End Sub   

这将产生相同的结果,并且您最终会得到 多个 事件处理程序。

如需了解更多信息,this 可能会有所帮助。

结合函数表达式是可能的以及它可以随时动态分配的事实,它几乎需要代码路径分析来识别所有事件处理程序的可能入口点。这意味着您需要Rubberduck 的解析器。请注意,静态代码路径分析已计划但尚未实现,AIUI。

另外,我想确定一下——如果您打算为错误处理构建调用堆栈跟踪,您可能需要查看第三方加载项。例如,vbWatchDog,一种商业产品,可以在 VBA 中提供该信息。

【讨论】:

我知道通过动态分配,OnClick 的当前值可以改变,但是有没有办法在应用程序运行时查看当前值是多少?这样我至少可以将它记录到一个文件中。不过,我认为你可能是对的。一个好的堆栈跟踪实用程序可能是要走的路。 vbWatchDog 是否支持在不触发错误的情况下记录堆栈? 只是为了说清楚 - 它不仅仅是动态的 - 它可以由多个 VBA 过程处理,使用我上面显示的语法。当然,如果我们使用=fnProcess() 语法,这将不适用,但我想确保这是已知的。回复:vbWatchDog,您可以使用不需要触发错误的 LiveCallStack,但问题是您仍然需要编写代码才能在某处读取调用堆栈……例如,可能在 fnProcess 函数中。【参考方案2】:

这将返回事件的完整代码。如果您只在该事件中使用一个 sub,它应该可以工作。

Public Sub TestModulePrinter()
    Dim subName As String
    With Forms("Order Entry").Controls("OK")
      subName = "Private Sub " & .Name & "_OnClick()"
    End With
    Dim i As Long
    Dim IsPrinting As Boolean
    With Forms("Order Entry").Module
        For i = 1 To .CountOfLines
            If Not IsPrinting Then
                If Trim(.Lines(i, 1)) = subName Then
                    IsPrinting = True
                End If
            Else
                If Trim(.Lines(i, 1)) = "End Sub" Then
                    IsPrinting = False
                Else
                    Debug.Print .Lines(i, 1)
                End If
            End If
        Next i
    End With
End Sub

【讨论】:

我正在尝试查找控件的当前 OnClick 函数,而不更改该特定控件。我假设 OnClick 的值是在程序的其他地方设置的。 @AlbertPerrien 等等,这是一次性的吗?如果是这种情况,您可以在控件的OnClick 属性上使用设置为Break when value changes 的监视表达式... 这是一个非常不错的建议!如果我使用错误处理程序设置该属性来捕获它,我可能能够中断并记录应用程序中的所有活动。这不会处理 VB 编辑器在编译时设置的内容,但这是一个很好的开始。 @Albert 你试过运行我的代码吗?它返回表单模块中OnClick 事件的完整代码,并且不会更改任何内容

以上是关于在 VBA 中以编程方式确定 OnClick 事件的事件处理程序的主要内容,如果未能解决你的问题,请参考以下文章

如何在 MRTK 中以编程方式分配可交互事件

在 Swift 中以编程方式设置动作监听器

在 Android 中以编程方式创建的多个文本视图的单个 onclick 侦听器

在 Windows 中以编程方式确定电源使用情况?

使用 jQuery 在 IE 的 Javascript 中以编程方式触发事件

在 Darwin/OSX 中以编程方式确定进程信息