Dynamically Duplicating a page in a Multipage - vba

Good afternoon, So I have been assigned a task where I'm supposed to take a Calendar Userform I made and implement the ability to duplicate as many calendars as needed as tabs on a multipage Userform.
My question is : Is it possible to do so dynamically? I copied over all the controls over to another tab to test it, but all the control buttons get renamed and I'm not able to name them the the same name as the buttons on the 'master page' because of the ambiguity error when naming 2 buttons the same on one userform.
I could write new code using the new button names, but this would not be able to be done dynamically since for ever new tab added, I'd have to have code ready for it prior to creation.
Any help would greatly be appreciated.

Demo Workbook
The key to handling this type of problem is to create a class to hold references to the newly created controls. Using WithEvents will allow you to handle the events of the referenced controls.
Read about WithEvents here: Events And Event Procedures In VBA
In order to make this work you'll have to Set references between the newly created Page controls and their doppelgangers in your class. You'll also need to keep the class references alive by adding them to a global collection, dictionary or array.
In my example I created a subroutine that will iterate over the template page controls, creating and copy the code necessary to declare the variable and set the references into the Windows ClipBoard.
The final step is to copy the event code from the Userform into the class module.
Download Demo Workbook for the code example.
Alternatively, you could replace the Multipage control with a TabStrip control. The difference is that you can have the controls on a TabStrip span across all the Tabs. You could than use the TabStrip1_Change() to update the controls based on the selected tab (TabStrip1.SelectedItem).

First of all I would recommend looking at Thomas Inzina's answer.
However, in one of my worksheets I did the same without relying on WithEvents, so I wanted to show the alternative method.
This method sets names of the controls along a predetermined format (e.g. "Input1_" (counter + 1)) so the controls are easily referenced from other operations.
The controls themselves are within Frames with set Captions, so I can still reference them after they've been copied.
I've edited the code somewhat since I stripped it from a longer procedure, but hopefully it's still intact.
Dim Ctrl As msforms.Control
Dim Mpage As msforms.Control
Dim Ctrl2 As msforms.Control
Dim pge As msforms.Page
Dim L As Double, R As Double
Dim PageName As String, PageTitle As String
Dim counter As Long
counter = 0
Set Mpage = Me.Controls("Multipage_1") 'set Multipage
'count current number of tabs within MPage
For Each pge In Mpage.Pages
counter = counter + 1
Next pge
'set name/title for new page
PageName = "Tab_" & (counter + 1)
PageTitle = "Tab " & (counter + 1)
With Mpage 'add tab
.Pages.Add PageName, PageTitle
.Pages(0).Controls.Copy
.Pages(counter).Paste
End With
'get position of original frame (controls are within this frame)
For Each Ctrl In Mpage.Pages(0).Controls
If TypeOf Ctrl Is msforms.Frame Then
L = Ctrl.Left
R = Ctrl.Top
Exit For
End If
Next
'apply position to new frame
For Each Ctrl In Mpage.Pages(counter).Controls
If TypeOf Ctrl Is msforms.Frame Then
Ctrl.Left = L
Ctrl.Top = R
Exit For
End If
Next
'renames input-controls and removes copied values by looping through frames
'that contain the controls, since frame-captions can be duplicates
For Each Ctrl In Mpage.Pages(counter).Controls
If TypeOf Ctrl Is msforms.Frame Then
Select Case Ctrl.Caption
Case "Input1"
For Each Ctrl2 In Ctrl.Controls
Ctrl2.Name = "Input1_" (counter + 1)
Ctrl2.Text = ""
Next Ctrl2
Case "Input2"
For Each Ctrl2 In Ctrl.Controls
Ctrl2.Name = "Input2_" (counter + 1)
Ctrl2.Text = ""
Next Ctrl2
Case "Input3"
For Each Ctrl2 In Ctrl.Controls
Ctrl2.Name = "Input3_" (counter + 1)
Ctrl2.Text = ""
Next Ctrl2
End Select
End If
Next Ctrl

use a prefix for the control names. eg. tab1_button1, tab2_button1, tab33_button1. then have only one one event handler that services all the events (button presses, checkbox clicks)
here is some info using one sub for multiple buttons in excel vba

Related

Rename TextBoxes on Form after Copying it into a new multipage

I'm dealing with this issue. I have a userform that addapts after the user enters a value (for example, if he wants to add 3 members, the user form creates 3 different pages with the same boxes, after renaming them).
I have this right now:
After pressing "Lanzar", the userform will create more pages with "Datos Educando 2", "Datos Educando 3"... and so on, taking the value introduced on "Educandos a inscribir:"
The code is the following:
Private Sub lanzar_numero_educandos_Click()
Dim l As Double, r As Double
Dim ctl As Control
Me.MultiPage1.Pages(1).Visible = True
If Me.MultiPage1.Pages.Count > 2 Then
For a = Me.MultiPage1.Pages.Count - 1 To 2 Step -1
Me.MultiPage1.Pages.Remove a
Next a
End If
Me.MultiPage1.Pages(1).Visible = True
If educandos_a_inscribir.Value <> 1 Then
For a = 1 To educandos_a_inscribir.Value
MultiPage1.Pages.Add
MultiPage1.Pages(a).Controls.Copy
MultiPage1.Pages(a + 1).Paste
For Each ctl In MultiPage1.Pages(a).Controls
If TypeOf ctl Is MSForms.Frame Then
l = ctl.Left
r = ctl.Top
Exit For
End If
Next
For Each ctl In MultiPage1.Pages(a + 1).Controls
If TypeOf ctl Is MSForms.Frame Then
ctl.Left = l
ctl.Top = r
Exit For
End If
Next
Me.MultiPage1.Pages(a + 1).Caption = "Datos Educando " & a
Next a
Me.MultiPage1.Pages(1).Visible = False
End If
End Sub
Now, the problem I have is that the pages generated with this code have random names on each TextBox, so I'm not able to locate the information introduced from the user.
For example, this is the first page (the one that has the names I already know):
Here, the TextBox "Nombre Educando" is called "nombre_educando_1", so I can locate it easily on code:
The Textboxes created when copying the first page, have random names (like "TextBox 34", "TextBox 35"... and so on), so I'm not able to controll how are they called.
There is a way of generate the pages editing the TextBox names? Like, for example, for the second page generated, the TextBox in the example above should be "nombre_educando_2" and so on.
Thanks!
Each control on the first page has an attribute named Tag. You can value these with unique and meaningful values. When you paste the controls to the new page the Tag values will follow. Then as you loop through the controls on any page, use a Select Ctl.tag statement to identify what to do with the value of the control.

Question on multiple checkboxes launching code

I have a user form and a frame with 35 checkboxes in it, numbered 1 to 35. They represent 35 Named Ranges. I test to see if any of the name ranges are not set, if set correctly the checkbox value is set to TRUE.
I found some code that would allow me to trigger a sub if one of the checkboxes is clicked. That code seems to work, but my check code above also triggers the checkbox events, which I do not want. I only want the sub to run when the checkbox is clicked with the mouse? I can post the code I'm using, but though I'd first ask the question to see if what I would like to do is possible.
Thanks,
Jim
Code in class module:
Public WithEvents ChkBox As MSForms.CheckBox
Public Sub AssignClicks(ctrl As Control)
Set ChkBox = ctrl
End Sub
Public Sub chkBox_Click()
If chkBoxProcess = "Y" Then
'ThisWorkbook.Worksheets("Sheet1").Range(ChkBox.Name).Value = Format(Now, "dd.mm.yyyy")
'MsgBox ("check box number = " & ChkBox.Name & " " & ChkBox.Value)
' Else
End If
End Sub
Code in Forms:
Public Sub UserForm_Initialize()
Dim SheetCount, i As Integer
Dim sh As Worksheet
'Public SheetName, SheetName2, StartOldNewTimeA, OldNewTimeAdd As String
'Initialize the form frmChgNameRng
'Set array values of the day options
'Set array values for 12:00 timeframes
'Set array values for 12:30 timeframes
'Set colors used in Checkboxes
'Set array for Checkboxes (boxes are numbered accross the page, 1 corressponds to Mon_1200/Mon_1230, 8 corresponds to Mon_200/Mon_230, etc.)
'Formulas are placed in the time cells on the left of the page, the macro will add the appropriate value into the Mon_1200 time slot and all other cells update off that cell
chkBoxProcess = "N"
Dim ChkBoxes As cls_ChkBox
Dim ctrl As Control
Set colTickBoxes = New Collection
For Each ctrl In Me.Controls
If TypeName(ctrl) = "CheckBox" Then
Set ChkBoxes = New cls_ChkBox
ChkBoxes.AssignClicks ctrl
colTickBoxes.Add ChkBoxes
End If
Next ctrl
'..... lots of code for Range Name Checks, etc.
End Sub
Your code is conflating control state with model data, and so the only way to tell it "named range 32 is ON", or "named range 13 is OFF", is to alter a checkbox' state, which fires that control's Change event.
There's no way around that, it's just how controls work: they fire a Change event whenever their value changes, regardless of how that's done.
Instead of having controls' state be the data, make the controls' state alter the data.
This requires conceptualizing this data, first: looks like you need to associate a number/index to some Boolean value. An array can do this.
Private namedRangeStates(1 To 35) As Boolean
Note that depending on what you're doing, initializing the state should be feasible by iterating the workbook's Names collection in the UserForm_Initialize handler. Or better, the form could expose a method that takes an array of Boolean values, and copies that state into namedRangeStates.
Now, when a checkbox is modified, make it alter the state:
Private Sub Checkbox31_Change()
namedRangeStates(31) = Checkbox31.Value
End Sub
Your form can expose that state as a property:
Public Property Get NamedRangeState(ByVal index As Long) As Boolean
NamedRangeState = namedRangeStates(index)
End Property
Public Property Let NamedRangeState(ByVal index As Long, ByVal value As Boolean)
namedRangeStates(index) = value
End Property
And now you can modify the enapsulated state independently of the combobox values.

vba - Best Practice - Access Form Tab Control List Box Selected Items

What works ...
I have an Access I have a form, with a folder that has two pages or tabs. Each of these has it's own combobox related to the tabs topic. I wanted a fairly simply way to do this, by looking into the form, folder, tab I wanted, then pulling all selected values. I didn't know I was going to have to double for loop just to get this information.
Regardless, this is my good enough solution..
Private Sub getComboBoxInsideTabControl()
Dim selectedPage As Page
Dim pageIter As Page
Dim ctrl As Control
Dim varItm As Variant
Dim str As String
Set selectedPage = Me.folder1.Pages(1)
' GETS THE CONTROLS ON A PAGE
For Each ctrl In selectedPage.Controls
If ctrl.Name = "fields_lb" Then
MsgBox ("ok...")
' SEARCHES THROUGH A COMBOBOX CONTROLS SELECTED ITEMS
For Each varItm In ctrl.ItemsSelected
str = str & ctrl.ItemData(varItm) & ","
Next varItm
End If
Next ctrl
MsgBox (str)
End Sub
What I had hoped for ...
I really just want to know if there is a more simplistic way of doing it. For example, I kind of wanted something like this:
Dim results As String
Set results = Me.folder1.Pages(1).Controls("fields_lb").ItemsSelected
Is there a better way of doing this I am missing?

Save code-generated elements of form

VBA, Excel 2010.
Given:
MultiPage Userform
Script, which makes copies of a page (based on this question)
Process:
Open form (1 page in multipage, screenshot 1);
Copy a page by running script (2 pages in multipage, screenshot 2);
Close form;
Re-open form (again only 1 page, same as on Step 1);
Script code:
Private Sub CommandButton1_Click()
Dim l As Double, r As Double
Dim ctl As Control
Dim totalPageNum As Integer
'Get number of existing pages
totalPageNum = MultiPage1.Pages.Count
'Add new one
MultiPage1.Pages.Add
'Copy elements
MultiPage1.Pages(0).Controls.Copy
MultiPage1.Pages(totalPageNum).Paste
MultiPage1.Pages(totalPageNum).Caption = "Page" & MultiPage1.Pages.Count
'Copying frame coordinates
For Each ctl In MultiPage1.Pages(0).Controls
If TypeOf ctl Is MSForms.Frame Then
l = ctl.Left
r = ctl.Top
Exit For
End If
Next
For Each ctl In MultiPage1.Pages(totalPageNum).Controls
If TypeOf ctl Is MSForms.Frame Then
ctl.Left = l
ctl.Top = r
Exit For
End If
Next
End Sub
Question: How to make pages to be saved on re-open of form/file (so I could get 2 pages on Step 4)
P.S. Unfortunately, I can't post pictures yet to explain the question in a better way.
Thanks for help!
The only way to do this is to simply hide the UserForm instead of unloading it if it is within the same session of Excel. If you have to close Excel and reopen it then there is not a way to save.
I would suggest saving the info you need to create your pages on a VeryHidden Worksheet and dynamically create the UserForm each time.

VBA How do you copy and paste in a Userform using right-click?

I want to allow users to be able to paste values into TextBoxes in a userForm in VBA. You can use Ctrl-v just fine, but not everyone knows how to do that.
How do I enable copy and pasting using a right-click menu?
I realize this is an old post but I believe there is a more efficient method.
Userform Contextual Menu class code
http://www.andypope.info/vba/uf_contextualmenu.htm
There are even sample excel spreadsheets for the code examples.
The class module handles the construction of the contextual menu, the capture of right clicking in textboxes and the actual Cut. Copy and Paste actions. The class makes use of the userform's ActiveControl object. The code even handles controls within container controls such as Frames and Multipage.
The follow Initialization code, from the userform, shows how simple it is to define and use the class object. You only need declare a variable to the object and then set a reference for each textbox you want to have contextual menu capabilities. You can loop through all controls and automatically reference each textbox.
Private m_colContextMenus As Collection
Private Sub UserForm_Initialize()
Dim clsContextMenu As CTextBox_ContextMenu
Dim cTRL as Control
Set m_colContextMenus = New Collection
For Each cTRL In Me.Controls
Select Case TypeName(cTRL)
Case "TextBox"
'MsgBox cTRL.Name & ": " & Me.Controls(cTRL.Name).Value
Set clsContextMenu = New CTextBox_ContextMenu
With clsContextMenu
Set .TBox = Me.Controls(cTRL.Name)
Set .Parent = Me
End With
m_colContextMenus.Add clsContextMenu, CStr(m_colContextMenus.Count + 1)
Case Else
'MsgBox TypeName(cTRL) & ": " & cTRL.Name
End Select
Next
End Sub
Download example workbook which contains both .xls and .xlsm files
This may be of interest: http://word.mvps.org/faqs/userforms/AddRightClickMenu.htm