Looping through a 2D array of text boxes - vba

I have a user form having a 100 text boxes. They are arranged 10 x 10 arrays. The text boxes have names
C1_A1 to C1_A10 (first row)
C2_A1 to C2_A10 (second row)
.....
C10_A1 to C10_A10 (tenth row)
How can I loop through the textboxes row by row. The code for the textbox_change() is given below. I successfully implemented this for a form containing 10 boxes. But now I have to scale to a form having 100 boxes and it is no longer practical.
Private Sub C1_A1_Change()
Dim wt As Double
C1_A1.SetFocus
If IsNumeric(C1_A1.Value) Then
wt = CDbl(C1_A1.Value)
If wt >= 0 And wt <= 1 Then
'do nothing
Else
MsgBox "Enter a number between 0 and 1"
C1_A1.Value = vbNullString
End If
Else
wt = 0
End If
End Sub
'an action button to read all values
Private Sub ReadDataTT1_Click()
Me.C1_A1.Value = Range("Wt!E9").Value
............
Me.C10_A10.Value = Range("Wt!N18").Value
End Sub
'an action button to save all values
Private Sub SaveDataTT1_Click()
If C1A1.Value <> "" Then
Range("Wt!E9").Value = C1_A1.Value
............
Range("Wt!N18").Value = C10_A10.Value
End If
End Sub

To use only one single event handler (TextBox_Change Event) for all text boxes you can use a class module.
Add a class module called clsTextBox with the following content:
Option Explicit
Public WithEvents pTbx As MSForms.TextBox
Private Sub pTbx_Change()
Dim wt As Double
If IsNumeric(pTbx.Value) Then
wt = CDbl(pTbx.Value)
If wt >= 0 And wt <= 1 Then
'do nothing
Else
MsgBox "Enter a number between 0 and 1"
pTbx.Value = vbNullString
End If
Else
wt = 0
End If
End Sub
Note that this is the code you used in your TextBox_Change event which we want to apply to all text boxes.
Add the following to your user form to apply the class to your text boxes
Option Explicit
Private mArrClsTbx(1 To 9) As clsTextBox 'change 9 to number of textboxes
Const TbxRows As Long = 3 'change 3 to number of text box rows
Const TbxCols As Long = 3 'change 3 to number of text box columns
Private Sub UserForm_Initialize()
Dim i As Long
Dim iRow As Long, iCol As Long
For iRow = 1 To TbxRows
For iCol = 1 To TbxCols
i = i + 1
Set mArrClsTbx(i) = New clsTextBox
Set mArrClsTbx(i).pTbx = Controls("C" & iRow & "_A" & iCol)
Next iCol
Next iRow
End Sub
To read/save the values you can use a loop similar to the one above writing/reading cells instead of the two Set lines.
Public Sub WriteDataToWorksheet()
Dim iRow As Long, iCol As Long
For iRow = 1 To TbxRows
For iCol = 1 To TbxCols
Worksheets("Wt").Range("E9").Offset(iRow - 1, iCol - 1).Value = Controls("C" & iRow & "_A" & iCol).Value
Next iCol
Next iRow
End Sub
Public Sub ReadDataFromWorksheet()
Dim iRow As Long, iCol As Long
For iRow = 1 To TbxRows
For iCol = 1 To TbxCols
Controls("C" & iRow & "_A" & iCol).Value = Worksheets("Wt").Range("E9").Offset(iRow - 1, iCol - 1).Value
Next iCol
Next iRow
End Sub

Related

ComboBox display wrongly

I have a code which display the text of the cell underneath it. However, it seems that the Combobox just refuse to display the correct text. As you can see in the screenshot
The Text property is different from the displaying text. It's the previous value.
ScreenUpdating is True
The combobox is enabled
There is only 1 combobox, no other objects/shapes/buttons/forms. And a single table in this sheet.
Other information:
Problematic ComboBox is in sheet LinhKien, other comboboxes work fine. I don't know how to upload file here, so it's a 7 days link valid begin from 20220712 (YYYYMMDD)
The combobox is hidden when user is not selecting column 1 or select more than 1 cell. It becomes visible when a cell in column 1 is selected.
I have 2 other sheets with Comboboxes behave the exact same way (hidden when not in certain column, text comes from underneath cell) but they don't have this problem.
If the code is of relevant, here it is.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
DoEvents
If Selection.Count > 1 Then Exit Sub
If Application.CutCopyMode Then
searchBoxAccessories.Visible = False
Exit Sub
End If
If searchBoxAccessories Is Nothing Then
Set searchBoxAccessories = ActiveSheet.OLEObjects("SearchCombBoxAccessories")
End If
If Target.Column = 1 And Target.Row > 3 Then
Dim isect As Range
Set isect = Application.Intersect(Target, ListObjects(1).Range)
If isect Is Nothing Then GoTo DoNothing
isInitializingComboBox = True
GetSearchAccessoriesData
searchBoxAccessories.Activate
isInitializingComboBox = True 'This prevent "_Change" fires up when something changes
searchBoxAccessories.Top = Target.Top
searchBoxAccessories.Left = Target.Left
searchBoxAccessories.Width = Target.Width + 15
searchBoxAccessories.Height = Target.Height + 2
Application.EnableEvents = False 'Another attemp to prevent "_Change" fires up when something changes
searchBoxAccessories.Object.text = Target.text
Application.EnableEvents = True
searchBoxAccessories.Object.SelStart = 0
searchBoxAccessories.Object.SelLength = Len(Target.text)
searchBoxAccessories.Visible = True
isInitializingComboBox = False 'Screenshot is taken here
Set workingCell = Target
Else
DoNothing:
If searchBoxAccessories Is Nothing Then
Set searchBoxAccessories = ActiveSheet.OLEObjects("SearchCombBoxAccessories")
End If
If searchBoxAccessories.Visible Then searchBoxAccessories.Visible = False
End If
End Sub
_____________________
Public Sub GetSearchAccessoriesData()
Dim col2Get As String: col2Get = "3;4;5;6"
Dim dataSourceRg As Range: Set dataSourceRg = GetTableRange("PhuKienTbl")
If Not IsEmptyArray(searchAccessoriesArr) Then Erase searchAccessoriesArr
searchAccessoriesArr = GetSearchData(col2Get, dataSourceRg, Sheet22.SearchCombBoxAccessories)
End Sub
_____________________
Public Function GetSearchData(col2Get As String, dataSourceRg As Range, searchComboBox As ComboBox, _
Optional filterMat As String = "") As Variant
Dim filterStr As String: filterStr = IIf(filterMat = "", ";", "1;" & filterMat)
Dim colVisible As Integer: colVisible = 1
Dim colsWidth As String: colsWidth = "200"
Dim isHeader As Boolean
Dim colCount As Integer: colCount = Len(col2Get) - Len(Replace(col2Get, ";", "")) + 1
GetSearchData = GetArrFromRange(dataSourceRg, col2Get, False, filterStr)
With searchComboBox
.ColumnCount = colVisible
.ColumnWidths = colsWidth
.ColumnHeads = False
End With
Set dataSourceRg = Nothing
End Function
_____________________
Public Function GetArrFromRange(rg As Range, cols2GetStr As String, isHeader As Boolean, Optional colCriFilterStr As String = ";") As Variant
Dim col2Get As Variant: col2Get = Split(cols2GetStr, ";")
Dim arrRowsCount As Integer
Dim arrColsCount As Integer: arrColsCount = UBound(col2Get) + 1
Dim resultArr() As Variant
Dim iRow As Integer
Dim iCol As Integer
Dim criCol As Integer
If Len(colCriFilterStr) = 1 Then
criCol = 0
Else: criCol = CInt(Left(colCriFilterStr, InStr(colCriFilterStr, ";") - 1))
End If
Dim criStr As String: criStr = IIf(isHeader, "", Mid(colCriFilterStr, InStr(colCriFilterStr, ";") + 1))
If isHeader Then
arrRowsCount = 1
Else
If criCol <> 0 Then
arrRowsCount = WorksheetFunction.CountIf(rg.Columns(criCol), criStr)
Else
arrRowsCount = rg.Rows.Count
End If
End If
If arrRowsCount = 0 Then GoTo EndOfFunction
ReDim resultArr(1 To arrRowsCount, 1 To arrColsCount)
Dim wkCell As Range
Dim arrRow As Integer: arrRow = 1
For iRow = IIf(isHeader, 1, 2) To IIf(isHeader, 1, rg.Rows.Count)
If criStr = "" Then
For iCol = 1 To arrColsCount
resultArr(arrRow, iCol) = rg.Cells(iRow, CDbl(col2Get(iCol - 1))).Value
Next iCol
arrRow = arrRow + 1
Else
If rg.Cells(iRow, criCol).Value = criStr Then
For iCol = 1 To arrColsCount
resultArr(arrRow, iCol) = rg.Cells(iRow, CDbl(col2Get(iCol - 1))).Value
Next iCol
arrRow = arrRow + 1
End If
End If
Next iRow
EndOfFunction:
GetArrFromRange = resultArr
Erase resultArr
End Function
After weeks of frustration, I am please to announce that I found out the cause. It was the Freeze Panes that affects the display of combobox. Particularly, ComboBox placed in freezed column is not refreshed as frequently as in other cell. In that area, combobox almost act as it's disabled (visually). No text changes update even when you type, no selection/highlighting. I changed to only freeze upper rows and the combobox works just as expected. That's why my other comboboxes in other sheets behaved correctly.
I suspect that Excel tries to save resources by making the freezed part not as responsive. That behavior override Application.ScreenUpdating and not exposed to user.
Since this "feature" could be version specific, my system is Win 10 pro, Excel 16 pro plus.

Next buttons userform Excel VBA

I have an admin sheet that has a column containing a list of True and False. I am building a userform UI so users can click next (for now - building previous button after making next work), the userform will show the next False item in admin sheet and its corresponding data in Sheet1 will be displayed in Textbox1.
Reason for this is the row id in admin sheet correlates with Sheet1. So if data in Sheet1 row(31) has something wrong, column(13) in Admin sheet row(31) will be False.
Code:
Dim n As Long
Private Sub CommandButton1_Click()
Dim LR As Long
LR = Sheets("Sheet1").Cells(Rows.count, "B").End(xlUp).row
n = 7
With Worksheets("Admin")
For i = n To LR
If .Cells(i, 13).Value = "False" Then
With Worksheets("Sheet1")
Me.TextBox1 = .Cells(i, 2).Value
Exit For
End With
End If
Next i
End With
n = i + 1
End Sub
This successfully goes to the next False item and displays it correctly in Textbox1. However, it does not iterate to the next one..
Whatever logic we use to set up Next, I am going to assume Previous will be the same?
Thanks guys.
You can do something like this:
Sub cmdNext_Click()
FindRow True
End Sub
Sub cmdPrev_Click()
FindRow False
End Sub
Private Sub FindRow(bForward As Boolean)
Const RW_START As Long = 7
Dim LR As Long, t As Long, dir As Long, i As Long
LR = Sheets("Sheet1").Cells(Rows.Count, "B").End(xlUp).Row
'going forwards, or back?
If bForward Then
n = IIf(n = 0, RW_START, n + 1) '<< Start at top
t = LR '<< towards here
dir = 1 '<< increasing
Else
n = IIf(n = 0, LR, n - 1) '<< Start at bottom
t = RW_START '<< towards here
dir = -1 '<< decreasing
End If
For i = n To t Step dir
If Worksheets("Admin").Cells(i, 13).Value = "False" Then
Me.TextBox1 = Worksheets("Sheet1").Cells(i, 2).Value
n = i
Exit For
End If
Next i
End Sub

How can I modify this code to count the same object in a normal Microsoft word table

Following code is for counting the number of checked checkboxes in a formfield. How can I modify it to count the same object in a normal table, and not a form field?
Private Sub CommandButton2_Click()
Dim i As Long, j As Long, k As Long
k = 0
With ActiveDocument
With .Tables(1)
j = 3
For i = 1 To .Rows.Count
If .Cell(i, j).Range.FormFields(1).CheckBox.Value = True Then
k = k + 1
End If
Next i
i = .Rows.Count
End With
End With
MsgBox k & " instances were found"
End Sub
use the ContentControls property of the Range object to return a ContentControls object and exploit it
here's some examples of how to count checkboxes or checked checkboxes in a table or a in a single column of it:
Option Explicit
Sub main()
With ActiveDocument
MsgBox CountCheckBoxes(.Tables(1)) & " CheckBox instances were found"
MsgBox CountCheckedCheckBoxes(.Tables(1)) & " checked CheckBox instances were found"
MsgBox CountCheckBoxesInColumn(.Tables(1), 1) & " CheckBox instances were found in column 1"
MsgBox CountCheckedCheckBoxesInColumn(.Tables(1), 1) & " checked CheckBox instances were found in column 1"
End With
End Sub
Private Function CountCheckBoxes(table As table, Optional col As Variant) As Long
Dim cc As ContentControl
With table
For Each cc In .Range.ContentControls
If cc.Type = wdContentControlCheckBox Then CountCheckBoxes = CountCheckBoxes + 1
Next cc
End With
End Function
Private Function CountCheckedCheckBoxes(table As table) As Long
Dim cc As ContentControl
With table
For Each cc In .Range.ContentControls
If cc.Type = wdContentControlCheckBox Then If cc.Checked Then CountCheckedCheckBoxes = CountCheckedCheckBoxes + 1
Next cc
End With
End Function
Private Function CountCheckBoxesInColumn(table As table, col As Long) As Long
Dim i As Long
With table
For i = 1 To .Rows.count
CountCheckBoxesInColumn = CountCheckBoxesInColumn + .Cell(i, col).Range.ContentControls.count
Next i
End With
End Function
Private Function CountCheckedCheckBoxesInColumn(table As table, col As Long) As Long
Dim i As Long
With table
For i = 1 To .Rows.count
CountCheckedCheckBoxesInColumn = CountCheckedCheckBoxesInColumn + CountCheckBoxesCheked(.Cell(i, col).Range)
Next i
End With
End Function
Function CountCheckBoxesCheked(rng As Range) As Long
Dim cc As ContentControl
With rng
For Each cc In .ContentControls
If cc.Type = wdContentControlCheckBox Then If cc.Checked Then CountCheckBoxesCheked = CountCheckBoxesCheked + 1
Next cc
End With
End Function

Randomise numbers without repeating the number

My end result is to output the names in column A to column B in random order.
I have been researching but cant seem to find what I need.
So far I can kinda of randomise the numbers but its still giving me repeated numbers + the heading (A1).
I need it to skip A1 because this is the heading\title of the column and start at A2.
I assume once that is working correctly I add the randomNumber to a random name to Worksheets("Master Sheet").Cells(randomNumber, "B").Value ...something like that...?
OR if there is a better way of doing this let me know.
Sub Meow()
Dim CountedRows As Integer
Dim x As Integer
Dim i As Integer
Dim PreviousCell As Integer
Dim randomNumber As Integer
i = 1
PreviousCell = 0
CountedRows = Worksheets("Master Sheet").Range("A" & Rows.Count).End(xlUp).Row
If CountedRows < 2 Then
' If its only the heading then quit and display a messagebox
No_People_Error = MsgBox("No People entered or found, in column 'A' of Sheetname 'Master Sheet'", vbInformation, "Pavle Says No!")
Exit Sub
End If
Do Until i = CountedRows
randomNumber = Int((Rnd * (CountedRows - 1)) + 1) + 1
If Not PreviousCell = randomNumber Then
Debug.Print randomNumber
i = i + 1
End If
PreviousCell = randomNumber
Loop
Debug.Print "EOF"
End Sub
Here's a quick hack...
Sub Meow()
'On Error GoTo err_error
Dim CountedRows As Integer
Dim x As Integer
Dim i As Integer
Dim PreviousCell As Integer
Dim randomNumber As Integer
Dim nums() As Integer
PreviousCell = 0
CountedRows = Worksheets("Master Sheet").Range("A" & Rows.Count).End(xlUp).Row
ReDim nums(CountedRows - 1)
If CountedRows < 2 Then
' If its only the heading then quit and display a messagebox
No_People_Error = MsgBox("No People entered or found, in column 'A' of Sheetname 'Master Sheet'", vbInformation, "Pavle Says No!")
Exit Sub
End If
For i = 1 To CountedRows
rand:
randomNumber = randomNumbers(1, CountedRows, nums)
nums(i - 1) = randomNumber
Worksheets("Master Sheet").Range("B" & randomNumber) = Range("A" & i)
Next i
Exit Sub
err_error:
Debug.Print Err.Description
End Sub
Public Function randomNumbers(lb As Integer, ub As Integer, used As Variant) As Integer
Dim r As Integer
r = Int((ub - lb + 1) * Rnd + 1)
For Each j In used
If j = r Then
r = randomNumbers(lb, ub, used)
Else
randomNumbers = r
End If
Next
End Function
I've managed something similar previously using two collections.
Fill one collection with the original data and leave the other collection empty. Then keep randomly picking an index in the first collection, adding the value at that index to the second collection and delete the value from the original collection. Set that to loop until the first collection is empty and the second collection will be full of a randomly sorted set of unique values from your starting list.
***Edit: I've thought about it again and you don't really need the second collection. You can pop a random value from the first collection and write it directly to the worksheet, incrementing the row each time:
Sub Meow()
Dim lst As New Collection
Dim rndLst As New Collection
Dim startRow As Integer
Dim endRow As Integer
Dim No_People_Error As Integer
startRow = 2
endRow = Worksheets("Master Sheet").Cells(startRow, 1).End(xlDown).Row
If Cells(startRow, 1).Value = "" Then
' If its only the heading then quit and display a messagebox
No_People_Error = MsgBox("No People entered or found, in column 'A' of Sheetname 'Master Sheet'", vbInformation, "Pavle Says No!")
Exit Sub
End If
' Fill a collection with the original list
Dim i As Integer
For i = startRow To endRow
lst.Add Cells(i, 1).Value
Next i
' Create a randomized list collection
' Use i as a row counter
Dim rowCounter As Integer
rowCounter = startRow
Dim index As Integer
Do While lst.Count > 0
'Find a random index in the original collection
index = Int((lst.Count - 1 + 1) * Rnd + 1)
'Place the value in the worksheet
Cells(rowCounter, 2).Value = lst(index)
'Remove the value from the list
lst.Remove (index)
'Increment row counter
rowCounter = rowCounter + 1
Loop
End Sub
P.S. I hope there's an excellent story behind naming your sub Meow() :P

Locate and display data using listbox in vba

I have tried this code to locate specific data from excel using List Box in VBA, It populated a list of names from sheet3 range(E7), then everytime I click an item/name on it the program should located the name from sheet3 and display the data on that row into their corresponding textboxes in my userform.But this doesn't work pecisely.Thanks.
Private Sub ListBox1_Click()
Dim isRow As Long
If Me.ListBox1.ListIndex > -1 Then
isRow = Me.ListBox1.ListIndex + 1
End If
Me.Label1 = Cells(sRow, 5)
Me.txt_Mon_in.Text = Cells(sRow,6)
End Sub
Populating data from Sheet3.
Private Sub Userform_Initialize()
Dim vCol As Variant
Dim Lrow As Long
Lrow = Sheets("Sheet3").UsedRange.Rows(Sheets("Sheet3").UsedRange.Rows.Count).Row
vCol = Sheets("Sheet3").Range("E7:E" & Lrow).Value
Me.ListBox1.List = vCol
End Sub
Im not quite sure what you are doing but try the below code
Private Sub Userform_Initialize()
Dim vCol As Variant
Dim Lrow As Long
Lrow = Sheets("Sheet3").UsedRange.Rows(Sheets("Sheet3").UsedRange.Rows.Count).Row
vCol = Sheets("Sheet3").Range("E7:E" & Lrow).Value
Me.ListBox1.List = vCol
End Sub
Private Sub ListBox1_Click()
Dim selectedName As String
Dim i As Long
With ListBox1
For i = 0 To .ListCount - 1
If .Selected(i) Then
selectedName = .List(i)
End If
Next i
End With
Dim c As Range
For Each c In Sheets(3).Range("E7:E" & Sheets(3).Range("E" & Rows.Count).End(xlUp).Row)
If c = selectedName Then
Label1 = Sheets("Sheet3").Cells(c.Row, 5)
txt_Mon_in.Text = Sheets("Sheet3").Cells(c.Row, 6)
End If
Next c
End Sub
the Listbox1_Click() sub will iterate over the column E in sheet 3 and put the name in the Label1 control and will put offset of (0,1) of the found cell into the txt_Mon_in contol.
Sheet3
Userform
Result