+ Reply to Thread
Results 1 to 27 of 27

Add Command Buttons to User Form at Runtime

Hybrid View

  1. #1
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Add Command Buttons to User Form at Runtime

    I have a number of macros that I would like to call from a User Form. I want to be able to pass an array of the names of these macros to a function that will runtime create a UserForm with Command Buttons linked to these macros. Is anybody able to assist with this?

    Attached is as far as I have got.

    I am having difficulty with the following:
    1. Create the form dynamically (currently using the first form in ThisWorkbook)
    2. Spacing the Cmd Buttons over the form (currently overwriting the same button)
    3. Adding the Calls to the Command Buttons
    Attached Files Attached Files
    *******************************************************

    HELP WANTED! (Links to Forum threads)
    Trying to create reusable code for Custom Events at Workbook (not Application) level

    *******************************************************

  2. #2
    Forum Guru Norie's Avatar
    Join Date
    02-02-2005
    Location
    Stirling, Scotland
    MS-Off Ver
    Microsoft Office 365
    Posts
    19,644

    Re: Add Command Buttons to User Form at Runtime

    Here's a simple example of 1 and 2.
    Option Explicit
    Const vbext_ct_MSForm = 3
    
    Sub CreateFormAddButtons()
    Dim objFrm As Object
    Dim Btn As MSForms.CommandButton
    Dim I As Long
    Dim lngTop As Long
    Dim lngLeft As Long
    
        lngLeft = 5
        
        Set objFrm = ThisWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm)
    
        For I = 1 To 10
    
            Set Btn = objFrm.Designer.Controls.Add("Forms.CommandButton.1")
            Btn.Caption = "Button" & I
            Btn.Left = lngLeft
            Btn.Top = lngTop
            lngTop = Btn.Top + Btn.Height + 3
    
        Next I
        
        objFrm.Properties("Height") = lngTop + Btn.Height + 3
        
        VBA.UserForms.Add(objFrm.Name).Show
    
    End Sub
    I'll also post this as it's quite handy when playing about with this sort of thing.
    Sub DeleteAllForms()
    Dim vbComp As Object
    
        For Each vbComp In ThisWorkbook.VBProject.VBComponents
            If vbComp.Type = vbext_ct_MSForm Then
               ThisWorkbook.VBProject.VBComponents.Remove vbComp
            End If
        Next vbComp
        
    End Sub
    If posting code please use code tags, see here.

  3. #3
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Re: Add Command Buttons to User Form at Runtime

    Thanks Norie. +1

  4. #4
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Re: Add Command Buttons to User Form at Runtime

    See attached workbook. Almost there.

    Two problems left.

    I haven't thought through how to initialize the form each time I send an array of macros to it. I have thought of two options:
    1. I don't add the form dynamically. Instead I create a named form and programatically add the buttons to this. However the forms code would have to be erased every time before the buttons are added. Is this possible?!
    2. I add the form at runtime (like I originally planned) but somehow set the form to self destruct when the form is closed. Is this possible?!


    The second problem - just an aesthetic thing - I would prefer to see the buttons spread over the form rather than one long column. I'm not sure how to do this. (Maybe count number of macros to add and then add the buttons as, say, 3/4/5 columns by X no. of rows - depending on how many buttons we will have to play with).
    Attached Files Attached Files

  5. #5
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Re: Add Command Buttons to User Form at Runtime

    Almost there Only one thing left:

    I would prefer to see the buttons spread over the form rather than appearing as one long column. I'm not sure how to do this. (Maybe count number of macros to add and then add the buttons as, say, 3/4/5 columns by X no. of rows - no. of columns chosen depending on how many buttons we will have to play with).
    Attached Files Attached Files

  6. #6
    Forum Guru Norie's Avatar
    Join Date
    02-02-2005
    Location
    Stirling, Scotland
    MS-Off Ver
    Microsoft Office 365
    Posts
    19,644

    Re: Add Command Buttons to User Form at Runtime

    If you want to remove the temporary form after it's closed put this after the code to show the form.
        ThisWorkbook.VBProject.VBComponents.Remove objFrm
    Changing the layout of the buttons is straightforward, but the exact code really depends on how you want to show them.

    All you need to do is change Top and Left appropriately as you add the buttons.

    Option Explicit
    Const vbext_ct_MSForm = 3
    
    Sub CreateFormAddButtons()
    Dim objFrm As Object
    Dim Btn As MSForms.CommandButton
    Dim I As Long
    Dim lngTop As Long
    Dim lngLeft As Long
    
        lngLeft = 5
    
        Set objFrm = ThisWorkbook.VBProject.VBComponents.Add(vbext_ct_MSForm)
    
        For I = 1 To 20
    
            Set Btn = objFrm.Designer.Controls.Add("Forms.CommandButton.1")
            Btn.Caption = "Button" & I
            Btn.Left = lngLeft
            Btn.Top = lngTop
    
            lngTop = Btn.Top + Btn.Height + 3
            If I Mod 5 = 0 Then
                lngTop = 0
                lngLeft = lngLeft + Btn.Width + 3
            End If
    
        Next I
    
        objFrm.Properties("Height") = (Btn.Height + 3) * (20 / 4 + 1)
        objFrm.Properties("Width") = (Btn.Width + 5) * (20 / 5)
        
        VBA.UserForms.Add(objFrm.Name).Show
    
    End Sub

  7. #7
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Re: Add Command Buttons to User Form at Runtime

    To Norie: Nice work. I like it!
    Last edited by mc84excel; 10-10-2013 at 10:27 PM.

  8. #8
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Re: Add Command Buttons to User Form at Runtime

    Well I have been making a lot of progress (thanks to Norie ). See attached.

    A few things I have planned:
    1. Have the buttons placed from left to right then down (instead of down then left to right)
    2. Work out a way to have a visually pleasing number of rows & columns. As can be seen from my sub, I am toying with the idea of using mod (once the number of buttons is greater than 10) to determine whether the number of buttons would fit neatly into X number of columns (say 5/7/9 columns). If no mod found on the desired column options then I will have to choose the number of columns that will have the closest fit (so that there isn't a large empty space on the last row). I'm not sure how to go about that yet.
    Attached Files Attached Files

  9. #9
    Forum Guru Norie's Avatar
    Join Date
    02-02-2005
    Location
    Stirling, Scotland
    MS-Off Ver
    Microsoft Office 365
    Posts
    19,644

    Re: Add Command Buttons to User Form at Runtime

    1 You just need to swap things around a bit.

    2 What is a 'visually pleasing' no of rows and columns?

  10. #10
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Re: Add Command Buttons to User Form at Runtime

    1. I've solved the 'left to right'. See attached.

    2. A good question. I guess I don't want to see a really elongated user form. (I would like to see the buttons laid out in a ratio that doesn't cause the form to hog the screen). I also don't want to see a single button on the last row. I am stumped over a good way to achieve these two objectives.

    For sake of sanity, I think a max of 50 buttons per form would be more then could ever possibly need.
    I would prefer to work in columns of 5/7/9. (Odd numbers to help reduce problems with Mod function)

    For less than 10 buttons, I have a Select Case choose a preset number of columns & rows (refer code "SetMaxRowsAndCols")

    But how do I work button quantities 11 to 50?
    I was thinking of a loop. For qty X, does it mod 0 on 5? If yes choose 5 cols. Else does it mod 0 on 7? If yes choose 7. And so on. Obviously not all numbers between 11 to 50 will divide neatly into 5, 7 or 9. So after running the 'perfect division' loop and getting nowhere, it would need to check the next closest match. One gap on the last row is acceptable. So for X + 1, does it mod 0 into 5/7/9? And so on.
    Attached Files Attached Files

  11. #11
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Re: Add Command Buttons to User Form at Runtime

    I've made some changes to the code.
    • Allow different button name to macro name (array is made up of pairs, 1st=button name, 2nd=macro name)
    • Delete Form after close (Thanks Norie)
    • Unload Form before running sub
    • Button width set by len of largest button name string
    • Attempted to calc a more pleasing ratio of columns to rows (THIS STILL WIP )

    Updated Module below:

    Option Explicit
    Option Private Module
    
    Private objFrm As Object
    
    Private Const bytMarginW    As Byte = 5
    Private Const bytMarginH    As Byte = 7
    Private Const strFormName   As String = "UserForm1" 'keep the temp form on this name
    Rem DON'T RENAME! After corrupting several workbooks beyond repair, don't recommend renaming a form while adding it to workbook
    
    Public Sub CreateTempMenuForm(ByRef varMacroArray As Variant, Optional ByVal strFormCaption As String)
    '\ Creates a Temporary Menu form for calling various macros. Form should be deleted on exit.
    
    
    '   delete Temp Menu UserForm (In case wasn't deleted last time it was run)
        Call KillTheTemporaryMenuForm(strFormName)
    
    '   set object as userform
        Set objFrm = ThisWorkbook.VBProject.VBComponents.Add(3) 'vbext_ct_MSForm
    
    '   set up form controls & code
        Call FormOther
        Call FormButtons(varMacroArray)
    Rem Would like to have the buttons placed in a visually pleasing ratio of rows & columns.
    
    '   add object to Forms collection & show
        With VBA.UserForms.Add(objFrm.Name)
            If Len(strFormCaption) > 0 Then
                .Caption = strFormCaption
            Else
                .Caption = "MENU" 'add generic title if not provided by user
            End If
            .Show
        End With
    Rem When a new UserForm is created, it exists in VBA's memory however it isn't officially part of the project yet. _
    So you need to use the Add method to formally enroll UserForm1 into the collection of UserForms before it can be used as a form.
    
    '   remove form after closing
        ThisWorkbook.VBProject.VBComponents.Remove objFrm
    End Sub
    
    Public Sub KillTheTemporaryMenuForm(ByRef strTempForm As String)
    Dim vbComp As Object
    
        For Each vbComp In ThisWorkbook.VBProject.VBComponents
            If vbComp.Type = 3 Then
                If vbComp.Name = strTempForm Then
                    Debug.Print vbNewLine & Now() & ": About to remove Form: " & vbComp.Name
                    ThisWorkbook.VBProject.VBComponents.Remove vbComp
                    Exit For
                End If
            End If
        Next vbComp
    End Sub
    
    Private Sub FormOther()
    Dim lngLines As Long
    
        With objFrm.CodeModule
        'Activate (sets form position)
            lngLines = .CountOfLines
            .InsertLines lngLines + 1, "Sub UserForm_Activate"
            .InsertLines lngLines + 2, vbTab & "With Me"
            .InsertLines lngLines + 3, vbTab & vbTab & ".StartUpPosition = 0"
            .InsertLines lngLines + 4, vbTab & vbTab & ".Left = Application.Left + (0.5 * Application.Width) - (0.5 * .Width)"
            .InsertLines lngLines + 5, vbTab & vbTab & ".Top = Application.Top + (0.5 * Application.Height) - (0.5 * .Height)"
            .InsertLines lngLines + 6, vbTab & "End With"
            .InsertLines lngLines + 7, "End Sub"
        'QueryClose (kill form on close)
            lngLines = .CountOfLines + 1
            .InsertLines lngLines + 1, "Private Sub UserForm_QueryClose(ByRef Cancel As Integer, ByRef CloseMode As Integer)"
            .InsertLines lngLines + 2, vbTab & "If CloseMode = vbFormControlMenu Then Call KillTheTemporaryMenuForm(Me.Name)"
            .InsertLines lngLines + 3, "End Sub"
        End With
    End Sub
    
    Private Sub FormButtons(ByRef varMacroArray As Variant)
    Dim bytMaxRow       As Byte
    Dim bytMaxCol       As Byte
    Dim lngBtnW         As Long
    Dim bytCountButtons As Byte
    Dim bytLoopBtn      As Byte
    Dim bytLoopCol      As Byte
    Dim Btn             As MSForms.CommandButton
    Dim lngTop          As Long
    Dim lngLeft         As Long
    Dim lngLines        As Long
    Dim avarNamesButtons    As Variant
    Dim avarNamesSubs       As Variant
    
    '   create two sub arrays from input array
        avarNamesSubs = Extract1DArrayOfElementsFrom1DArray(varMacroArray, True)
        avarNamesButtons = Extract1DArrayOfElementsFrom1DArray(varMacroArray, False)
        Erase varMacroArray
    
    '   for grid of buttons - count buttons needed + calc max rows & max cols needed
        Call SetMaxRowsAndCols(avarNamesButtons, bytMaxRow, bytMaxCol, bytCountButtons)
    
    '   set button width
        lngBtnW = ReturnLongestLenFrom1DArrElement(avarNamesButtons)
        lngBtnW = lngBtnW * 4.2
    
    '   loop to add buttons
        lngLeft = bytMarginW
        lngTop = bytMarginH
        bytLoopCol = 0
    
        For bytLoopBtn = 1 To bytCountButtons
            'add new button - position
            Set Btn = objFrm.Designer.Controls.Add("forms.CommandButton.1")
            With Btn
                .Left = lngLeft
                .Top = lngTop
                .Width = lngBtnW
                If Not bytLoopBtn = bytCountButtons Then
                    .Caption = avarNamesButtons(bytLoopBtn)
                    '.WordWrap = True
                Else 'last button is exit button
                    .Caption = "Exit"
                    .Accelerator = "X"
                End If
            End With
            'add new button - code
            With objFrm.CodeModule
                lngLines = .CountOfLines + 1
                .InsertLines lngLines + 1, "Sub CommandButton" & bytLoopBtn & "_Click()"
                If Not bytLoopBtn = bytCountButtons Then
                    .InsertLines lngLines + 2, vbTab & "Unload Me" 'don't want form showing OR loaded while sub is running
                    .InsertLines lngLines + 3, vbTab & "Call " & avarNamesSubs(bytLoopBtn)
                    '.InsertLines lngLines + 4, vbTab & "Call KillTheTemporaryMenuForm(Me.Name)" 'want Temp Form deleted after sub runs
                    .InsertLines lngLines + 5, "End Sub"
                    '.InsertLines lngLines + 2, vbTab & "Call KillTempMenuBeforeRunningSub(Me.Name," & Chr(34) & avarNamesSubs(bytLoopBtn) & Chr(34) & ")"
                Else 'last button is exit button
                    .InsertLines lngLines + 2, vbTab & "Call KillTheTemporaryMenuForm(Me.Name)"
                    .InsertLines lngLines + 3, "End Sub"
                    Exit For
                End If
            End With
            'add new button - completed loop
            bytLoopCol = bytLoopCol + 1
            'set position next button
            If bytLoopCol = bytMaxCol Then
                'start next row
                bytLoopCol = 0
                lngLeft = bytMarginW
                lngTop = lngTop + bytMarginH + Btn.Height
            Else 'start next col
                lngLeft = lngLeft + bytMarginW + lngBtnW
            End If
        Next bytLoopBtn
    
    '   remove
        Erase avarNamesButtons
        Erase avarNamesSubs
        bytLoopBtn = vbNull
        bytLoopCol = vbNull
        lngTop = vbNull
        lngLeft = vbNull
        lngLines = vbNull
        bytCountButtons = vbNull
    
    '   set Form dimensions
        With objFrm
            .Properties("Height") = (Btn.Height + bytMarginH) * (bytMaxRow + 1) - bytMarginH
            .Properties("Width") = (lngBtnW + bytMarginW) * (bytMaxCol) + (bytMarginW * 2)
        End With
    End Sub
    
    Private Sub SetMaxRowsAndCols(ByRef varMacroArray As Variant, _
                                    ByRef bytMaxRow As Byte, _
                                    ByRef bytMaxCol As Byte, _
                                    ByRef bytCountButtons As Byte)
    
    Dim bytTest As Byte
    
    '   Count no. of buttons required
        bytCountButtons = UBound(varMacroArray) - LBound(varMacroArray) + 1 '+1 to make result inclusive
    
    '   add 1 more button (for the Exit button)
        bytCountButtons = bytCountButtons + 1
    
    '   determine how many rows/columns to use
    
    '   use preset layouts for low number of buttons
        Select Case bytCountButtons
            Case Is < 5: 'i x 1
                bytMaxRow = bytCountButtons
                bytMaxCol = 1
            Case 5 To 6:
                bytMaxRow = 3
                bytMaxCol = 2
            Case 7 To 8:
                bytMaxRow = 4
                bytMaxCol = 2
            Case 9:
                bytMaxRow = 3
                bytMaxCol = 3
            Case 10: '2 x 5
                bytMaxRow = 5
                bytMaxCol = 2
            Case 11 To 12:
                bytMaxRow = 4
                bytMaxCol = 3
            Case 13 To 15:
                bytMaxRow = 5
                bytMaxCol = 3
            Case 16
                bytMaxRow = 4
                bytMaxCol = 4
            Case 17 To 18
                bytMaxRow = 6
                bytMaxCol = 3
            Case Else
            End Select
    
        If bytMaxCol > 0 Then Exit Sub
    
    '   somehow work out a formula to pick a pleasing ratio of rows to columns...
        bytTest = bytCountButtons
    
        Do Until bytMaxCol > 0
            If bytTest Mod 5 = 0 Then
                bytMaxCol = 5
            ElseIf bytTest Mod 7 = 0 Then bytMaxCol = 7
            ElseIf bytTest Mod 9 = 0 Then bytMaxCol = 9
            End If
            bytTest = bytTest + 1
        Loop
    
    Solved:
        'work out rows from no. of columns chosen
        bytMaxRow = bytCountButtons / bytMaxCol
    End Sub
    Last edited by mc84excel; 10-14-2013 at 09:48 PM.

  12. #12
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Re: Add Command Buttons to User Form at Runtime

    Some array code is required for the updated module in the previous post (have to do separate post due to exceeded character limit!)


    Option Explicit
    Option Private Module
    Option Base 1
    
    
    Public Function ReturnLongestLenFrom1DArrElement(ByRef var1DArray As Variant) As Long
        Dim lngMaxLen As Long
        Dim bytElement As Byte
    
    '   validate non optional arguments
        If Not NoOfDimensionsInArray(var1DArray) = 1 Then Exit Function 'input must be 1D array
    
        For bytElement = LBound(var1DArray) To UBound(var1DArray)
            If Len(var1DArray(bytElement)) > lngMaxLen Then lngMaxLen = Len(var1DArray(bytElement))
        Next bytElement
    
        ReturnLongestLenFrom1DArrElement = lngMaxLen
    End Function
    
    Public Function Extract1DArrayOfElementsFrom1DArray(ByRef var1DArray As Variant, ByVal blnEven As Boolean) As Variant
        Dim avarOutput  As Variant
        Dim bytElement  As Byte
    
    '   validate non optional arguments
        If Not NoOfDimensionsInArray(var1DArray) = 1 Then Exit Function 'input must be 1D array
        bytElement = UBound(var1DArray) - LBound(var1DArray) + 1
        If Not bytElement Mod 2 = 0 Then Exit Function 'input must have even number of elements
    
    '   convert input array to Base 1 if not already (makes it easier to work with)
        If LBound(var1DArray) = 0 Then ReDim Preserve var1DArray(LBound(var1DArray) + 1 To UBound(var1DArray) + 1)
    
    '   create output array
        bytElement = bytElement / 2
        ReDim avarOutput(1 To bytElement)
        If blnEven = True Then
            'all even elements
            For bytElement = LBound(var1DArray) To UBound(var1DArray)
                If bytElement Mod 2 = 0 Then avarOutput(bytElement / 2) = var1DArray(bytElement)
            Next bytElement
        Else
            'all odd elements
            For bytElement = LBound(var1DArray) To UBound(var1DArray)
                If Not bytElement Mod 2 = 0 Then avarOutput((bytElement + 1) / 2) = var1DArray(bytElement)
            Next bytElement
        End If
    
        Extract1DArrayOfElementsFrom1DArray = avarOutput
    End Function

  13. #13
    Forum Guru Kyle123's Avatar
    Join Date
    03-10-2010
    Location
    Leeds
    MS-Off Ver
    365 Win 11
    Posts
    7,239

    Re: Add Command Buttons to User Form at Runtime

    Out of interest, what's the use case for this? Wouldn't it just be easier to add controls dynamically in the normal way without adding them to the project?

  14. #14
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Re: Add Command Buttons to User Form at Runtime

    Quote Originally Posted by Kyle123 View Post
    Out of interest, what's the use case for this?
    I need a userform to act as a temporary menu for the end user to choose an option from. The number of buttons I require will keep changing over time.

    The code is currently working but I have two headaches:
    1. Choosing an aesthetic layout of buttons & form height/width ratio. http://www.excelforum.com/excel-gene...d-outputs.html
    2. I can't use breakpoints in the sub called from the button. http://www.excelforum.com/excel-prog...t-runtime.html


    Quote Originally Posted by Kyle123 View Post
    Wouldn't it just be easier to add controls dynamically in the normal way without adding them to the project?
    I discovered yesterday that I had learnt Userforms wrong. I need to unlearn everything I thought I knew about UserForms and re-learn it correctly

    I'm confused "adding controls without adding them to the project"? Could you give a quick demo?
    Last edited by mc84excel; 11-21-2013 at 06:15 PM.

  15. #15
    Forum Guru Izandol's Avatar
    Join Date
    03-29-2012
    Location
    *
    MS-Off Ver
    Excel 20(03|10|13)
    Posts
    2,581

    Re: Add Command Buttons to User Form at Runtime

    Why do you not use the Ribbon or contextmenus if you require a menu? If you must use a userform may you not use comboboxes or listboxes over so many buttons?

  16. #16
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Re: Add Command Buttons to User Form at Runtime

    Quote Originally Posted by Izandol View Post
    Why do you not use the Ribbon or contextmenus if you require a menu? If you must use a userform may you not use comboboxes or listboxes over so many buttons?
    Thanks. Both are good suggestions. And normally I would do just that.

    The thing is I have this one-off project where I need to use a UserForm to display a grid of buttons.

  17. #17
    Forum Guru Norie's Avatar
    Join Date
    02-02-2005
    Location
    Stirling, Scotland
    MS-Off Ver
    Microsoft Office 365
    Posts
    19,644

    Re: Add Command Buttons to User Form at Runtime

    mc84excel

    Is all this code you've posted just to allow the user to choose an option?

  18. #18
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Re: Add Command Buttons to User Form at Runtime

    Quote Originally Posted by Norie View Post
    Is all this code you've posted just to allow the user to choose an option?
    Yes and no - There is more to it than just selecting an option. I can't say much more than that. Anyway I broke down the code into portions and only uploaded the part that I need solving.

    (But yes I am guilty of code bloat on this particular project if that's what you are inferring ).

  19. #19
    Forum Guru Norie's Avatar
    Join Date
    02-02-2005
    Location
    Stirling, Scotland
    MS-Off Ver
    Microsoft Office 365
    Posts
    19,644

    Re: Add Command Buttons to User Form at Runtime

    Any chance you could give us a hint of what you are actually trying to do?

  20. #20
    Forum Guru Kyle123's Avatar
    Join Date
    03-10-2010
    Location
    Leeds
    MS-Off Ver
    365 Win 11
    Posts
    7,239

    Re: Add Command Buttons to User Form at Runtime

    I'm on my ipad, so I can't really give you an example for your specific question, so here are a couple of more general examples:

    An answer I wrote on SO: http://stackoverflow.com/a/10596866/1240154 - this is the most simple

    Possibly a little bit closer to what you are trying to do, but more complex - there's more functionality than you need here but it adds buttons dynamically to a grid of buttons: http://www.excelforum.com/excel-prog...eferences.html

    I'm sure I've given you an example of this before though when you wanted a custom drop down box where you could add rows

  21. #21
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Re: Add Command Buttons to User Form at Runtime

    Quote Originally Posted by Kyle123 View Post
    Possibly a little bit closer to what you are trying to do, but more complex - there's more functionality than you need here but it adds buttons dynamically to a grid of buttons: http://www.excelforum.com/excel-prog...eferences.html
    You're right, this concept is closer to what I am working on. Interesting

    Quote Originally Posted by Kyle123 View Post
    I'm sure I've given you an example of this before though when you wanted a custom drop down box where you could add rows
    You did and I appreciate it. I found it very helpful. Unfortunately my class skills are none-existent. I will learn them someday (Just working my way up by learning custom types!)

  22. #22
    Forum Guru Kyle123's Avatar
    Join Date
    03-10-2010
    Location
    Leeds
    MS-Off Ver
    365 Win 11
    Posts
    7,239

    Re: Add Command Buttons to User Form at Runtime

    I think that this is what you are trying to do:

    Events handling class - called cButtons - In this case we don't strictly need properties, we could have made the private declarations public and dispensed with them, but this is better practice
    Option Explicit
    
    Private p_sSubToExecute As String
    Private WithEvents oBtn As MSForms.CommandButton
    
    Public Property Let SubToExecute(RHS As String)
        p_sSubToExecute = RHS
    End Property
    Public Property Get SubToExecute() As String
        SubToExecute = p_sSubToExecute
    End Property
    
    Public Property Set Button(RHS As MSForms.CommandButton)
        Set oBtn = RHS
    End Property
    
    Private Sub oBtn_Click()
        Application.Run Me.SubToExecute
    End Sub
    Blank userform for building the menu - called ufBaseForm
    'To hold the classes, object level so the classes don't go out of scope once the init sub has run
    Dim oColl As Collection
    
    Public Sub ShowFromArray(procs() As String)
        Dim x As Long
        Dim oBtn As MSForms.CommandButton
        Dim oBtnEvent As cButtons
        
        Set oColl = New Collection
        
        'Loop through the procedures and add a button for each
        For x = LBound(procs) To UBound(procs)
            'Add the button and set the properties
            Set oBtn = Me.Controls.Add("Forms.CommandButton.1")
            oBtn.Top = 30 * x
            oBtn.Height = 30
            oBtn.Caption = procs(x)
            oBtn.Left = 30
            
            'Create an instance of our event handling class for each button
            Set oBtnEvent = New cButtons
            Set oBtnEvent.Button = oBtn 'Tell it which button it should listen for events from
            oBtnEvent.SubToExecute = procs(x) 'Tell it which proc to run when clicked
            
            oColl.Add oBtnEvent 'Stick it in the collection
        Next x
        
        Me.Show
    
    End Sub
    Module containing subs to run and code to kick things off
    Option Explicit
    
    Public Sub Test1()
        MsgBox "Test1"
    End Sub
    
    Public Sub Test2()
        MsgBox "Test2"
    End Sub
    
    Private Sub Load_Form()
    
        Dim uf As ufBaseForm
        Dim procArray(1 To 2) As String
    
        procArray(1) = "Test1"
        procArray(2) = "Test2"
        
        Set uf = New ufBaseForm
        ufBaseForm.ShowFromArray procArray
        
        Unload uf
    
    End Sub

  23. #23
    Forum Guru Kyle123's Avatar
    Join Date
    03-10-2010
    Location
    Leeds
    MS-Off Ver
    365 Win 11
    Posts
    7,239

    Re: Add Command Buttons to User Form at Runtime

    I reckon this is a better approach, it's probably a bit more logical

    Events handling class - called cButtons
    Option Explicit
    
    Private p_sSubToExecute As String
    Private p_oParent As ufBaseForm
    Private WithEvents oBtn As MSForms.CommandButton
    Public Property Set Parent(RHS As ufBaseForm)
        Set p_oParent = RHS
    End Property
    Public Property Get Parent() As ufBaseForm
        Set Parent = p_oParent
    End Property
    Public Property Let SubToExecute(RHS As String)
        p_sSubToExecute = RHS
    End Property
    Public Property Get SubToExecute() As String
        SubToExecute = p_sSubToExecute
    End Property
    Public Property Set Button(RHS As MSForms.CommandButton)
        Set oBtn = RHS
    End Property
    
    Private Sub oBtn_Click()
        Me.Parent.Btn_Click Me.SubToExecute
    End Sub
    Blank userform for building the menu - called ufBaseForm
    Option Explicit
    'To hold the classes, object level so the classes don't go out of scope once the init sub has run
    Dim oColl As Collection
    
    Public Sub ShowFromArray(procs() As String)
        Dim x As Long
        Dim oBtn As MSForms.CommandButton
        Dim oBtnEvent As cButtons
        
        Set oColl = New Collection
        
        'Loop through the procedures and add a button for each
        For x = LBound(procs) To UBound(procs)
            'Add the button and set the properties
            Set oBtn = Me.Controls.Add("Forms.CommandButton.1")
            oBtn.Top = 30 * x
            oBtn.Height = 30
            oBtn.Caption = procs(x)
            oBtn.Left = 30
            
            'Create an instance of our event handling class for each button
            Set oBtnEvent = New cButtons
            Set oBtnEvent.Parent = Me
            Set oBtnEvent.Button = oBtn 'Tell it which button it should listen for events from
            oBtnEvent.SubToExecute = procs(x) 'Tell it which proc to run when clicked
            
            oColl.Add oBtnEvent 'Stick it in the collection
        Next x
        
        Me.Show
    
    End Sub
    Public Sub Btn_Click(sSubToRun As String)
        Me.Hide
        Application.Run sSubToRun
        Me.Show
    End Sub
    Module containing subs to run and code to kick things off
    Option Explicit
    
    Public Sub Test1()
        MsgBox "Test1"
    End Sub
    
    Public Sub Test2()
        MsgBox "Test2"
    End Sub
    
    Private Sub Load_Form()
    
        Dim uf As ufBaseForm
        Dim procArray(1 To 2) As String
    
        procArray(1) = "Test1"
        procArray(2) = "Test2"
        
        Set uf = New ufBaseForm
        ufBaseForm.ShowFromArray procArray
        
    End Sub

  24. #24
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Thumbs up Re: Add Command Buttons to User Form at Runtime

    Thanks a lot

    I've tested it out now and have a few questions/comments:
    1. I don't get the "Can't enter break mode" issue! For my learning - Why does your code not cause this?
    2. :S It uses the same button name as the sub name. But I should be able to rework the code to pick it up from every second element...
    3. The buttons appear as a column.
    Attached Files Attached Files
    Last edited by mc84excel; 11-28-2013 at 07:55 PM.

  25. #25
    Forum Guru Kyle123's Avatar
    Join Date
    03-10-2010
    Location
    Leeds
    MS-Off Ver
    365 Win 11
    Posts
    7,239

    Re: Add Command Buttons to User Form at Runtime

    Quote Originally Posted by mc84excel View Post
    I don't get the "Can't enter break mode" issue! For my learning - Why does your code not cause this?
    I've never had this, so I can only speculate, you're trying to debug code whilst automating the IDE, trying to do two things at once sounds like a recipe for disaster so at a guess MS have stopped you doing it to prevent you doing any damage/save headaches.

    Since my code doesn't attempt to automate the IDE (and I can't actually think of an actual instance where I'd want to do so, except for maybe making an add-in to make development easier, something like MZTools), this scenario never occurs.
    Quote Originally Posted by mc84excel View Post
    It uses the same button name as the sub name
    Why do you care what the buttons are called? You reference them by location in the collection
    Quote Originally Posted by mc84excel View Post
    The buttons appear as a column
    Indeed, I'm not doing it all for you besides I can't even see what you're trying to do since I can't run your code as I've no intention of allowing trusted access to the VBA project.

    Quote Originally Posted by mc84excel View Post
    Unfortunately my class skills are none-existent. I will learn them someday
    Learn them instead of trying to automate the IDE, they're far more useful, less hacky and not really complicated. Automating the IDE is nasty you've done UDTs now, you've got no excuse...

  26. #26
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Re: Add Command Buttons to User Form at Runtime

    Thanks for the info Kyle

    Quote Originally Posted by Kyle123 View Post
    Why do you care what the buttons are called?
    I have my reasons

    Quote Originally Posted by Kyle123 View Post
    Indeed, I'm not doing it all for you
    I wasn't expecting you to. Many thanks for what you provided.

    Quote Originally Posted by Kyle123 View Post
    you've done UDTs now, you've got no excuse...
    Almost done learning UDTs But you're right. I can't keep putting it off

  27. #27
    Valued Forum Contributor
    Join Date
    08-29-2012
    Location
    In lockdown
    MS-Off Ver
    Excel 2010 (2003 to 2016 but 2010 for choice)
    Posts
    1,766

    Re: Add Command Buttons to User Form at Runtime

    SOLVED. I've tweaked Kyles code so that the form does what I want it to.
    Below is a generic version for anyone who needs this code.

    Form Code:
    Option Explicit
    
    'multiplier needs fine tuning
    Private Const msngcWidthMultiplier  As Single = 4.31 '7.8
    
    'margins for buttons. these are fine. do not change!
    Private Const mbytcMarginW          As Byte = 5
    Private Const mbytcMarginH          As Byte = 7
    Private Const mbytcButtonH          As Byte = 24
    
    'name of sub to close form
    Private Const mstrcSubCloseForm     As String = "CloseTempForm"
    
    Private Sub UserForm_Activate()
        With Me
            .StartUpPosition = 0
            .Left = Application.Left + (0.5 * Application.Width) - (0.5 * .Width)
            .Top = Application.Top + (0.5 * Application.Height) - (0.5 * .Height)
        End With
    End Sub
    
    Private Sub UserForm_QueryClose(ByRef Cancel As Integer, ByRef CloseMode As Integer)
        Set oColl = Nothing
        Application.Run mstrcSubCloseForm
        'If CloseMode = vbFormControlMenu Then Cancel = True
    End Sub
    
    Public Sub SetButtonsFromArrays(ByRef procs() As Variant)
        Dim avarNamesButtons    As Variant
        Dim avarNamesSubs       As Variant
        Dim bytMaxRow           As Long
        Dim bytMaxCol           As Long
        Dim bytCountButtons     As Long
        Dim lngBtnW             As Long
        Dim lngTop              As Long
        Dim lngLeft             As Long
        Dim bytLoopBtn          As Long
        Dim strSubName          As String
        Dim bytBracketStart     As Byte
        Dim bytLoopCol          As Long
        Dim oBtn                As MSForms.CommandButton
        Dim oBtnEvent           As cButtons
    
    
        Set oColl = New Collection
    
        'create two sub arrays from input array
        avarNamesSubs = Get1DArr_varEveryEvenElement(procs(), False)
        avarNamesButtons = Get1DArr_varEveryEvenElement(procs(), True)
        Erase procs()
    
        'buttons - calc required total no + max row + max col
        Call SetMaxRowsAndCols(avarNamesButtons, bytMaxRow, bytMaxCol, bytCountButtons)
    
        'buttons - calc width
        lngBtnW = Get1DArr_lngElementMaxLen(avarNamesButtons)
        lngBtnW = lngBtnW * msngcWidthMultiplier
    
        'Loop through the procedures and add a button for each
        lngLeft = mbytcMarginW
        lngTop = mbytcMarginH
        bytLoopCol = 0
    
        For bytLoopBtn = 1 To bytCountButtons
    
            strSubName = vbNullString
            bytBracketStart = vbNull
            
            'Add the button and set the properties
            Set oBtn = Me.Controls.Add("Forms.CommandButton.1")
            With oBtn
                .Left = lngLeft
                .Top = lngTop
                .Width = lngBtnW
                .Height = mbytcButtonH
                Select Case bytLoopBtn
                Case bytCountButtons
                'last button is exit button
                    .Caption = "Exit"
                    .Accelerator = "X"
                Case Else
                    .Caption = avarNamesButtons(bytLoopBtn)
                    strSubName = avarNamesSubs(bytLoopBtn)
                    bytBracketStart = InStr(1, strSubName, "(")
                    If bytBracketStart = 0 Then
                        .Tag = 0
                    Else
                        bytBracketStart = bytBracketStart
                        .Tag = Mid(strSubName, bytBracketStart + 1, 2)
                    End If
                End Select
            End With
    
            'Create an instance of our event handling class for each button
            Set oBtnEvent = New cButtons
            Set oBtnEvent.Parent = Me
            Set oBtnEvent.Button = oBtn 'Tell it which button it should listen for events from
    
            Select Case bytLoopBtn
            Case bytCountButtons
            'last button is exit button
                oBtnEvent.SubToExecute = mstrcSubCloseForm
                oColl.Add oBtnEvent 'Stick it in the collection
            Case Else
                If bytBracketStart = 0 Then
                    oBtnEvent.SubToExecute = strSubName 'Tell it which proc to run when clicked
                Else
                    oBtnEvent.SubToExecute = Left(strSubName, bytBracketStart - 1)
                End If
                oColl.Add oBtnEvent 'Stick it in the collection
            End Select
    
            'add new button - completed loop
            bytLoopCol = bytLoopCol + 1
            'set position next button
            If bytLoopCol = bytMaxCol Then
                'start next row
                bytLoopCol = 0
                lngLeft = mbytcMarginW
                lngTop = lngTop + mbytcMarginH + oBtn.Height
            Else 'start next col
                lngLeft = lngLeft + mbytcMarginW + lngBtnW
            End If
        Next bytLoopBtn
    
    
        'set Form dimensions
        Me.Height = (mbytcButtonH + mbytcMarginH) * (bytMaxRow + 1) - mbytcMarginH
        Me.Width = (lngBtnW + mbytcMarginW) * (bytMaxCol) + (mbytcMarginW * 2)
    
        'remove
        Erase avarNamesButtons
        Erase avarNamesSubs
        bytLoopBtn = vbNull
        bytLoopCol = vbNull
        lngTop = vbNull
        lngLeft = vbNull
        bytCountButtons = vbNull
    End Sub
    
    Public Sub Btn_Click(ByVal sSubToRun As String, Optional ByVal varTag As Variant)
        If sSubToRun = mstrcSubCloseForm Then
            Application.Run sSubToRun
            Exit Sub
        End If
    
        Me.Hide
    
        If varTag > 0 Then
            Application.Run sSubToRun, varTag
        Else
            Application.Run sSubToRun
        End If
    
        Me.Show
    End Sub
    
    Private Sub SetMaxRowsAndCols(ByRef varMacroArray As Variant, _
                                    ByRef bytMaxRow As Long, _
                                    ByRef bytMaxCol As Long, _
                                    ByRef bytCountButtons As Long)
    
        Dim bytTest As Long
    
        'Count no. of buttons required
        bytCountButtons = UBound(varMacroArray) - LBound(varMacroArray) + 1 '+1 to make result inclusive
    
        'add 1 more button (for the Exit button)
        bytCountButtons = bytCountButtons + 1
    
        'determine how many rows/columns to use
    
        'use preset layouts for low number of buttons
        Select Case bytCountButtons
            Case Is < 5: 'i x 1
                bytMaxRow = bytCountButtons
                bytMaxCol = 1
            Case 5 To 6:
                bytMaxRow = 3
                bytMaxCol = 2
            Case 7 To 8:
                bytMaxRow = 4
                bytMaxCol = 2
            Case 9:
                bytMaxRow = 3
                bytMaxCol = 3
            Case 10: '2 x 5
                bytMaxRow = 5
                bytMaxCol = 2
            Case 11 To 12:
                bytMaxRow = 4
                bytMaxCol = 3
            Case 13 To 15:
                bytMaxRow = 5
                bytMaxCol = 3
            Case 16
                bytMaxRow = 4
                bytMaxCol = 4
            Case 17 To 18
                bytMaxRow = 6
                bytMaxCol = 3
            Case 19 To 20
                bytMaxRow = 5
                bytMaxCol = 4
            Case 21
                bytMaxRow = 7
                bytMaxCol = 3
            Case Else
            End Select
    
        If bytMaxCol > 0 Then Exit Sub
    
    '   somehow work out a formula to pick a pleasing ratio of rows to columns...
    
    Rem maybe something closer to a Fibonacci/Golden Ratio
    '=SQRT(A1*((1+SQRT(5))/2)^3)/((1+SQRT(5))/2)
    '=SQRT(A1*((1+SQRT(5))/2)^3)/((1+SQRT(5))/2)^2
    
    
        bytTest = bytCountButtons
    
        Do Until bytMaxCol > 0
            If bytTest Mod 5 = 0 Then
                bytMaxCol = 5
            ElseIf bytTest Mod 7 = 0 Then bytMaxCol = 7
            ElseIf bytTest Mod 9 = 0 Then bytMaxCol = 9
            End If
            bytTest = bytTest + 1
        Loop
    
    Solved:
        'work out rows from no. of columns chosen
        bytMaxRow = bytCountButtons / bytMaxCol
    End Sub
    Module code:
    Option Explicit
    Option Private Module
    
    'To hold the classes, object level so the classes don't go out of scope once the init sub has run
    Public oColl As Collection
    
    Public Sub CreateTempFormAlt(ByRef varMacroArray() As Variant, _
                                Optional ByVal strFormCaption As String = "MENU")
    
        Dim uf As ufBaseForm: Set uf = New ufBaseForm
    
        ufBaseForm.SetButtonsFromArrays varMacroArray
        ufBaseForm.Caption = strFormCaption
        ufBaseForm.Show
    End Sub
    
    Public Sub CloseTempForm()
        On Error Resume Next
        Application.DisplayAlerts = False
        Application.EnableEvents = False
    
        Set oColl = Nothing
    
        Debug.Print "attempt to unload form"
        Unload ufBaseForm
    
        Application.EnableEvents = True
        Application.DisplayAlerts = True
        On Error GoTo 0
    End Sub
    Class code:
    Option Explicit
    
    Private p_sSubToExecute     As String
    Private p_oParent           As ufBaseForm
    Private WithEvents oBtn     As MSForms.CommandButton
    
    Public Property Set Parent(RHS As ufBaseForm)
        Set p_oParent = RHS
    End Property
    Public Property Get Parent() As ufBaseForm
        Set Parent = p_oParent
    End Property
    
    Public Property Let SubToExecute(RHS As String)
        p_sSubToExecute = RHS
    End Property
    Public Property Get SubToExecute() As String
        SubToExecute = p_sSubToExecute
    End Property
    
    Public Property Set Button(RHS As MSForms.CommandButton)
        Set oBtn = RHS
    End Property
    
    Private Sub oBtn_Click()
        Call Me.Parent.Btn_Click(Me.SubToExecute, oBtn.Tag)
    End Sub

    There are a few array functions used by the code above, see below:
    Public Function Get1DArr_lngElementMaxLen(ByRef var1DArray As Variant) As Long
        Dim lngMaxLen As Long
        Dim bytElement As Byte
    
    '   validate non optional arguments
        If Not NoOfDimensionsInArray(var1DArray) = 1 Then Exit Function 'input must be 1D array
    '   loop
        For bytElement = LBound(var1DArray) To UBound(var1DArray)
            If Len(var1DArray(bytElement)) > lngMaxLen Then lngMaxLen = _
                Len(var1DArray(bytElement))
        Next bytElement
        Get1DArr_lngElementMaxLen = lngMaxLen
    End Function
    Public Function NoOfDimensionsInArray(ByVal varArray As Variant) As Byte
    '\ returns number of dimensions as 0 - 4
    '\ 0 = not an array, 4 = anything above 3 dimensions
        Dim bytDimNum       As Byte
        Dim varErrorCheck   As Variant
    
        On Error GoTo FinalDimension
        For bytDimNum = 1 To 4
            varErrorCheck = LBound(varArray, bytDimNum)
        Next
    FinalDimension:
        On Error GoTo 0
        NoOfDimensionsInArray = bytDimNum - 1
    End Function
    Public Function Get1DArr_varEveryEvenElement(ByRef var1DArray As Variant, _
        Optional ByVal blnOddVersion As Boolean = False) As Variant
        Dim avarOutput      As Variant
        Dim bytElementIn    As Byte
        Dim bytElementOut   As Byte
    
    '   validate non optional arguments
        If Not NoOfDimensionsInArray(var1DArray) = 1 Then Exit Function 'input must be 1D array
        
    '   convert input array to Base 1 if not already
        Call Force1DArrToBase1(var1DArray)
    '   create output array
        If Not bytElementIn Mod 2 = 0 Then
            bytElementOut = UBound(var1DArray) + 1 / 2
        Else
            bytElementOut = UBound(var1DArray) / 2
        End If
        ReDim avarOutput(1 To bytElementOut)
    '   loop
        bytElementOut = 1
        If blnOddVersion = False Then
            'all even elements
            For bytElementIn = LBound(var1DArray) To UBound(var1DArray)
                If bytElementIn Mod 2 = 0 Then
                    avarOutput(bytElementOut) = var1DArray(bytElementIn)
                    bytElementOut = bytElementOut + 1
                End If
            Next bytElementIn
        Else
            'all odd elements
            For bytElementIn = LBound(var1DArray) To UBound(var1DArray)
                If Not bytElementIn Mod 2 = 0 Then
                    avarOutput(bytElementOut) = var1DArray(bytElementIn)
                    bytElementOut = bytElementOut + 1
                End If
            Next bytElementIn
        End If
    
        Get1DArr_varEveryEvenElement = avarOutput
    End Function

+ Reply to Thread

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Similar Threads

  1. [SOLVED] Option buttons on a user form
    By Aland2929 in forum Excel Programming / VBA / Macros
    Replies: 3
    Last Post: 07-30-2013, 07:39 AM
  2. [SOLVED] Call command buttons by user-defined names
    By Willardio in forum Excel Programming / VBA / Macros
    Replies: 3
    Last Post: 01-14-2013, 10:58 AM
  3. command buttons with hyperlink to form
    By stats09 in forum Access Tables & Databases
    Replies: 4
    Last Post: 12-10-2010, 01:20 AM
  4. User Form Option & Command Buttons
    By Information Hog in forum Excel Programming / VBA / Macros
    Replies: 2
    Last Post: 08-19-2005, 06:37 PM
  5. Code Behind Buttons on User Form
    By robertguy in forum Excel General
    Replies: 0
    Last Post: 02-09-2005, 11:53 AM

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts

Search Engine Friendly URLs by vBSEO 3.6.0 RC 1