Rename TextBoxes on Form after Copying it into a new multipage - vba

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.

Related

VBA Checkbox to Listbox (uncheck option to remove array)

I am struggling with a simple thing and cannot resolve it. I have a userform that user can populate from a textbox manually. I decided to add a checkbox as well to allow the user to populate the same listbox with a specific list of items. To do it,I made a simple checkbox with array. It works perfectly fine. But obviously keeps adding the items every time you check and uncheck it.
Private Sub Checkbox1_Click()
Dim mylist(7) As String
Dim i As Long
mylist(0) = "time"
mylist(1) = "hour"
mylist(2) = "how"
mylist(3) = "test"
mylist(4) = "number"
mylist(5) = "sent"
mylist(6) = "memo"
mylist(7) = "value"
For i = 0 To 7
If CheckBox1.Value = True Then
Finallist.AddItem mylist(i)
End If
Next i
End Sub
I can populate the list when the checkbox is checked with the code above, but struggling to remove the array items from the list when the user unchecks the listbox. I simply need to remove the same items from listbox when user unchecks the same checkbox.
I tried the following solution after the code, but seem to be making something very wrong with it, I understand. Just totally stuck....Could someone help me please?
If checkobx.value=false then
For i = 0 To 7
For j = 0 To FinalList.ListCount - 1
If InStr(Final.List(j), mylist(i)) > 0 Then
Finallist.RemoveItem mylist(i)
End If
Next j
Next i
end if
Try this (explanations in comments and untested):
If CheckBox1.Value Then ‘ if checkbox checked
For i = 0 To 7
NegKeyList.AddItem interrlist(i)
Next
Else ‘otherwise
Dim i As Long
For i = NegKeyList.ListCount - 1 To 0 Step -1 ‘ loop through listbox items from last one backwards
If Not IsError(Application.Match(NegKeyList.List(i), interrlist,0)) Then NegKeyList.RemoveItem i ‘ if current listbox item matches any interrlist array item then remove it from listbox
Next
End If

Dynamically Duplicating a page in a Multipage

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

Populating a dropdown box with already-existing options in VBA?

I'm making an add records form for a spreadsheet of mine, and let's say that I want one of the controls to be a dropdown that is populated by unique entries under a certain column "type". However, I want to also make it such that the dropbox always has a initial option to "add new type" and upon such selection, it becomes a regular text box. How would I do this in VBA?
You cannot change a control type at run time. The easiest thing to do is create a combo box and a text box. Set the text box visibility to false. Then in the onchange event of the combo box your code will unhide the text box and hide the combo box. You will also need a save button so that when it is clicked it will add the option to the drop down, clear the text box, hide the text box, hide the button and unhide the drop down.
Okay, so here's my idea of how to tackle this.
Create 2 hidden elements (Visibility = False), one a TextBox and one a CommandButton.
Populate your ComboBox with the values from the sheet under column "type"
Add one more entry AddItem with wording such as "Add new item..."
When the user selects "Add new item...", change the Visibility of the TextBox & CommandButtons to True
When the user clicks the CommandButton, add the phrase to the column and add a new element to the ComboBox
I have created a mockup UserForm and code that does a little more than just this; it also styles the user entry to sentence case (consistency purposes) and checks to make sure the value isn't already in the column.
Excel Sheet with "type" column
UserForm with name labels
UserForm Code
Private Sub bAdd_Click()
Dim str As String
Dim rng As Range
Dim ro As Integer
'Makes sure there is an entry, adds it to the Sheet and then updates the dropdown
If Len(Me.tbNew) > 0 Then
'Converts user entry to "Sentance Case" for better readability
str = StrConv(Me.tbNew, vbProperCase)
'Finds out if the entry already exists
Set rng = Sheets(1).Range(Sheets(1).Cells(2, 1), Sheets(1).Cells(Sheets(1).Cells(Sheets(1).Rows.Count, 1).End(xlUp).Row, 1))
On Error Resume Next
Err.Number = 0
'Searches for duplicate; if found, then ListIndex of cbColor is modified without inserting new value (prevents duplicates)
ro = rng.Find(str, LookIn:=xlValues, LookAt:=xlWhole).Row
Debug.Print Err.Number
'Ensures a user doesn't add the same value twice
If Err.Number > 0 Then
Sheets(1).Cells(Sheets(1).Cells(Sheets(1).Rows.Count, 1).End(xlUp).Row + 1, 1) = str
Me.cbColor.AddItem StrConv(Me.tbNew, vbProperCase), Me.cbColor.ListCount - 1
Me.cbColor.ListIndex = Me.cbColor.ListCount - 2
Else
Me.cbColor.ListIndex = ro - 2
End If
'Resets and hides user form entries
Me.tbNew = vbNullString
Me.tbNew.Visible = False
Me.bAdd.Visible = False
End If
End Sub
Private Sub bClose_Click()
Unload Me
End Sub
Private Sub cbColor_Change()
'Visibility is toggled based on if the user selected the last element in the dropdown
Me.bAdd.Visible = Me.cbColor.ListIndex = Me.cbColor.ListCount - 1
Me.tbNew.Visible = Me.cbColor.ListIndex = Me.cbColor.ListCount - 1
End Sub
Private Sub UserForm_Initialize()
'Populate from the sheet
For a = 2 To Sheets(1).Cells(Cells(Sheets(1).Rows.Count, 1).End(xlUp).Row, 1).Row
Me.cbColor.AddItem Sheets(1).Cells(a, 1)
Next
'Add option for new type
Me.cbColor.AddItem "Add new type..."
End Sub

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.

VB Avoiding text box that is occupied

I am making an application where there is a form with a column of text boxes that are filled from two different forms with a button on each. when the button of these forms are clicked they input the results into the text boxes in the column.
the problem I am having is that say i click (form2.button 1) three times it will occupy text boxes 1,2 and 3. Now say I want to use (form1.button 1) to input data into text box 4 it will occupy text box 1.
I have each button set up for multiple clicks so I would like to understand how i can have is so say(form2.button 1) is clicked then (form1.button1) will go to 2nd click for example. there are 10 text boxes so I will need it so that they react to how many times each has been clicked.
Assuming the TextBoxes were called TextBox1 thru TextBox10, you could use a sub like this:
Public Sub AddValue(ByVal value As String)
For i As Integer = 1 To 10
Dim tbName As String = "TextBox" & i
Dim matches() As Control = Me.Controls.Find(tbName, True)
If matches.Length > 0 AndAlso TypeOf matches(0) Is TextBox Then
Dim tb As TextBox = DirectCast(matches(0), TextBox)
If tb.Text.Trim.Length = 0 Then
tb.Text = value
Exit Sub
End If
End If
Next
MessageBox.Show("All TextBoxes are already taken!")
End Sub
Why not just store a counter variable in the form that contains the textboxes, say NextTextBoxNumber that increments each time a textbox is filled in and then you know exactly which textbox to go to next based upon the value of this variable?
Also, to continue with #Oded's comment, you can easily accomplish what he meant using something along the lines of If Textbox1.Text <> "" Then ...
Does that make sense??