在 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 事件的事件处理程序的主要内容,如果未能解决你的问题,请参考以下文章
在 Android 中以编程方式创建的多个文本视图的单个 onclick 侦听器