Loop Through Userform & Paste to Offset Cells - vba

It's me again!
I am trying to input data into a database with a userform by looping through each control and pasting it into a cell via an offset with a counter. I am getting an error on the line which actually inputs the data to the cell and cannot figure out how to do this via a loop. It would be easy to do it field by field but I do not want to write that many lines of code.
Here is my most recent attempt:
Option Explicit
Sub cbSubmit_Click()
' Set worksheet
Dim dbFood As Worksheet
Set dbFood = Sheets("dbFood")
'Set last row and column
Dim lRow As Long
lRow = Cells(Rows.Count, 1).End(xlUp).Row
Dim lCol As Long
lCol = Cells(1, Columns.Count).End(xlLeft).Row
'Define idCell as Range type
Dim idCell As Range
' If no records exit, add first record
If Cells(lRow, 1).Value = "ID" Then
Set idCell = dbFood.Range("A2")
idCell.Value = 1
' Add Data
Dim ufControl As Control
Dim Counter As Long
Counter = 1
For Each ufControl In Me.Controls
If TypeOf ufControl Is MSForms.ComboBox Or MSForms.TextBox Then
idCell.Offset(0, Counter).Value = ufField.Value
Counter = Counter + 1
End If
Next ufControl
MsgBox "Added to database!"
' Else add next record
ElseIf Cells(lRow, 1).Value >= 0.1 Then
Dim lastID As Long
lastID = Cells(lRow, 1).Value
Set idCell = dbFood.Cells(lRow + 1, 1)
idCell.Value = lastID + 1
' Add Data
' If none of the above display ERROR and exit sub
Else: MsgBox ("ERROR - Cannot Create Record")
Exit Sub
End If
End Sub
If anyone could help me figure out how to solve this one then great!

I managed to solve this by using the method Kathara suggested to me but edited it to avoid the 438 error. Below is the small adjustment I made to make it work:
For Each ufControl In Me.Controls
If TypeOf ufControl Is MSForms.TextBox Then
idCell.Offset(0, Counter).Value = ufControl.Text
Counter = Counter + 1
ElseIf TypeOf ufControl Is MSForms.ComboBox Then
idCell.Offset(0, Counter).Value = ufControl.Text
Counter = Counter + 1
End If
Next ufControl
Many thanks for your help :)

I saw some things that I have adapted down below. May I ask you to test that bit of code?
Option Explicit
Sub cbSubmit_Click()
Dim dbFood As Worksheet
Set dbFood = ActiveWorkbook.Sheets("dbFood")
Dim lRow As Long
lRow = dbFood.Cells(dbFood.Rows.Count, 1).End(xlUp).Row
Dim lCol As Long
lCol = dbFood.Cells(1, dbFood.Columns.Count).End(xlLeft).Row
Dim idCell As Range
If dbFood.Cells(lRow, 1).Value = "ID" Then
Set idCell = dbFood.Range("A2")
idCell.Value = 1
Dim ufControl As Control
Dim Counter As Long
Counter = 1
For Each ufControl In Me.Controls
If TypeOf ufControl Is MSForms.TextBox Then
idCell.Offset(0, Counter).Value = ufControl.Result
Counter = Counter + 1
ElseIF TypeOf ufControl Is MSForms.ComboBox
idCell.Offset(0, Counter).Value = ufControl.SeletedItem.Value
End If
Next ufControl
MsgBox "Added to database!"
ElseIf dbFood.Cells(lRow, 1).Value >= 0.1 Then
Dim lastID As Long
lastID = dbFood.Cells(lRow, 1).Value
Set idCell = dbFood.Cells(lRow + 1, 1)
idCell.Value = lastID + 1
Else
MsgBox ("ERROR - Cannot Create Record")
Exit Sub
End If
End Sub
As you can see I have divided the types of the ufcontrol as I am not sure that with a combobox you can directly say .Value so you'll have to add .SelectedItem. You can at least try it once :)

Related

recursive tree parsing with vba

Given the following spreadsheet of data: https://ethercalc.org/q7n9zwbzym5y
I have the following code that will parse this and will derive a tree from the parent-child relationships in the sheet. Note that fact that every column occurs twice is because the first instance of the columns is for another type of data, I am only concerned with the populated columns. This is the desired output from the sheet above:
Code:
Sub performanceSheet(someParams)
' Write to "Performance" sheet
Dim w1 As Worksheet, w2 As Worksheet, wsSearch As Worksheet, wsData As Worksheet
Dim num_rows
Dim parent As Range, parentName As String
Dim parentRange As Range, childrenRange As Range
Dim childCount As Long
Dim p As Variant
Dim f1 As Range, f2 As Range
currRow = 8
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Set w1 = wbk.Sheets("PositionsDB")
Set w2 = wbk.Sheets("Performance")
num_rows = w1.Cells(Rows.Count, 1).End(xlUp).row
'If there's no parentName column, we can't continue.
If w1.Rows(1).Find("portfolioName") Is Nothing Then Exit Sub
'find first instance
Set f1 = w1.Rows(1).Find("portfolioName", lookat:=xlWhole)
If Not f1 Is Nothing Then
'find second instance
Set f2 = f1.Offset(0, 1).Resize(1, w1.Columns.Count - f1.Column).Find("portfolioName", lookat:=xlWhole)
If Not f2 Is Nothing Then
'set range based on f2
Set parentRange = w1.Range(f2.Offset(1, 0), _
w1.Cells(Rows.Count, f2.Column).End(xlUp))
End If
End If
'If there's no Root level, how do we know where to start?
If parentRange.Find("Main") Is Nothing Then Exit Sub
For Each parent In parentRange
If Not dict.Exists(parent.Value) Then
childCount = Application.WorksheetFunction.CountIf(parentRange, parent.Value)
Set childrenRange = parent.Offset(, 2).Resize(childCount, 1)
dict.Add parent.Value, Application.Transpose(Application.Transpose(childrenRange.Value))
End If
Next
' Recursive method to traverse our dictionary, beginning at Root element.
Call PerformanceProcessItem("", "Main", dict, w2, 9)
wbk.Sheets("Performance").Columns("A:F").AutoFit
End Sub
Private Sub PerformanceProcessItem(parentName As String, name As String, dict As Object, ws As Worksheet, row_num As Long, Optional indent As Long = 0)
Dim output As String, v
Dim w2 As Worksheet
'Debug.Print WorksheetFunction.Rept(" ", indent) & name
'Debug.Print parentName & name
'write to sheet
ws.Cells(row_num, 3).Value = name
row_num = row_num + 1
If Not dict.Exists(name) Then
'we're at a terminal element, a child with no children.
Exit Sub
Else
For Each v In dict(name)
' ## RECURSION ##
Call PerformanceProcessItem(name, CStr(v), dict, ws, row_num, indent + 2)
Next
End If
End Sub
However, when creating this tree, it gets stuck on an infinite loop of India's, where after recognizing "Cash" as the terminal element of India, rather than exiting that subtree it will create another India and continue until overflow. Is there a logic error in my code? Hours of debugging hasn't worked for me and any input would be appreciated on where I have a flaw in my logic.
I am assuming that "Main" and "Cash" will always be there. If not then we will have to tweak the code little bit. I have commented the code so you may not have a problem understanding it. But if you do, simply ask. I quickly wrote this code so I am sure it can be optimized :)
Option Explicit
Dim sB As String
Dim tmpAr As Variant
Sub Sample()
Dim col As New Collection
Dim s As String
Dim ws As Worksheet
Dim lRow As Long, i As Long, j As Long
Dim itm As Variant, vTemp As Variant
Set ws = Sheet1 '<~~ Change this to the relevant sheet
With ws
'~~> Get Last Row of Col AA
lRow = .Range("AA" & .Rows.Count).End(xlUp).Row
'~~> Store Range AA:AC in an array
tmpAr = .Range("AA2:AC" & lRow).Value
End With
'~~> Create a unique collection of portfolioName
For i = LBound(tmpAr) To UBound(tmpAr)
If tmpAr(i, 1) = "Main" Then
On Error Resume Next
col.Add tmpAr(i, 3), CStr(tmpAr(i, 3))
On Error GoTo 0
End If
Next i
'~~> Sort the collection
For i = 1 To col.Count - 1
For j = i + 1 To col.Count
If col(i) > col(j) Then
vTemp = col(j)
col.Remove j
col.Add vTemp, vTemp, i
End If
Next j
Next i
s = "Main"
For Each itm In col
sB = vbTab & itm
s = s & vbNewLine & sB
sB = ""
GetParentChild itm, 2
If Trim(sB) <> "" Then _
s = s & vbNewLine & sB
Next itm
s = s & vbNewLine & vbTab & "Cash"
Debug.Print s
End Sub
Private Sub GetParentChild(strg As Variant, n As Integer)
Dim sTabs As String
Dim j As Long, k As Long
For k = 1 To n
sTabs = sTabs & vbTab
Next k
For j = LBound(tmpAr) To UBound(tmpAr)
If Trim(tmpAr(j, 1)) = Trim(strg) And Trim(tmpAr(j, 1)) <> "Cash" Then
sB = sB & sTabs & tmpAr(j, 3) & vbNewLine
GetParentChild tmpAr(j, 3), n + 1
End If
Next j
End Sub
This is what I got when I ran it on the data that you provided.

using checkboxes with userform

I have an user form designed with three listboxes.
The 3 listboxes are populated by the location from three different sheets.
By selecting the listbox, the user can filter the data in the sheet "Data".
if the user is selecting the "BBE Bebra" from the Listbox1. then he could find the filtered result of Bebra in the sheet.
Similary, if the user is selecting from the Listbox2, the same procedure is followed and if the user is selecting from listbox3, the same procedure is followed.
The user can also, select all the three checkbox and looks for the filtered result in the sheet.
I have a issues with the working code.
If I am selecting the checkboxes and click "Filter" then I always see the filtered result. The next time I click on the Filter Button I would like to see the whole data sheet with filters clear and checkboxes cleared.
Can someone tell how I can do it ?
Below is the code, I am using in the filter button
Sub DoFilter()
Dim strCriteria() As String
Dim strCriteria2() As String
Dim strcriteria3() As String
Dim arrIdx As Integer
Dim arrIdx2 As Integer
Dim arrIdx3 As Integer
Dim xRow As Integer
Dim arrCounter As Integer
Dim lo As ListObject
arrIdx = 0
arrIdx2 = 0
arrIdx3 = 0
For xRow = 2 To Last(1, List.Cells)
If List.Cells(xRow, 2) = True Then
ReDim Preserve strCriteria(0 To arrIdx)
strCriteria(arrIdx) = List.Cells(xRow, 3)
arrIdx = arrIdx + 1
End If
Next xRow
For xRow = 2 To Last(1, List.Cells)
If List_Man.Cells(xRow, 2) = True Then
ReDim Preserve strCriteria2(0 To arrIdx2)
strCriteria2(arrIdx2) = List_Man.Cells(xRow, 3)
arrIdx2 = arrIdx2 + 1
End If
Next xRow
For xRow = 2 To Last(1, List.Cells)
If List_S.Cells(xRow, 2) = True Then
ReDim Preserve strcriteria3(0 To arrIdx3)
strcriteria3(arrIdx3) = List_S.Cells(xRow, 3)
arrIdx3 = arrIdx3 + 1
End If
Next xRow
Set Ws = ThisWorkbook.Sheets("Data")
Set lo = Ws.ListObjects("Table7")
If arrIdx = 0 And arrIdx2 = 0 And arrIdx3 = 0 Then
'Ws.UsedRange.AutoFilter
Else
With Ws
With lo
'.AutoFilterMode = True
' .UsedRange.AutoFilter
If arrIdx <> 0 Then
.Range.AutoFilter field:=13, Criteria1:=Array(strCriteria), Operator:=xlFilterValues
End If
If arrIdx2 <> 0 Then
.Range.AutoFilter field:=14, Criteria1:=Array(strCriteria2), Operator:=xlFilterValues
End If
If arrIdx3 <> 0 Then
.Range.AutoFilter field:=15, Criteria1:=Array(strcriteria3), Operator:=xlFilterValues
End If
If .AutoFilter.Range.Columns(1).SpecialCells(xlCellTypeVisible).Cells.Count = 1 Then
MsgBox " Your filter has no result"
End If
End With
End With
Dim i As Long
On Error Resume Next
With ThisWorkbook.Worksheets("Dev").PivotTables("PivotTable1").PivotFields("Lo.")
.ClearAllFilters
For i = 1 To .PivotItems.Count
.PivotItems(i).Visible = False
Next
For arrCounter = LBound(strCriteria) To UBound(strCriteria)
.PivotItems(strCriteria(arrCounter)).Visible = True
Next arrCounter
End With
End If
End Sub
I call the function do filter in my button "Filter".
with the button "exit" I always have the
following code
Private Sub CBExit_Click()
If ActiveSheet.FilterMode Then
ActiveSheet.ShowAllData
End If
Sheets("Dev").Select
Sheets("Dev").PivotTables("PivotTable1").PivotFields("Development Loc.").ClearAllFilters
Unload Me
End Sub
You will need to keep track of your current state using some sort of flag. I would do something like the following:
Private Sub Filter_Click()
If Filter.Caption = "Filter" Then
DoFilter
Filter.Caption = "Unfilter"
Else
'do logic to clear
Filter.Caption = "Filter"
End If
End Sub
This has the added benefit of telling the user what the next click of the button will do.

VBA - USERFORM - Find equall value and make entry for adjacent value in row to textbox for every match

I need upgrade this code to find.next version. In attachment is sample form for better understanding. Keycombobox value can be found more then once and every match for adjacent values has to be in adjacent textbox.
DATA SEMPLE Keytextbox value = TEST1
.Cells(row with FIRST find TEST1, 1) = textbox10 (located in multipage.page(find))
.Cells(row with SECOND find TEST1, 1) = textbox110 (locateted in multipage.page(alternative find))
Option Explicit
Sub TestFind()
Dim sonsat As Long
Dim FindRng As Range
With Sheets("DATA")
Set FindRng = .Range("A:A").Find(Keycombobox.Text) ' <-- assuming Keycombobox is a textBox
If Not FindRng Is Nothing Then ' <-- successful find
sonsat = FindRng.Row
' rest of yout code here ....
.Cells(sonsat, 1) = TextBox10 '<-- for good coding practice use TextBox1.Value ' or TextBox1.Text
.Cells(sonsat, 2) = TextBox20
.Cells(sonsat, 3) = TextBox30
.Cells(sonsat, 4) = TextBox40
.Cells(sonsat, 5) = TextBox50
.Cells(sonsat, 6) = TextBox60
.Cells(sonsat, 7) = TextBox70
Else
MsgBox "Unable to find " & Keycombobox.Text & " in specified Range !"
End If
End With
End Sub
may be you're after this:
Sub TestFind()
Dim f As Range
Dim firstAddress As String
Dim iPage As Long, i As Long
With Sheets("DATA").Range("A:A").SpecialCells(xlCellTypeConstants)
Set f = .Find(what:=Keycombobox.Text, LookIn:=xlvalkue, lookat:=xlWhole) ' <-- assuming Keycombobox is a textBox
If Not f Is Nothing Then
firstAddress = f.address
Do
For i = 1 To 7
Me.Controls("TextBox" & iPage + i * 10) = .Cells(f.Row, i)
Next
iPage = iPage + 100
Set f = .FindNext(f)
Loop While f.address <> firstAddress
End If
End With
End Sub

Read cell for cell and create sheets

How can I read in Visual Basic from column B, sheet "control" in Excel cell for cell till an empty cell?
After that I would like to generate for every cell a new sheet with the name from cells. In this:
:
you see the content of this column, which could be different from time to time. After reading it I want to generate empty sheets with names: RW_BONDS, ... .
You can do something like this.
Private Sub CommandButton1_Click()
Dim ws As Excel.Worksheet
Dim lRow As Long
Dim lastRow As Long
'Set the sheet to read from
Set ws = Application.Sheets("control")
'Set the row to start reading at
lRow = 3
lastRow = wsOwners.Cells(wsOwners.Rows.Count, "B").End(xlUp).Row
'Loop through the rows
Do While lRow <= lastRow
If ws.Range("B" & lRow).Value <> "" then
'Add a new sheet
ActiveWorkbook.Sheets.Add Before:=Worksheets(Worksheets.Count)
'Change the name to the value of column B in the current row
ActiveWorkbook.ActiveSheet.Name = ws.Range("B" & lRow).Value
End If
'Increment your row to the next one
lRow = lRow + 1
Loop
End Sub
Sub test()
Dim i As Long
i = 1
While Len(Sheets("Control").Cells(i, 2))
Worksheets.Add.Name = Sheets("Control").Cells(i, 2): i = i + 1
Wend
End Sub
EDIT answer for the comment:
Sub test()
Dim i As Long
i = 1
With Sheets("Control")
On Error Resume Next
Application.DisplayAlerts = False
While Len(.Cells(i, 2))
If Len(Sheets(.Cells(i, 2).Value).Name) = 0 Then Else Sheets(.Cells(i, 2).Value).Delete
Worksheets.Add.Name = .Cells(i, 2): i = i + 1
Wend
Application.DisplayAlerts = True
On Error GoTo 0
End With
End Sub
set ws = worksheets("Source")
row = 1
col = "B"
Do
row = row + 1
if ws.range(col & row).text = "" then exit do
worksheets.add.name = ws.range(col & row).text
Loop
End Sub
Sub createSheets()
With Worksheets("control")
iRow = 1 'Start on the first row
While Len(.Cells(iRow, 2)) > 0 'While there isn't a blank cell
Worksheets.Add.Name = .Cells(iRow,2) 'Create/rename sheet
iRow = iRow + 1
Wend
End With
End Sub

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