Delete multiple textboxes from list of controls - vb.net

I have a list of controls created dynamically in a control list. The user has options to add textboxes in the control list and to delete them as well.
I have created the textboxes using the following code:
Dim tb As New TextBox
tb.Name = "Textbox" & counter.ToString
tb.Left = 55
tb.Top = fields
Me.Controls.Add(tb)
MyControls.Add(tb)
counter = counter + 1
So, the textbox names when created are Textbox1, Textbox2 and so on maximum up to Textbox10.
The user can delete textboxes by button clicks one by one. If the user wants to delete these textboxes, counter will run backward and will delete Textbox10 first and then Textbox9 and so on (This is the same as First in First Out).
So, for deleting I tried the following code, but it didn't execute, giving an error. The code is written under Button's Click event of the delete button.
For Each CType(Me.Controls("Textbox" & counter), TextBox) As Control In MyControls
Me.Controls.Remove(...) 'The textbox's name to be deleted in place of dots
'The textbox name to be deleted here with .Dispose()
Next
The error is: "Expression is a value and therefore cannot be the target of an assignment" in the first line of the above code.
How to delete a series of textboxes dynamically?

This line is causing the problem:
For Each CType(Me.Controls("Textbox" & counter), TextBox) As Control In MyControls
CType(Me.Controls("Textbox" & counter), TextBox) resolves to a value, so it cannot be a loop increment variable.
To delete based on a loop, you would need to know how many controls you want to delete. Here is one way:
For i As Integer = 1 To Math.Min(NumberOfControlsToDelete, 10) ' Cap deleting at 10.
' Make sure we don't go below 1.
If counter < 1 Then Continue
' Expected that the control will exist.
Me.Controls.Remove(Controls.Find("TextBox" & counter, True)(0))
' Decrement counter.
counter -= 1
Next

It looks like you're already keeping track of those dynamic buttons by adding them to a List? in this line (your 6th line of code):
MyControls.Add(tb)
As such, simply grab the last entry and remove it, no need to go searching for the control by name:
If MyControls.Count > 0 Then
Dim TB As TextBox = MyControls(MyControls.Count - 1)
MyControls.Remove(TB)
TB.Dispose()
End If
If you want to delete all of them at the same time:
For Each TB As TextBox In MyControls
TB.Dispose()
Next
MyControls.Clear()

Is this what you're looking for? I'm not sure I'm reading your question correctly.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Me.Controls.Remove(Me.Controls.Find(""Textbox" & counter"))
End Sub

Related

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.

In VBA, why does putting a textbox value into a cell trigger a for loop in another userform subroutine?

I created an userform with 2 textboxes, 3 buttons and a listbox with two columns. If I click on an entry in the listbox, the list entry which is selected gets transfered to two different textboxes.
See the code below:
Private Sub NewSourceListBox_Click()
Dim i As Integer
'Show the selected data in the corresponding text boxes
For i = 0 To NewSourceListBox.ListCount - 1
If NewSourceListBox.Selected(i) Then
'Hide the add button and show the change button
NewSourceBtnChange.Top = 168
NewSourceBtnChange.Visible = True
NewSourceBtnAdd.Visible = False
NewSourcesIDTxtBox.Value = NewSourceListBox.List(i, 0)
NewSourcesSourceTxtBox.Value = NewSourceListBox.List(i, 1)
'Pass on the selected item row to another subroutine
selectedItem = i
Exit For
End If
Next i
End Sub
selectedItem is a global variable created in another module, which I need to use in another subroutine. If I change the entries in the text boxes in the userform and click the change button the following code gets executed.
This code:
Private Sub NewSourceBtnChange_Click()
Dim row As Integer
row = 6257 + selectedItem
'Change the selected data in the list box to the corresponding data in the text boxes
Sheets("Datensätze").Range("A" & row).Value = NewSourcesIDTxtBox.Value
Sheets("Datensätze").Range("B" & row).Value = NewSourcesSourceTxtBox.Value
'Another duplicate entry to make vLookup work
Sheets("Datensätze").Range("C" & row).Value = NewSourcesIDTxtBox.Value
Unload Me
'Unload the new entry user form to repopulate the comboboxes
Unload NewEntryUserForm
NewEntryUserForm.Show
End Sub
If I watch this step by step via F8 then the following happens: As soon as I click the "NewSourceBtnChange" button the corresponding subroutine NewSourceBtnChange_Click() starts. When I reach Sheets("Datensätze").Range("A" & row).Value = NewSourcesIDTxtBox.Value the program jumps to the NewSourcesListBox_Click() routine, executes it two times and jumps back to Sheets("Datensätze").Range("B" & row).Value = NewSourcesSourceTxtBox.Value, then executes the NewSourcesListBox_Click() routine for another two times and jumps back again to the last entry Sheets("Datensätze").Range("C" & row).Value = NewSourcesIDTxtBox.Value and executes the rest of the NewSourceBtnChange_Click() routine.
This makes it impossible to get the new data from the text boxes into their destined cells.
Edit:
Just to make it easier to reconstruct the described behavior, I exported the userform and its code and uploaded it.
Here is what your code is going through (just the important parts):
1) While initializing userform, it populates the listbox with:
.RowSource = "Datensätze!A6257:B" & 6257 + Sheets("Datensätze").Range("F2").Value - 1
2) When you click listbox item, you trigger NewSourceListBox_Click code, populate textboxes with selected items and set item index number to selectedItem variable. (which is handled wrong. You need to declare selectedItem as public variable.)
3) Then you click NewSourceBtnChange which triggers NewSourceBtnChange_Click. It sets row number of your selected item:
row = 6257 + selectedItem
Then you change this very cell using:
Sheets("Datensätze").Range("A" & row).Value = NewSourcesIDTxtBox.Value
which you have used to populate your listbox with:
.RowSource = "Datensätze!A6257:B" & 6257 + Sheets("Datensätze").Range("F2").Value - 1
At this moment, listbox is populated again, but this time it has been already selected so it triggers the NewSourceListBox_Click code.
Whenever you change the RowSource range, if the listbox is selected, it will behave like this. So you need to deselect the listbox item to workaround this.
TL;DR:
After:
row = 6257 + selectedItem
Insert:
NewSourceListBox.Selected(NewSourceListBox.ListIndex) = False
Also to be able to get selectedItem value in other subs, you need to declare it as public variable. Outside of subs, on the very top, insert:
Public selectedItem As Long

VB DropDown Read Only

I have a DropDown and a DropDownList on my form. I am aware that a DropDown can hold a placeholder text and a DropDownList cannot, however; I would like some code or a work around to allow either:
DropDown as read-only, therefore not allowing a user to type
But preferably a DropDownList with placeholder text (context menu option, or not)
Is this possible?
Thanks.
But preferably a DropDownList with placeholder text (context menu option, or not)
By definition, the text displayed in this control is always the text of the selected item. You can add a "fake" item to the list if you want (e.g. "Select Property Code"), but you will have to check that this item isn't selected later.
To display one of the items (fake or not), simply set the SelectedIndex to the appropriate value once the list is loaded.
DropDown as read-only, therefore not allowing a user to type
This will actively make sure that the text is either in the list or a default value, effectively making it read only. (Written for a ComboBox, but the behavior should be identical with a DropDown.)
Private Sub ComboBox1_TextChanged(sender As Object, e As EventArgs) Handles ComboBox1.TextChanged
Static recursion As Boolean = False
Dim defaultText As String = "My Default Text"
If recursion Then
recursion = False
Else
If ComboBox1.Items.Count > 0 Then
For i As Integer = 0 To ComboBox1.Items.Count - 1
If ComboBox1.Items(i).ToString = ComboBox1.Text Then
Exit Sub
End If
Next
recursion = True
ComboBox1.Text = defaultText
End If
End Sub
Alternatively, here is a sub I call whenever a "strict" ComboBox looses focus to accomplish the same thing. The difference is that doing it this way allows you to keep the AutoComplete functionality:
Public Sub EnforceList(ByRef box As ComboBox) 'FORCES .TEXT TO BEST (OR FIRST) MATCH IN .ITEMS
'If list contains item whose name begins another item's, the shorter must be listed first, e.g. "sew" must preceed "sewer"
If box.Items.Count = 0 Then Exit Sub 'Can't enforce a list that doesn't exist
Dim txt As String = box.Text
Do
For i As Integer = 0 To box.Items.Count - 1
If box.Items(i).ToString Like txt & "*" Then
box.Text = box.Items(i).ToString
Exit Sub
End If
Next
txt = Left(txt, Len(txt) - 1)
Loop
End Sub

ms access vba dynamic array -- why won't this work?

I have an access form with a textbox named txtInput and a button name btnAdd.
The following is the sub procedure for the button's click event; each time a user enters some text into the text box and clicks the button it should be adding the text string to the dynamic array.
Public Sub btnAdd_Click()
Dim equipArray() As String
Dim ctr As Integer
ctr = 0
Do While txtInput <> "stop"
ReDim Preserve equipArray(x)
equipArray(x) = txtInput
ctr = ctr + 1
Loop
End Sub
But it's not working, can anyone help please?
It's recreating the array every time you click the button. You're not preserving it until after you're recreating it. The best thing to do would be to put a hidden textbox on the form and write the values to that textbox, and then on some event (form close? whatever you want) you can write the value from that textbox to your table.

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??