单击事件不适用于以编程方式/动态创建的选项按钮

Posted

技术标签:

【中文标题】单击事件不适用于以编程方式/动态创建的选项按钮【英文标题】:click event not working on programmatically / dynamically created optionbutton 【发布时间】:2020-03-30 17:05:27 【问题描述】:

我有以下代码以编程方式/动态创建一个框架并添加一个选项按钮:

Private Sub ComboBox1_Change()

    Dim cb1234Frame As MsForms.Frame
    Dim opbtn1 As MsForms.OptionButton

    Set cb1234Frame = RT_Graph_Form.Controls.Add("Forms.Frame.1")

    With cb1234Frame
        .Top = 132
        .Left = 12
        .Height = 30
        .Width = 144
        .Caption = "Number of Graphs to Display"
    End With

    Set opbtn1 = cb1234Frame.Controls.Add("Forms.OptionButton.1")

    With opbtn1
        .Top = 6
        .Left = 6
        .Height = 18
        .Width = 21.75
        .Caption = "1"
    End With

End Sub

但这不起作用:

Private Sub opbtn1_Click()

    MsgBox "Test Successful!!"

End Sub

【问题讨论】:

Set opbtn1=... 之后尝试调试并检查debug.print opbtn1.name 这将解释您的问题 我明白了...返回为“OptionButton5”...谢谢 您正在将控件添加到表单的默认实例,它可能是也可能不是正在显示的实例。此外,opbtn1 对象引用超出了End Sub 的范围——该引用需要保持在模块级别才能在Change 过程范围内生存。另请注意,处理程序将向表单的默认实例每次更改时添加一个控件,...这可能不是预期的。 好吧...我没有显示这段代码,因为我觉得它不相关,但我在更改过程中有“ComboBox1.Enabled = False”,这样一旦用户选择了一个项目comboBox1... 他们不能改变他们的选择。所以我认为这可以解决这个问题。另外,我尝试在我的帖子之前将 opbtn1 引用移动到模块级别,但这没有用。我想那是因为它是“OptionButton5”......但无论如何,解决我的问题的最简单方法是使用“OptionButton5”名称吗?或者我可以轻松地将其更改为“opbtn1”,这是我最初尝试做的。 【参考方案1】:

问题是事件处理程序需要在编译时绑定:您不能为动态创建的控件创建事件处理程序。

向您的项目添加一个新的类模块,命名为DynamicOptionButton。该类的作用是包装 MSForms 控件并对其进行编译时引用:

Option Explicit
Private WithEvents Wrapper As MSForms.OptionButton

Public Sub Initialize(ByVal ctrl As MSForms.OptionButton)
    Set Wrapper = ctrl
End Sub

Private Sub Wrapper_Click()
    MsgBox "Works!"
End Sub

请注意,只有一部分事件可以处理:哪些事件可用,取决于您声明包装器引用的接口 - MSForms.Control 有许多事件(和属性),@987654325 @ 有另一组:您可能需要声明两个接口(即同一对象的 2 个包装器)才能访问所有成员。

现在在表单的 declarations 部分中,您需要保存对所有包装器的引用,否则对象将超出范围,处理程序将无法工作。 Collection 可以做到这一点:

Option Explicit
Private ControlWrappers As Collection

Private Sub UserForm_Initialize()
    Set ControlWrappers = New Collection
End Sub

'...

Private Sub CreateOptionButton()
    Dim ctrl As MSForms.OptionButton
    Set ctrl = Me.Controls.Add("Forms.OptionButton.1")
    'set properties...

    Dim wrap As DynamicOptionButton
    Set wrap = New DynamicOptionButton
    wrap.Initialize ctrl

    ControlWrappers.Add wrap
End Sub

注意不要在表单自己的代码隐藏中引用表单的类名:全局范围 RT_Graph_Form 标识符指的是 VBA 控制的“默认实例”自动实例化对象,它可能是也可能不是实际的正在显示的表单实例。您想将动态控件添加到 Me.Controls,而不是 RT_Graph_Form.Controls

现在,我们可以处理在运行时生成的控件事件,但还有另一个问题:DynamicOptionButton 类中的事件处理程序没有引用它所在的窗体!

是吗?

每个 MSForms 控件都有一个Parent 属性;您可以通过递归上升 Parent 属性直到返回的引用是 UserForm 来获取父 UserForm - 并且从那里您可以访问所有公开的内容。

【讨论】:

我很抱歉...这完全是我的想法。我至少需要一周的时间来破译所有这些并达到我可以理解所有这些的水平。但是,我想我从你的回答中收集了两件事。首先,我需要使用“我”,这是我认为我可能首先需要做的事情,但不确定。其次,听起来我认为相对简单的任务并不那么简单。所以我认为在运行时创建整个表单并“隐藏”或“变灰”表单的部分直到我需要它们可能更简单。 @XCELLGUY 希望this article 有助于阐明用户表单。事实上,在运行时动态创建控件绝非易事。 谢谢...我没有意识到【参考方案2】:

我不确定它是否合适,但我设法做到了。

我直接从这个工作簿创建用户表单,所以所有内容都存储在那里。我在任何地方都不需要父属性。

Option Explicit

Const FolderPath As String = "C:"

Public TESTS As New Collection
Public CONTROLWRAPPERS As New Collection

Sub gotothere()
On Error GoTo bleh
Call Shell("explorer.exe" & " " & FolderPath & "\" & ThisWorkbook.ActiveSheet.Range("C14").Value, vbNormalFocus)

Exit Sub
bleh: Call Shell("explorer.exe" & " " & FolderPath, vbNormalFocus)
End Sub

Sub ChooseFolder()
Call Createform
End Sub

Private Sub Createform()

Set TESTS = Nothing

Call listalltests
Call Module1.MakeUserForm

Dim i As Integer

For i = 1 To TESTS.Count
   Call CreateCommandbuttonButton(i)
Next i

Formol.Show vbModeless
End Sub

Private Sub listalltests()

Dim objFSO As Object
Dim objFolder As Object
Dim objSubFolder As Object
Dim i As Integer
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder(FolderPath & "\")
i = 1
For Each objSubFolder In objFolder.subfolders
TESTS.Add objSubFolder.Name
i = i + 1
Next objSubFolder
End Sub

Private Sub CreateCommandbuttonButton(pos As Integer)
    Dim ctrl As MSForms.CommandButton
    Set ctrl = Formol.Controls.Add("Forms.commandbutton.1")
    With ctrl
        .Caption = TESTS(pos)

        If (pos * 20 + 2) > 600 Then
            .Left = 130
            .Top = (pos - 29) * 26 + 2
            .Width = 102
        Else
            .Left = 12
            .Top = pos * 26 + 2
            .Width = 102
        End If
    End With

    Dim wrap As DynamicOptionButton
    Set wrap = New DynamicOptionButton
    wrap.Initialize ctrl
    CONTROLWRAPPERS.Add wrap
End Sub

MakeUserForm 函数存储在一个模块中,只需检查是否存在名为 formol 的表单,如果没有则创建具有特定宽度和高度的表单。这是一个空表单。

除了 Wrapper_click 事件之外,该类与 mathieu 制作的类完全相同。

【讨论】:

最终,我使用的解决方案是通过“SomeControlName.visible = False”简单地隐藏控件。您的解决方案可能有效,但我无法理解您的解决方案。 如果这有意义的话,我也不是。我想我应该使用 parent 属性。

以上是关于单击事件不适用于以编程方式/动态创建的选项按钮的主要内容,如果未能解决你的问题,请参考以下文章

动态创建新 Vimeo 播放器列表并添加事件以暂停每个按钮单击的最佳方法是啥?

spannablestring 不适用于以编程方式创建的按钮

在 Rmarkdown 中动态创建选项卡不适用于 ggplot,而它适用于 plotly

JQuery .on() 没有将点击事件绑定到动态创建的元素[重复]

Monotouch:以编程方式使用动态 ViewControllers 创建 UITabbar

对于以编程方式创建的 UIButton,有时不会触发按钮单击事件