【问题标题】:Reducing WithEvent declarations and subs with VBA and ActiveX使用 VBA 和 ActiveX 减少 WithEvent 声明和 subs
【发布时间】:2020-05-17 17:26:29
【问题描述】:

在工作表上,我有 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                      

TextBoxEvents 和 ListBoxEvents 类包含

Public Event Changed(txtBox As MSForms.TextBox)

Public Sub ChangeEvent(txtBox As MSForms.TextBox)
    RaiseEvent Changed(txtBox)
End Sub
Public Event Changed(ByRef myListBox As MSForms.ListBox)

Public Sub ChangeEvent(lisBox As MSForms.ListBox)
    RaiseEvent Changed(lisBox)
End Sub

工作表模块包含

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

如果我在 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                  

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

Public WithEvents objControl As ?????

【问题讨论】:

  • 这不是在 VBA 中使用“控件数组”的典型方法 - 您的复杂性更高,并且工作表模块中的每个控件仍然有一个事件处理程序。您是否出于某些特定的设计相关原因而采用这种方法?如果不了解工作表的用例,就很难提出“更好”的方法。
  • 在我的应用程序中,我有一个包含大约 40 个 tekstbox、8 个列表框和 48 个标签的工作表,我想根据这些文本和列表框的变化来更改颜色。(我可以根据名称识别标签为每个控件选择)。在其他工作表上,我也确实有许多对象。所以我想要一个事件类来引发所有文本框和列表框的事件。我想让它通用(复选框,选项按钮,...)。通过使用 TextBox 和 ListBox 的基类和 ... 使用 C++ 会很容易,因为它们将具有基类 Control。 .
  • @Tim Williams 当然,我将删除工作表上的 TextBox1_Change (等),它们现在仅用于测试目的,只是为了查看可以在 clsEvents 和其他类中处理哪些事件。我注意到 GotFocus、LostFocus 和 Click 在这些类中无法处理
  • 你可以让你所有的 clsEvents 将它们的控制对象传递到一个单独的 sub 中,然后它可以根据控件的名称响应更改事件
  • 您可以在 ConnectToConnectionPoint API 的帮助下一次性“捕捉”每个控件的更改事件

标签: excel vba class activex worksheet


【解决方案1】:

打开记事本并复制下面的代码并将其粘贴到新的 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

在您的 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

现在,当您运行 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

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

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

在工作表上,您有 LostFocus 和 GotFocus(1541 和 1542),但这些我无法开始工作,所以如果有人知道如何操作,那就太好了。 最后的评论:它在 mac 上不起作用

【讨论】:

  • Attribute MyChange.VB_UserMemId = 2 行与 ConnectToConnectionPoint API 结合使用,“=2”是 Change-event 。由于属性的原因,您必须先将其保存在 txt 文件中,然后再导入。我将编辑我的帖子并在其中添加其他活动
  • 恕我直言,您的工作是一个突破。我在 ONE 类(或模块或工作表)中寻找捕获事件,以避免每个控件有大量子项,但只找到了部分解决方案。遗憾的是尚未添加 GotFocus 和 LostFocus(可能在工作表级别捕获并重新路由?)。我相信很多人都会看到这个解决方案并会使用它。感谢您的出色工作!
  • 这太棒了。您是如何找到这些数字的:-2147384831 等?是否有可能从 System.Windows.Forms 中捕获 MSForms 中不存在的已知事件,即 MouseEnter、MouseLeave?
  • 谢谢,我使用了 Microsoft OLE/COM 对象查看器
  • 当然,这完全是为了分享知识,但也请查看 Mathieu 的博客,因为他(一如既往)将代码提升到了一个新的水平
猜你喜欢
  • 2015-02-08
  • 1970-01-01
  • 2018-03-30
  • 2016-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-08
相关资源
最近更新 更多