Search code examples
excelvbatextboxuserform

Trying to add Textboxes to a userform dynamically?


I have code inside a excel workbook that helps me create mass emails to send to users of various programs. I have a userform that pops up and the user populates all the info needed. but that only counts for one app at a time. Can someone share code with me that dynamically adds textboxes to a userform dependant on what checkboxes are ticked ?

In the first frame I have check boxes that indicate what applications are affected, second frame I have option buttons to describe what type of incident and then I would like the textboxes to appear according to what has been ticked.

Any guidance much appreciated as I think this is way too deep for me at the moment

I've reverse engineered this code it adds the boxes I want but I need to be able to populate them with cell data and then use it in the emails:

Option Explicit

Dim SpnColct As Collection

Private Sub CommandButton2_Click()

    Dim cSpnEvnt As cControlEvent
    Dim ctlSB As Control
    Dim ctlTXT As Control
    Dim lngCounter As Long

    For lngCounter = 1 To 7
        Set ctlTXT = Me.Frame7.Controls.Add("Forms.TextBox.1", "Text" & lngCounter)
        ctlTXT.Name = "Text" & lngCounter
        ctlTXT.Left = 5
        ctlTXT.Height = 125: ctlTXT.Width = 280
        ctlTXT.Top = (lngCounter - 1) * 125 + 2

        Set cSpnEvnt = New cControlEvent
        Set cSpnEvnt.SP = ctlSB
        Set cSpnEvnt.TXT = ctlTXT
        SpnColct.Add cSpnEvnt

    Next lngCounter

    Me.Frame1.ScrollHeight = (lngCounter - 1) * 17 + 2

End Sub

This added to a class module:

Option Explicit

Public WithEvents SP As MSForms.SpinButton
Public WithEvents TXT As MSForms.TextBox

Private Sub SP_SpinDown()
    SP.Value = SP.Value - 1
    MsgBox "Spin Down to " & SP.Value
End Sub

Private Sub SP_SpinUp()
    SP.Value = SP.Value + 1
    MsgBox "Spin Up to " & SP.Value
End Sub

Private Sub TXT_Change()
    MsgBox "You changed the value."
End Sub

Userform


Solution

  • Updated This is going to be a bit of a long one - step through it see if you understand it. Have changed it to create the textboxes on the CheckBox_Click event but change to the commandbutton if you wish. Any more then this and I think you'll need to start a new question.

    I've been doing something similar recently and found that the reason you're having issues is due to the order of loading objects. I unfortunately can't find the link that explains it at the moment (will update if can) but briefly to be able to achieve this you need an additional Class that does the loading of the objects, otherwise the Userform can't see them. This is the kind of solution that I came up with (using your example)

    Userform:

    Option Explicit
    Private WithEvents cControls As EventController
    
    Private Sub cControls_Click(ctrl As CheckBoxControl)
        Dim tBox As TextBoxControl
        Dim i As Long
        Dim NextTop As Long, FrameHeight As Long
    
        For i = 1 To cControls.GetControls.Count
            Debug.Print TypeName(cControls.GetControl(i))
            If TypeName(cControls.GetControl(i)) = "TextBoxControl" Then
                Set tBox = cControls.GetControl(i)
                If tBox.TXT.Parent Is Me.Frame7 Then
                    NextTop = tBox.Top + tBox.Height
                End If
            End If
        Next i
    
        Set tBox = cControls.AddTextBox
        With tBox
            .Height = 125
            .Width = 280
            .Left = 5
            .Top = NextTop
            .TXT.Text = ctrl.cBox.Caption
            FrameHeight = NextTop + .Height
        End With
        If FrameHeight > Me.Frame7.InsideHeight Then
            With Me.Frame7
                .ScrollBars = fmScrollBarsVertical
                .ScrollHeight = FrameHeight
                .Scroll yAction:=6
            End With
        End If
    
    End Sub
    
    Private Sub UserForm_Initialize()
        Dim i As Long
        Dim cBox As CheckBoxControl
        Set cControls = New EventController
        ' This can be set to a userform or a frame
        Set cControls.UserForm = Me
        For i = 1 To 8
            Set cBox = cControls.AddCheckBox
            cBox.cBox.Left = 5
            With cBox.cBox
                .Top = 5 + (i - 1) * .Height
                .Caption = IIf(i = 8, "App Unknown", "App " & i)
            End With
        Next i
    
    End Sub
    
    Private Sub cControls_Change(ctrl As TextBoxControl)
        ' This can be handled in the class instead as you were - just doing it in the userform to show the exposing of the event
        MsgBox ctrl.TXT.Name & " Change"
    End Sub
    
    Private Sub cControls_SpinDown(ctrl As TextBoxControl)
        ' This can be handled in the class instead as you were - just doing it in the userform to show the exposing of the event
        With ctrl.SP
            If .Value >0 Then
                .Value = .Value - 1
            End If
        End With
        MsgBox ctrl.SP.Name & " Spin Down"
    End Sub
    
    Private Sub cControls_SpinUp(ctrl As TextBoxControl)
    ' This can be handled in the class instead as you were - just doing it in the userform to show the exposing of the event
        With ctrl.SP
            .Value = .Value + 1
        End With
        MsgBox ctrl.SP.Name & " Spin Up"
    
    End Sub
    

    Classes - These need to be named as in bold

    EventControl

    Option Explicit
    
    Private CtrlCollection As Collection
    Private cUserForm As UserForm1
    
    Public Event SpinDown(ctrl As TextBoxControl)
    Public Event SpinUp(ctrl As TextBoxControl)
    Public Event Change(ctrl As TextBoxControl)
    Public Event Click(ctrl As CheckBoxControl)
    Public Property Set UserForm(v As UserForm1)
        Set cUserForm = v
    End Property
    Public Property Get UserForm() As UserForm1
        Set UserForm = cUserForm
    End Property
    Public Function AddTextBox() As TextBoxControl
        Dim tBox As TextBoxControl
        Set tBox = New TextBoxControl
    
        tBox.Initialize Me
    
        CtrlCollection.Add tBox
    
        Set AddTextBox = tBox
    
    End Function
    Public Function AddCheckBox() As CheckBoxControl
        Dim cBox As New CheckBoxControl
        cBox.Initalize Me
        CtrlCollection.Add cBox
    
        Set AddCheckBox = cBox
    End Function
    Public Function GetControl(Index As Long)
        Set GetControl = CtrlCollection(Index)
    End Function
    Public Function GetControls() As Collection
        Set GetControls = CtrlCollection
    End Function
    Private Sub Class_Initialize()
        Set CtrlCollection = New Collection
    End Sub
    Public Sub SpinDown(ctrl As TextBoxControl)
        RaiseEvent SpinDown(ctrl)
    End Sub
    Public Sub SpinUp(ctrl As TextBoxControl)
        RaiseEvent SpinUp(ctrl)
    End Sub
    Public Sub Change(ctrl As TextBoxControl)
        RaiseEvent Change(ctrl)
    End Sub
    Public Sub Click(ctrl As CheckBoxControl)
        RaiseEvent Click(ctrl)
    End Sub
    

    CheckBoxControl

    Option Explicit
    
    Public WithEvents cBox As MSForms.CheckBox
    Private cParent As EventController
    Public Property Set Parent(v As EventController)
        Set cParent = v
    End Property
    Public Property Get Parent() As EventController
        Set Parent = cParent
    End Property
    Public Sub Initalize(Parent As EventController)
        Set Me.Parent = Parent
        Set cBox = Parent.UserForm.Frame1.Controls.Add("Forms.CheckBox.1")
    End Sub
    
    Private Sub cBox_Click()
        Parent.Click Me
    End Sub
    

    TextBoxControl

    Option Explicit
    
    Public WithEvents SP As MSForms.SpinButton
    Public WithEvents TXT As MSForms.TextBox
    Private cParent As EventController
    Public Sub Initialize(Parent As EventController)
        Set Me.Parent = Parent
        With Parent.UserForm.Frame7.Controls
            Set SP = .Add("Forms.SpinButton.1")
            Set TXT = .Add("Forms.TextBox.1")
        End With
    End Sub
    Public Property Set Parent(v As EventController)
        Set cParent = v
    End Property
    Public Property Get Parent() As EventController
        Set Parent = cParent
    End Property
    Public Property Let Left(v As Single)
        TXT.Left = v
        SP.Left = TXT.Left + TXT.Width
    End Property
    Public Property Get Left() As Single
        Left = TXT.Left
    End Property
    Public Property Let Top(v As Single)
        TXT.Top = v
        SP.Top = v
    End Property
    Public Property Get Top() As Single
        Top = TXT.Top
    End Property
    Public Property Let Height(v As Single)
        TXT.Height = v
        SP.Height = v
    End Property
    Public Property Get Height() As Single
        Height = TXT.Height
    End Property
    Public Property Let Width(v As Single)
        TXT.Width = v - SP.Width
        SP.Left = TXT.Left + TXT.Width
    End Property
    Public Property Get Width() As Single
        Width = TXT.Width + SP.Width
    End Property
    
    Public Sub SP_SpinDown()
        Parent.SpinDown Me
    '    SP.Value = SP.Value - 1
    '    MsgBox "Spin Down to " & SP.Value
    End Sub
    ' The commented out lines below you can either leave in here, or handle in the Userform
    Public Sub SP_SpinUp()
        Parent.SpinUp Me
    '    SP.Value = SP.Value + 1
    '    MsgBox "Spin Up to " & SP.Value
    End Sub
    
    Public Sub TXT_Change()
        Parent.Change Me
    '    MsgBox "You changed the value."
    End Sub
    

    The issue is stemmed from that when the Userform is loaded the controls aren't loaded and therefore the Userform hasn't registered that they're something that has an Event. By using the intermediary class the Userform recognises that that class has an Event and we load this statically on initialize of the Userform. We can then add in whatever Controls we want to this Class and the Userform will handle them.

    Demo:

    enter image description here