通过按钮调用VBA表单会导致UserForm_Initialize运行两次,从而破坏我的代码?

时间:2018-11-23 16:28:04

标签: excel vba

您好,VBA社区, 我对vba还是很陌生,正在尝试学习很多东西。预先感谢您浏览我的代码和对所面临问题的描述。

我在页面上有一个按钮,用于调用新的用户表单。

代码片段1:

Sub btnShowDetails_Click()
    Call frmShowDeets.ShowDeets
End Sub

...,它将调用“ frmShowDeets”用户窗体中的下一段代码:

代码片段2:

Public Sub ShowDeets()

Dim frm As frmShowDeets
Set frm = New frmShowDeets 'this line triggers the Userform_Initialize() event below
  frm.Show

End Sub

...触发:

代码片段3:

Private Sub UserForm_Initialize()

Dim comboBoxItem As Range

For Each comboBoxItem In ContactList.Range("tblContactList[CompanyName]")
                                            '^refers to unique values in a named range
  With Me.boxCompanySelection
    .AddItem comboBoxItem.Value
  End With
Next comboBoxItem

End Sub

因此,在这一点上,我要显示的表单在其一个组合框中加载了供用户选择的值。用户选择一家公司,然后Combobox_Change事件触发其他例程,为该公司提取信息。

代码片段4:

Public Sub boxCompanySelection_Change()
  Call frmShowDeets.PullData
End Sub

Sub PullData()
Dim numCompanies As Long
  numCompanies = ContactList.Range("B6").Value 'this holds a count of the rows in the named range
Dim FoundCell As Range
  Set FoundCell = ContactList.Range("tblContactList[Company Name]").Find(What:=boxCompanySelection.Text, LookIn:=xlValues, LookAt:=xlWhole)

Dim CompanyRow As Long
  CompanyRow = FoundCell.Row

With ContactList
  'pull a bunch of the company's details
End With
End Sub

这是很奇怪的地方...一旦显示了表单,并且用户选择了一个组合框项目,则触发Combobox_Change事件,代码中断,因为Range()的'What:= boxCompanySelection.Text'部分).Find方法读取为“”为空(即使代码段3旨在加载公司名称,并且代码段4仅在用户从组合框中选择这些公司名称之一时才触发),并且我不需要构建处理“未找到”异常的方法,因为唯一可能的值应该是从我命名的范围中拉入的值。

通过逐步执行代码,我确定由于某些原因,代码段2和3在运行代码段4之前先运行 TWICE 。有谁知道我的代码导致这种情况发生?我认为显示和加载组合框值的表单与从中读取数据的任何代码片段4之间都存在脱节。

奇怪的是,如果我从代码片段2开始运行代码(忽略代码片段1中的按钮调用),那么该表格将按预期工作,并且据我所知2和3仅运行一次。

这个问题可能是我所忽略的简单问题,但我只是无法弄清楚它是什么。再次感谢!

1 个答案:

答案 0 :(得分:3)

您必须了解表单是一个对象-与其他任何类模块完全一样,除了表单恰巧具有设计器和基类,因此UserForm1继承了成员UserForm类中的一个。

表单也有一个默认实例,许多教程很高兴地跳过了这一非常重要但相当技术性的方面,这使我们正好进入了Stack Overflow,并涉及到涉及全局状态的错误。意外存储在默认实例上。

Call frmShowDeets.ShowDeets

假设frmShowDeets是表单类的名称,并假定这是对要运行的表单的第一个引用,则是默认实例的UserForm_Initialize处理程序 .点运算符执行并取消引用该对象时运行。 然后 ShowDeets方法运行。

Public Sub ShowDeets()

Dim frm As frmShowDeets
Set frm = New frmShowDeets 'this line triggers the Userform_Initialize() event below
  frm.Show

End Sub

该行在名为UserForm_Initialize的本地实例上触发frm -这是一个完全独立的同一类的对象。 Initialize处理程序只要正确地初始化了类的实例即创建即运行。 Terminate处理程序在实例被销毁时运行。

因此ShowDeets充当某种“工厂方法”,可以创建并显示frmShowDeets类/表单的新实例-换句话说,默认实例上发生的任何事情都与之无关点:您正在使用的对象位于ShowDeets范围内,被命名为frm,并且超出范围就会被销毁。

完全删除ShowDeets方法。替换为:

Call frmShowDeets.ShowDeets

与此:

With New frmShowDeets
    .Show
End With

现在Initialize处理程序不再在默认实例上运行。

您想要的是完全避免使用默认实例。将表单代码后的所有frmShowDeets替换为Me(请参阅Understanding 'Me' (no flowers, no bees)),这样就不会在默认实例中意外存储任何状态。

Call frmShowDeets.PullData

变得简单:

Call Me.PullData

甚至:

PullData

因为Call在任何地方都不需要,并且在类模块的代码中进行成员调用时,Me限定符始终是隐式的。