【问题标题】:Assign event handlers to controls on user form created dynamically in VBA将事件处理程序分配给在 VBA 中动态创建的用户表单上的控件
【发布时间】:2012-05-22 11:41:17
【问题描述】:

我在互联网上找到了很多资源,它们几乎可以 完成我想做的事情,但不完全是。我有一个命名范围“日程表”。对于 dayList 中的每一天,我想在用户表单上创建一个按钮,该按钮将运行当天的宏。我可以add the buttons dynamically,但不知道如何将 daycell.text 从命名范围传递到按钮、事件处理程序、宏:S 这是我必须创建用户表单的代码:

Sub addLabel()
ReadingsLauncher.Show vbModeless
Dim theLabel As Object
Dim labelCounter As Long
Dim daycell As Range
Dim btn As CommandButton
Dim btnCaption As String


For Each daycell In Range("daylist")
    btnCaption = daycell.Text
    Set theLabel = ReadingsLauncher.Controls.Add("Forms.Label.1", btnCaption, True)
    With theLabel
        .Caption = btnCaption
        .Left = 10
        .Width = 50
        .Top = 20 * labelCounter
    End With

    Set btn = ReadingsLauncher.Controls.Add("Forms.CommandButton.1", "runButton", True)
    With btn
        .Caption = "Run Macro for " & btnCaption
        .Left = 80
        .Width = 80
        .Top = 20 * labelCounter
    '   .OnAction = "btnPressed"
    End With

    labelCounter = labelCounter + 1
Next daycell

End Sub

为了解决上述问题,我目前提示用户输入他们想要运行的日期(例如第 1 天)并将其传递给宏并且它可以工作:

Sub B45runJoinTransactionAndFMMS()


loadDayNumber = InputBox("Please type the day you would like to load:", Title:="Enter Day", Default:="Day1")

Call JoinTransactionAndFMMS(loadDayNumber)

End Sub

Sub JoinTransactionAndFMMS(loadDayNumber As String)
xDayNumber = loadDayNumber

Sheets(xDayNumber).Activate
-Do stuff

End Sub

因此,对于我的每个 runButtons,它都需要显示 daycell.text,并运行一个使用相同文本作为参数的宏来选择要在其上执行操作的工作表。

任何帮助都会很棒。我已经看到动态编写 vba 代码以处理宏的响应,但我相信必须有某种方式可以通过传递参数更优雅地完成,只是不知道如何。非常感谢!

【问题讨论】:

  • this 看起来接近你想要的。 OTOH 为什么不创建一个从daylist 填充的组合框和一个可以读取所选值的命令按钮?
  • 我想我可能不得不选择那个选项。由于宏用于筛选和上传数据,我希望我能够创建一个小的启动屏幕,该屏幕将填充每个日期的一行并显示记录数、错误数等,并具有每个按钮都会使用户更加友好。但是,除非我能找到更简单的解决方案,否则我认为您的建议将是赢家。
  • 另一个想法...也许您可以在一个特殊的工作表中建立一个相关信息(天数、# 记录、# 错误等)的表格,并在那里捕获点击事件。
  • 不太清楚如何从工作表中捕获点击事件,但如果可以的话,它可能会简化接口、条件格式等......
  • 我添加了这个作为答案,因为评论框不适合发布多行代码。

标签: vba excel excel-2007


【解决方案1】:

我知道您现在已经接受了一个适合您的解决方案,并且比下面的要简单得多,但是如果您有兴趣,这将是您问题的更直接答案。

您需要创建一个类来处理按钮单击,因此每次单击按钮时,它都会使用类中的事件,您只需执行一次,然后为每个按钮创建一个新实例。为了阻止这些类超出范围并丢失,它们需要存储在类级别声明中。在下面,我稍微移动了您的代码。

在类模块中(我称之为cButtonHandler)

Public WithEvents btn As MSForms.CommandButton

Private Sub btn_Click()
    MsgBox btn.Caption
End Sub

使用事件是因为它允许您将大多数事件用于控件。我已将按钮生成代码移到用户窗体中,如下所示:

Dim collBtns As Collection

Private Sub UserForm_Initialize()

Dim theLabel As Object
Dim labelCounter As Long
Dim daycell As Range
Dim btn As CommandButton
Dim btnCaption As String
'Create a variable of our events class
Dim btnH As cButtonHandler
'Create a new collection to hold the classes
Set collBtns = New Collection

For Each daycell In Range("daylist")
    btnCaption = daycell.Text
    Set theLabel = ReadingsLauncher.Controls.Add("Forms.Label.1", btnCaption, True)
    With theLabel
        .Caption = btnCaption
        .Left = 10
        .Width = 50
        .Top = 20 * labelCounter
    End With

    Set btn = ReadingsLauncher.Controls.Add("Forms.CommandButton.1", "runButton", True)
    With btn
        .Caption = "Run Macro for " & btnCaption
        .Left = 80
        .Width = 80
        .Top = 20 * labelCounter
        'Create a new instance of our events class
        Set btnH = New cButtonHandler
        'Set the button we have created as the button in the class
        Set btnH.btn = btn
        'Add the class to the collection so it is not lost
        'when this procedure finishes
        collBtns.Add btnH
    End With

    labelCounter = labelCounter + 1
Next daycell


End Sub

然后我们可以从一个单独的例程中调用 useform:

Sub addLabel()
ReadingsLauncher.Show vbModeless

End Sub

VBA 中的类在许多 VBA 书籍中并没有得到很好的介绍(通常您需要阅读 VB6 书籍才能理解),但是一旦您了解了它们以及它们的工作原理,它们就会变得非常有用:)

希望对你有帮助

编辑 - 解决其他查询

要引用集合中的对象,可以通过键或索引来完成。要使用密钥,您需要在将项目添加到集合时添加它,因此:

collBtns.Add btnH

会变成

collBtns.Add btnH, btnCaption

因此,键必须是唯一的。然后可以参考如下:

'We refer to objects in a collection via the collection's key
'Or by it's place in the collection
'So either:
MsgBox collBtns("Monday").btn.Caption
'or:
MsgBox collBtns(1).btn.Caption
'We can then access it's properties and methods
'N.B you won't get any intellisense
collBtns("Monday").btn.Enabled = False

如果需要,您还可以向您的类添加其他属性/方法,例如:

Public WithEvents btn As MSForms.CommandButton

Private Sub btn_Click()
    MsgBox btn.Caption
End Sub

Public Property Let Enabled(value As Boolean)
    btn.Enabled = value
End Property

然后会被访问:

collBtns("Monday").Enabled = False

这有帮助吗?如需进一步阅读,我会将您指向 Chip Pearson 的网站,他在大多数主题上都有很棒的东西http://www.cpearson.com/excel/Events.aspx

请记住,VBA是基于VB6的,所以不是完全成熟的OO语言,例如它不支持正常意义上的继承,只支持接口继承

希望这会有所帮助:)

【讨论】:

  • 非常感谢您的深入回复。仍然在折腾是否使用用户表单或工作表,但这是一个很好的回应,特别是正如你提到的,似乎避免了课程(或超级技术解释)。我将您的答案更改为实际答案,因为它是实际回答问题的答案
  • 您的回答让我有信心(一旦我完成了当前基于工作表的解决方案)尝试用户表单。我认为对自己和其他菜鸟都有帮助的 2 个问题,1)如果我想稍后引用集合中的特定 btn(例如更改标题或使其不可见),我可以使用集合来引用它吗参考资料(例如 collBtns.btn[2]) 2) 你知道任何解释集合、事件处理程序等的在线资源吗?有一个菜鸟 java 背景,我喜欢更多面向对象和更少事件驱动的想法
  • 我已更改以上内容以回答您的问题
【解决方案2】:

捕获点击工作表的示例。把它放在工作表模块中:

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
  ' e.g., range(A1:E1) is clicked
  If Not Application.Intersect(Target, Range("A1:E1")) Is Nothing Then
    MsgBox "You clicked " & Target.Address
  End If
End Sub

【讨论】:

  • 我认为您有所作为。如果我使用 intersect 方法,然后使用类似cells(dayCell, 6 的东西来遍历我的命名范围,这可能是我的问题的一个值得哭泣的简单解决方案。将测试并报告回来。非常感谢!
  • 它有效!测试我使用的代码:Sub intersectCallingMacro() Range("D8").Select 'used for testing only Set target = ActiveCell If Not Application.Intersect(target, Range("A1:M100")) Is Nothing Then Call testIntersect(ActiveCell.Value) End If End Sub Sub testIntersect(dayString As String) Dim xDayString As String xDayString = dayString Sheets(xDayString).Activate End Sub(对代码格式化表示歉意)将单元格值作为参数传递给宏。太棒了!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多