使用 VBA 和 ActiveX 减少 WithEvent 声明和子项

Sof*_*ter 6 excel vba activex class worksheet

在工作表上,我有 3 个 ActiveX 对象,分别是 TextBox1、TextBox2、ListBox1

撇开其他代码我有一个类 clsEvents 包含

Private WithEvents txbControl As MSForms.TextBox
Private WithEvents lisControl As MSForms.ListBox 
Private txbEvents As TextBoxEvents                          
Private lisEvents As ListBoxEvents  

Private Sub txbControl_Change()
     txbEvents.ChangeEvent txbControl
End Sub     

Private Sub lisControl_Change()
     lisEvents.ChangeEvent lisControl
End Sub                      
Run Code Online (Sandbox Code Playgroud)

和类 TextBoxEvents 和 ListBoxEvents 包含

Public Event Changed(txtBox As MSForms.TextBox)

Public Sub ChangeEvent(txtBox As MSForms.TextBox)
    RaiseEvent Changed(txtBox)
End Sub
Run Code Online (Sandbox Code Playgroud)
Public Event Changed(ByRef myListBox As MSForms.ListBox)

Public Sub ChangeEvent(lisBox As MSForms.ListBox)
    RaiseEvent Changed(lisBox)
End Sub
Run Code Online (Sandbox Code Playgroud)

工作表模块包含

Public WithEvents tbxEvents As TextBoxEvents
Public WithEvents lisEvents As ListBoxEvents

Private Sub tbxEvents_Changed(tbxBox As MSForms.TextBox)
    Debug.Print "tbxEvents_Changed " & tbxBox.Name
End Sub

Private Sub lisEvents_Changed(lisBox As MSForms.ListBox)
    Debug.Print "lisEvents_Changed " & lisBox.Name
End Sub

Private Sub TextBox2_Change()
    Debug.Print "TextBox2_Change"
End Sub

Private Sub TextBox1_Change()
    Debug.Print "TextBox1_Change"
End Sub

Private Sub ListBox1_Change()
    Debug.Print "ListBox1_Changed "
End Sub
Run Code Online (Sandbox Code Playgroud)

如果我更改 TextBox1 或 TextBox2 或 ListBox1 中的某些内容,调试窗口显示事件将首先发送到工作表(TextBox1_Change 等),然后是 tbxEvents_Changed 或 LisEvents_Changed,因此它可以正常工作。

我想实现的是用类似的东西替换 clsEvents 中的代码

Private WithEvents objControl As OLEobject
Private txbEvents As TextBoxEvents                          
Private lisEvents As ListBoxEvents  

Private Sub objControl_Change()
     if (TypeOf objControl.Object Is MSForms.TextBox) Then
     txbEvents.ChangeEvent objControl
     elseif (TypeOf objControl.Object Is MSForms.ListBox) Then
     lisEvents.ChangeEvent objControl
     endif
End Sub                  
Run Code Online (Sandbox Code Playgroud)

所以基本上我想知道如何为 WithEvents 实现一个有效的定义,这将消除 clsEvents 中“许多”事件函数的必要性。

Public WithEvents objControl As ?????
Run Code Online (Sandbox Code Playgroud)

EvR*_*EvR 12

打开记事本并复制下面的代码并将其粘贴到一个新的 txt 文件中,另存为 CatchEvents2.cls

    VERSION 1.0 CLASS
    BEGIN
      MultiUse = -1  'True
    END
    Attribute VB_Name = "CatchEvents2"
    Attribute VB_GlobalNameSpace = False
    Attribute VB_Creatable = False
    Attribute VB_PredeclaredId = False
    Attribute VB_Exposed = False
    Private Type GUID
          Data1 As Long
          Data2 As Integer
          Data3 As Integer
          Data4(0 To 7) As Byte
    End Type

    #If VBA7 And Win64 Then
          Private Declare PtrSafe Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" (ByVal punk As stdole.IUnknown, _
                  ByRef riidEvent As GUID, ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, ByRef pdwCookie As Long, _
                  Optional ByVal ppcpOut As LongPtr) As Long
    #Else
         Private Declare Function ConnectToConnectionPoint Lib "shlwapi" Alias "#168" (ByVal punk As stdole.IUnknown, ByRef riidEvent As GUID, _
                  ByVal fConnect As Long, ByVal punkTarget As stdole.IUnknown, ByRef pdwCookie As Long, Optional ByVal ppcpOut As Long) As Long
    #End If

    Private EventGuide As GUID
    Private Ck As Long
    Private ctl As Object
    Private CustomProp As String

    Public Sub MyChange()
    Attribute MyChange.VB_UserMemId = 2

    Debug.Print " Change ControlName " & " Type: " & TypeName(ctl) & " CustomProp: " & CustomProp
    End Sub


    Public Sub ConnectAllEvents(ByVal connect As Boolean)
          With EventGuide
              .Data1 = &H20400
              .Data4(0) = &HC0
              .Data4(7) = &H46
          End With
          ConnectToConnectionPoint Me, EventGuide, connect, ctl, Ck, 0&
    End Sub

    Public Property Let Prop(newProp As String)
          CustomProp = newProp
    End Property

    Public Property Let Item(Ctrl As Object)
          Set ctl = Ctrl
          Call ConnectAllEvents(True)
    End Property

    Public Sub Clear()
          If (Ck <> 0) Then Call ConnectAllEvents(False)
          Set ctl = Nothing
    End Sub
Run Code Online (Sandbox Code Playgroud)

在您的 VBA 编辑器中导入此文件(右键单击您的 VBA 项目并选择导入)

在普通模块中,您输入以下代码:

Private AllControls() As New CatchEvents2

Sub connect()
Dim j As Long
With Worksheets("Sheet1")
ReDim AllControls(.OLEObjects.Count - 1)
    For j = 0 To .OLEObjects.Count - 1
       AllControls(j).Item = .OLEObjects(j + 1).Object
       AllControls(j).Prop = .OLEObjects(j + 1).Name
    Next
End With
End Sub

Sub disconnect()
Dim j As Long
  For j = LBound(AllControls) To UBound(AllControls)
          AllControls(j).Clear
   Next j
      Erase AllControls
End Sub
Run Code Online (Sandbox Code Playgroud)

现在,当您运行 connect sub 时,任何 activeX 控件的每个更改都会被捕获

编辑:评论后放入所有其他事件;其他事件:(所有这些也适用于用户表单)

Public Sub MyChange()
Attribute MyChange.VB_UserMemId = 2
Debug.Print "ch"
End Sub

Public Sub MyListClick()
Attribute MyListClick.VB_UserMemId = -610
Debug.Print "cl1"
End Sub

Public Sub MyClick()
Attribute MyClick.VB_UserMemId = -600
Debug.Print "cl2"
End Sub

Public Sub MyDropButtonClick()
Attribute MyDropButtonClick.VB_UserMemId = 2002
End Sub

Public Sub MyDblClick(ByVal Cancel As MSForms.ReturnBoolean)
Attribute MyDblClick.VB_UserMemId = -601
Debug.Print "dcl"
End Sub

Public Sub MyKeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
Attribute MyKeyDown.VB_UserMemId = -602
Debug.Print "kd"
End Sub

Public Sub MyKeyUp(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
Attribute MyKeyUp.VB_UserMemId = -604
Debug.Print "ku"
End Sub

Public Sub MyMouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Attribute MyMouseDown.VB_UserMemId = -605
Debug.Print "md"
End Sub

Public Sub MyMouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Attribute MyMouseMove.VB_UserMemId = -606
Debug.Print "mm"
End Sub

Public Sub MyMouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Attribute MyMouseUp.VB_UserMemId = -607
Debug.Print "mu"
End Sub

Public Sub myKeyPress(ByVal KeyAscii As MSForms.ReturnInteger)
Attribute myKeyPress.VB_UserMemId = -603
Debug.Print "kp"
End Sub
Run Code Online (Sandbox Code Playgroud)

然后有 4 个(用户表单)事件:Exit、Enter、AfterUpdate 和 BeforeUpdate,它们是容器控件的事件,您无法使用事件“捕获”这些事件,但通过这种方式您可以:

Public Sub myExit(ByVal Cancel As MSForms.ReturnBoolean)
Attribute myExit.VB_UserMemId = -2147384829
Debug.Print "exit"
End Sub

Public Sub MyAfterUpdate()
Attribute MyAfterUpdate.VB_UserMemId = -2147384832
Debug.Print "au"
End Sub

Public Sub MyBeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
Attribute MyBeforeUpdate.VB_UserMemId = -2147384831
Debug.Print "bu"
End Sub

Public Sub MyEnter()
Attribute MyEnter.VB_UserMemId = -2147384830
Debug.Print "enter"
End Sub
Run Code Online (Sandbox Code Playgroud)

在工作表上,您有 LostFocus 和 GotFocus(1541 和 1542),但这些我无法开始工作,因此如果有人知道如何使用,那就太好了。 最后一句话:它在mac上不起作用

  • 仅供参考,我在最近的一篇[博客文章](https://rubberduckvba.wordpress.com/2020/09/30/making-mvvm-work-in-vba-part-2-event-propagation)中链接到了这个答案,我整个星期都会在这个问答中保持+500的赏金,以给它更多的曝光度(要明确的是:当赏金期结束时你会得到+500) - 这绝对是一个宝石,非常感谢这项工作和研究非常重要! (4认同)
  • 恕我直言,你的工作是一个突破。我寻找在一个类(或模块或工作表)中捕获事件,以避免每个控件出现大量子项,但只找到了部分解决方案。遗憾的是 GotFocus 和 LostFocus 还无法添加(也许在工作表级别捕获并重新路由?)。我相信很多人都会看到这个解决方案并会使用它。感谢您的出色工作! (2认同)
  • 这真太了不起了。您是如何找到这些数字的:-2147384831 等?是否可以捕获 System.Windows.Forms 中已知的 MSForms 中不存在的事件,即 MouseEnter、MouseLeave? (2认同)
  • 谢谢,我使用了 Microsoft OLE/COM 对象查看器 (2认同)
  • 当然,这都是为了分享知识,但也请查看 Mathieu 的博客,因为他(一如既往)将代码提升到了新的水平 (2认同)