尝试将控件添加到以前打开的窗体时出现错误 91

Error 91 when attempting to add control to previously opened form

我有两个用户表单 - "PhaseHome" 和 "ModifyPhases"。

我必须完成 "PhaseHome" 表格才能进入 "ModifyPhases" 表格。在 "ModifyPhases" 表单上,我利用组合框和按钮为用户创建一个新的自定义命名用户表单,其中包含一些控件。代码如下所示:

请注意:

"Phasename" 是用户在前面的组合框中输入的自定义名称。

Sub New_form()

Dim Newphase As VBComponent
Dim ItemBox As MSForms.ComboBox
Dim AddItem As MSForms.CommandButton

Sheet1.Activate

'Creating the new form
Set Newphase = ActiveWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm)
    With Newphase
    .Properties("Height") = 250
    .Properties("Width") = 350
    .Properties("Caption") = Phasename
    .Name = Phasename
    End With


'Inserting the combobox into the dynamically created form
Set ItemBox = Newphase.Designer.Controls.Add("Forms.ComboBox.1")
With ItemBox
    .Name = Phasename & "Box"
    .Top = 60
    .Left = 12
    .Width = 140
    .Height = 80
    .Font.Size = 8
    .Font.Name = "Tahoma"
    .BorderStyle = fmBorderStyleOpaque
    .SpecialEffect = fmSpecialEffectSunken
End With

'Inserting buttons into the dynamically created form
Set AddItem = Newphase.Designer.Controls.Add("Forms.commandbutton.1")
With AddItem
    .Name = "cmd_1"
    .Caption = "Add Line Item"
    .Top = 5
    .Left = 200
    .Width = 110
    .Height = 35
    .Font.Size = 8
    .Font.Name = "Tahoma"
    .BackStyle = fmBackStyleOpaque
End With

完成并创建用户窗体;我现在想向 "PhaseHome" 表单添加一个按钮,允许用户访问我们刚刚创建的表单。

Sheet1.Select
Range("D5").Value = Range("D5").Value + 45

'Add button to Phase Home Form
Dim homeform_button As MSForms.CommandButton
Dim ufObj As UserForm

Set ufObj = ActiveWorkbook.VBProject.VBComponents("Phasehome").Designer
With ufObj
    Set homeform_button = .Controls.Add("Forms.CommandButton.1")
    With homeform_button
        .Name = "cmd" + Phasename
        .Caption = Phasename
        .Top = Range("D5").Value
        .Left = 45
        .Width = 78
        .Height = 36
        .Font.Size = 8
        .Font.Name = "Tahoma"
        .BackStyle = fmBackStyleOpaque
    End With
End With

'Making sure we don't overwrite previously existing code when we insert this into PhaseHome
Dim linestart As Integer 

linestart = Range("D8").Value

ThisWorkbook.VBProject.VBComponents("PhaseHome").CodeModule.InsertLines linestart, "Private Sub cmd" & Phasename & "_Click()"
linestart = linestart + 1
ThisWorkbook.VBProject.VBComponents("PhaseHome").CodeModule.InsertLines linestart, "Unload Me"
linestart = linestart + 1
ThisWorkbook.VBProject.VBComponents("PhaseHome").CodeModule.InsertLines linestart, "Sheet2.Activate"
linestart = linestart + 1
ThisWorkbook.VBProject.VBComponents("PhaseHome").CodeModule.InsertLines linestart, "" & Phasename & ".Show"
linestart = linestart + 1
ThisWorkbook.VBProject.VBComponents("PhaseHome").CodeModule.InsertLines linestart, "End Sub"
linestart = linestart + 1

Range("D8").Value = linestart

现在好消息是这段代码可以工作!....只要我直接从 "ModifyPhases" 表单中 运行 它。有一次,在会话中第一次打开和关闭 "PhaseHome" 表单时,我开始收到指向

的错误 91(未设置对象变量或块变量)
Set homeform_button = .Controls.Add("Forms.CommandButton.1")
每次我尝试再次 运行 宏时,

行。

我尝试过的事情:

  1. 我已确保 "PhaseHome" 表单已卸载。用户窗体之间的按钮总是包含一个卸载我,我也尝试过直接在宏本身内卸载 "PhaseHome",并且还使用与 "PhaseHome" 的终止和初始化函数相关的变量来确保它没有直接引用它就被卸载了。

  2. 在注意到刷新工作簿解决了这个问题之后,我在网上发现了一些代码(来自我很遗憾忘记的来源),每次启动 "ModifyPhases" 表单时都会关闭并重新打开工作簿这解决了这个问题。

    子CloseMe() Application.OnTime 现在 + TimeValue("00:00:02"), "OpenMe" ThisWorkbook.Close 正确 结束子

    子OpenMe() ModifyPhases.Show 结束子

我不知道为什么代码标签在这里不起作用。

这行得通,但是...会导致工作簿损坏,而且似乎也没有必要。你们对为什么会发生这种情况有任何理论吗?谢谢!

-马诺

别搞错了:在 运行-time 修改控件,尤其是 在 运行-time 添加表单,是一个高级VBA 运动,我认为这简直超出了你目前的能力范围。 (请不要误会,我没有居高临下的意思)

I need a new custom button for each custom form added to "PhaseHome".

我认为这是挂断,也是错误的来源。所以...

有没有更简单的方法??

让我们彻底放弃这个想法吧!

使用更易于在 运行 时进行修改的不同控件类型。按钮很棘手,因为它们每个都需要自己的 _Click 事件处理程序。无需为每个阶段添加按钮,只需将一个新项目添加到 ComboBox 控件,并利用其 _Change(或其他一些)事件作为用户输入的方法。

IOW,与其期望用户按下显示表单的按钮,不如让他们 select ComboBox 中的表单!

然后,调用 ComboBox 的 _Change 事件,返回 dict/collection 属性,并显示“Phaseform”对象,您可以在 运行 时修改它的控件,根据需要。

现在您有一个相对通用的表格,可用于任何可能的阶段

组合框的 List 属性 本身是动态的,并且有一个 _Change 事件处理程序供您使用!

Private Sub cbox_PhaseNames_Change()

    MsgBox Me.Value  'Show the value which is selected, for debugging

    'Modify the Newphase userform. There is only one form, and its properties
    ' will be modified based on the selection from the cBox_Phasenames control

   Newphase.Caption = Me.Value

   'If you need to change other controls, you can probably do that here, too

结束子

例子

我创建了一些粗略的示例 (download from Google Drive if you'd like),仅使用两个用户表单。 Phasehome实现上面描述的,Phaseform可以根据PhaseHome上的ComboBox中的selection修改(例如,它是Caption)。

注意:您至少使用了 3 种形式,请注意我只使用了 2 种,我的示例对 Pagehome 所做的可能是更适用于您的 ModifyPhases 表单,因此请注意并进行相应修改。

这就是我为 Phasehome:

设置代码的方式
Option Explicit
Private Sub cbox_Phasenames_Change()
    Dim val$, bFound As Boolean
    Dim i As Long
    
    'crude validation:
    val = Me.cbox_Phasenames.Value
    For i = 0 To Me.cbox_Phasenames.ListCount - 1
        If val = Me.cbox_Phasenames.List(i) Then
            bFound = True
            Exit For
        End If
    Next
    
    If Not bFound Then Exit Sub 'Avoid errors
    
    'Modify the PhaseForm:
    With phaseForm
        .Caption = val
        .Show
    End With
    
End Sub

Private Sub CommandButton1_Click()
   
    'Very simple example, allows duplicates, which you probably want to avoid
    Me.cbox_Phasenames.AddItem Me.TextBox1.Value

End Sub

这里是 Phaseform 的代码,我已经评论了一些项目,但是使用 Initialize 事件来分配您设置的属性:

Option Explicit

Private Sub UserForm_Initialize()
    Me.Height = 250
    Me.Width = 350
    'Me.Caption = ""   '## This is set in the calling procedure
    With Me.ComboBox1
        .Top = 60
        .Left = 12
        .Width = 140
        .Height = 80
        .Font.Size = 8
        .Font.Name = "Tahoma"
        .BackStyle = fmBackStyleOpaque
    End With
    With Me.CommandButton1
        '### There is no need to assign a dynamic Name property to this control
        '.Name = "cmd" + Phasename
        .Caption = ""
        .Top = Range("D5").Value
        .Left = 45
        .Width = 78
        .Height = 36
        .Font.Size = 8
        .Font.Name = "Tahoma"
        .BackStyle = fmBackStyleOpaque
    End With
    
End Sub

Private Sub ComboBox1_Change()
    MsgBox "Does something..."
End Sub

注意:如果您需要在 运行 期间保留其他属性,可以通过静态变量并可能使用 Collection 或 Dictionary 对象来协助完成组织需要的东西。将阶段的“列表”保存在数组中,并将它们的相关属性保存在 dict/collection 等中。如果绝对必要,您可以将一些内容存储在隐藏的工作表中,或工作簿中的 Name ,或在 CustomXMLPart 等中 - 有很多方法可以想象在用户会话之外保留元数据,以便明天、下周等更改可用。

所以我发现了我的宏的问题。一旦我的代码在设计时完成了自定义用户窗体的构建——当我试图将其切换到 "PhaseHome" 用户窗体上的设计模式以编辑我的按钮时,它并没有退出自定义窗体的设计模式,导致错误 91。我发现通过手动进入设计模式(使用我从 Peter Thornton 先生那里找到 here 的代码)然后在我的代码开始放置我的按钮之前使用时间延迟宏(如下所示)手动退出设计模式在 "Phasehome" 表格上每次都没有错误。

我使用了延时宏,因为进入设计模式会结束宏的执行。

我在尝试进入 "Phasehome" 用户窗体的设计模式之前使用 .hasopendesigner 属性 (here) 测试我的自定义窗体变量发现了这一点添加我的按钮,发现它仍然是打开的。 只是手动退出设计模式似乎并没有改变这个 - 我怀疑这是一个错误的部分。这就是我手动进入然后手动退出设计模式的原因。

我不确定,但我倾向于认为这是 VBA 用户表单中的一个错误,因为除了如前所述重新加载工作簿之外,它积极抵制任何其他形式的故障排除。

这是我完成自定义用户窗体设计后的代码(请注意选择已完成,因为我想控制用户在此过程中看到的内容并且大部分是不必要的):

Sheet1.Select

Dim linestart As Integer 'Making sure we don't overwrite our code when we insert it into PhaseHome

linestart = Range("D8").Value

ActiveWorkbook.VBProject.VBComponents("PhaseHome").CodeModule.InsertLines linestart, "Private Sub cmd" & Phasename & "_Click()"
linestart = linestart + 1
ActiveWorkbook.VBProject.VBComponents("PhaseHome").CodeModule.InsertLines linestart, "Unload Me"
linestart = linestart + 1
ActiveWorkbook.VBProject.VBComponents("PhaseHome").CodeModule.InsertLines linestart, "Sheet2.Activate"
linestart = linestart + 1
ActiveWorkbook.VBProject.VBComponents("PhaseHome").CodeModule.InsertLines linestart, "" & Phasename & ".Show"
linestart = linestart + 1
ActiveWorkbook.VBProject.VBComponents("PhaseHome").CodeModule.InsertLines linestart, "End Sub"
linestart = linestart + 1

Range("D8").Value = linestart
Range("D5").Value = Range("D5").Value + 45
Range("D10").Value = Phasename

Sheet2.Select
Range("A1").Select
Call Design_mode_on
End Sub

延时宏:

Sub Design_mode_on()
    Application.OnTime Now + TimeValue("00:00:01"), "Design_mode_off"
    EnterExitDesignMode True 'Enter Design Mode
End Sub




Sub Design_mode_off()
   EnterExitDesignMode False 'Exit Design Mode
   Call second_newphase
End Sub



Sub EnterExitDesignMode(bEnter As Boolean)
Dim cbrs As CommandBars
Const sMsoName As String = "DesignMode"

    Set cbrs = Application.CommandBars
    If Not cbrs Is Nothing Then
        If cbrs.GetEnabledMso(sMsoName) Then
            If bEnter <> cbrs.GetPressedMso(sMsoName) Then
                cbrs.ExecuteMso sMsoName
                Stop
            End If
        End If
    End If
End Sub

\延时宏:

Sub second_newphase()  'Divided this module in 2 due to some weird form interactions

Sheet1.Select

Phasename = Range("D10").Value
Range("D10").Clear
Dim homeform_button As MSForms.CommandButton
Dim ufObj As UserForm

EnterExitDesignMode False 'Exit again just for good measure hehe

Set ufObj = ActiveWorkbook.VBProject.VBComponents("Phasehome").Designer
With ufObj
    Set homeform_button = .Controls.Add("Forms.CommandButton.1")
    With homeform_button
        .Name = "cmd" + Phasename
        .Caption = Phasename
        .Top = Range("D5").Value
        .Left = 45
        .Width = 78
        .Height = 36
        .Font.Size = 8
        .Font.Name = "Tahoma"
        .BackStyle = fmBackStyleOpaque
    End With
End With

Sheet2.Select
Range("A1").Select
End Sub