VBA chart creation from scattered data rows - excel

I'm creating a UI for entering, editing, exporting, importing patient data (cholesterol biomarkers).
I've encountered a problem with the need to display available data from a "Data" worksheet.
The Data looks like this:
What I need is to create a chart from available data of ,, Elizbeth Norton,, with date in X-axis and Chol, Trig, LDL, HDL (separate lines) in Y-axis.
The results are being managed by using a Userform with a Listbox (from which a button should create the chart when found data is selected. Data is selected in a Listbox)
and this code finds needed data and puts selected results into an array
The Userform:
Code to find needed data:
If Len(f_FindAll.TextBox_Find.Value) >= 3 Then 'Do search if text in find box is longer than 3 character.
Set SearchRange = ActiveWorkbook.Worksheets("Data").Range("C:E").Cells
FindWhat = f_FindAll.TextBox_Find.Value
'Calls the FindAll function
Set FoundCells = FindAll(SearchRange:=SearchRange, _
FindWhat:=FindWhat, _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByColumns, _
MatchCase:=False, _
BeginsWith:=vbNullString, _
EndsWith:=vbNullString, _
BeginEndCompare:=vbTextCompare)
If FoundCells Is Nothing Then
ReDim arrResults(1 To 1, 1 To 5)
arrResults(1, 1) = "Data not found!!!"
Else
'Add results of FindAll to an array
ReDim arrResults(1 To FoundCells.Count, 1 To 5)
lFound = 1
For Each FoundCell In FoundCells
If FoundCell.Column = 3 Then
arrResults(lFound, 1) = FoundCell.Offset(0, -1).Value
arrResults(lFound, 2) = FoundCell.Value
arrResults(lFound, 3) = FoundCell.Offset(0, 1).Value
arrResults(lFound, 4) = FoundCell.Offset(0, 2).Value
arrResults(lFound, 5) = FoundCell.Address
lFound = lFound + 1
Else
If FoundCell.Column = 4 Then
arrResults(lFound, 1) = FoundCell.Offset(0, -2).Value
arrResults(lFound, 2) = FoundCell.Offset(0, -1).Value
arrResults(lFound, 3) = FoundCell.Value
arrResults(lFound, 4) = FoundCell.Offset(0, 1).Value
arrResults(lFound, 5) = FoundCell.Address
lFound = lFound + 1
Else
If FoundCell.Column = 5 Then
arrResults(lFound, 1) = FoundCell.Offset(0, -3).Value
arrResults(lFound, 2) = FoundCell.Offset(0, -2).Value
arrResults(lFound, 3) = FoundCell.Offset(0, -1).Value
arrResults(lFound, 4) = FoundCell.Value
arrResults(lFound, 5) = FoundCell.Address
lFound = lFound + 1
End If
End If
End If
Next FoundCell
End If
'Populate the listbox with the array
Me.ListBox_Results.List = arrResults
Code to display selected data in a user form:
Private Sub ListBox_Results_Click()
'Go to selection on the sheet when the result is clicked
Dim strAddress As String
Dim l As Integer
For l = 0 To ListBox_Results.ListCount
If ListBox_Results.Selected(l) = True Then
strAddress = ListBox_Results.List(l, 4)
Rownum = Range(strAddress).Row
Colnum = Range(strAddress).Column
ActiveWorkbook.Worksheets("Data").Select
Cells(Rownum, Colnum).Select
'Populate textboxes with results
'and maybe populate chart data range with results aswell????
With ActiveWorkbook.Worksheets("Data")
f_FindAll.TextBox_Results1.Value = .Cells(.Range(strAddress).Row, 1).Value
f_FindAll.TextBox_Results2.Value = .Cells(.Range(strAddress).Row, 2).Value
f_FindAll.TextBox_Results3.Value = .Cells(.Range(strAddress).Row, 3).Value
f_FindAll.TextBox_Results4.Value = .Cells(.Range(strAddress).Row, 4).Value
f_FindAll.TextBox_Results5.Value = .Cells(.Range(strAddress).Row, 5).Value
f_FindAll.TextBox_Results6.Value = .Cells(.Range(strAddress).Row, 6).Value
f_FindAll.TextBox_Results7.Value = .Cells(.Range(strAddress).Row, 7).Value
f_FindAll.TextBox_Results8.Value = .Cells(.Range(strAddress).Row, 8).Value
f_FindAll.TextBox_Results9.Value = .Cells(.Range(strAddress).Row, 9).Value
f_FindAll.TextBox_Results10.Value = .Cells(.Range(strAddress).Row, 10).Value
f_FindAll.TextBox_Results11.Value = .Cells(.Range(strAddress).Row, 11).Value
f_FindAll.TextBox_Results12.Value = .Cells(.Range(strAddress).Row, 12).Value
End With
GoTo EndLoop
End If
Next l
EndLoop:
End Sub
So what would be the best option? Maybe instead sort the data in sheet "Data" and create a chart from the selected range?
Thank you for your help.

One way would be to build a collection of row numbers and then use them to create arrays for each series of the chart. Alternatively dump the array to another sheet and use that as the source data.
Option Explicit
Sub PlotData()
Dim wb As Workbook, ws As Worksheet
Dim rngSearch As Range, rngFound As Range
Dim FindWhat As String, FirstFound As String
Dim datarows As Collection, ar
Dim r As Long, i As Integer, n As Integer
Set wb = ThisWorkbook
Set ws = wb.Sheets("Data")
Set datarows = New Collection
Set rngSearch = ws.UsedRange.Columns("C:E")
' build collection of rows
FindWhat = "11342"
Set rngFound = rngSearch.Find(FindWhat, _
LookIn:=xlValues, LookAt:=xlPart, _
SearchOrder:=xlByColumns, MatchCase:=False)
If rngFound Is Nothing Then
' no match
Exit Sub
Else
FirstFound = rngFound.Address
Do
n = n + 1
datarows.Add rngFound.Row, CStr(n)
Set rngFound = rngSearch.FindNext(After:=rngFound)
Loop While Not rngFound Is Nothing And rngFound.Address <> FirstFound
End If
' fill array
ReDim ar(1 To n, 1 To 5), x(n - 1), y(n - 1)
Dim sname
sname = Array("date", "chol", "trig", "LDL", "HDL")
For i = 1 To n
With ws
r = datarows(i)
x(i - 1) = .Cells(r, "B") 'date
ar(i, 1) = .Cells(r, "B") 'date
ar(i, 2) = .Cells(r, "I") 'chol
ar(i, 3) = .Cells(r, "J") 'trig
ar(i, 4) = .Cells(r, "K") 'LDL
ar(i, 5) = .Cells(r, "L") 'HDL
End With
Next
' copy to sheet if required as source data for plot
'Sheet2.Range("A1:E1") = sname
'Sheet2.Range("A2:E" & n + 1) = ar
' plot graph
Dim cht As Chart, c As Integer, srs As Series
Set cht = ws.Shapes.AddChart(xlLineMarkers).Chart
With cht
.HasTitle = True
.ChartTitle.Text = FindWhat
For c = 2 To 5
'Define the array of values for each series
For i = 1 To n
y(i - 1) = ar(i, c)
Next
Set srs = .SeriesCollection.NewSeries
With srs
.XValues = x
.Values = y
.name = sname(c - 1)
End With
Next
.Location Where:=xlLocationAsNewSheet, name:=FindWhat
End With
MsgBox "Done"
End Sub

Related

For Each loop on filtered data returning 0 results, no errors

I need to generate a sheet of values out of a database between dates that the user selects. The date is in column 2 of the database, but I need the whole row for every date in this range. I got some advice to use a For Each instead to more easily use the SpecialCells(xlCellTypeVisible). While I am no longer getting any errors I also get no data in my product worksheet. Could someone tell me why I am not returning data?
Sub Generate()
Dim g As Integer
Dim h As Integer
Dim datemin As String
Dim datemax As String
datemin = CDbl(CDate(Sheets("start").Cells(15, 8)))
datemax = CDbl(CDate(Sheets("start").Cells(15, 9)))
Worksheets("Database").Range("A1").AutoFilter Field:=10, Criteria1:=">=" & datemin, _
Operator:=xlAnd, Criteria2:="<=" & datemax
g = 0
For Each Row In Worksheets("database").Range("A1")
g = g + 1
If Cells(g, 1).SpecialCells(xlCellTypeVisible) = True And Cells(g, 1) <> "" Then
Sheets("product").Activate
Dim NextRow As Long
NextRow = Application.WorksheetFunction.CountA(Range("A:A")) + 10
'fill KPI
Cells(NextRow, 1) = Format(Sheets("database").Cells(g, 1), "mm/dd/yyyy") 'Date1
Cells(NextRow, 2) = Format(Sheets("database").Cells(g, 2), "mm/dd/yyyy") 'Date2
Cells(NextRow, 3) = Sheets("database").Cells(g, 3) 'value1
Cells(NextRow, 4) = Sheets("database").Cells(g, 4) 'value2
Cells(NextRow, 6) = Sheets("database").Cells(g, 5) 'value3
Cells(NextRow, 9) = Sheets("database").Cells(g, 8) 'comment
Cells(NextRow, 13) = Sheets("database").Cells(g, 6) 'person
Else
Exit For
End If
Next
End Sub
You are only 'looping' through one cell - A1.
If you want to use a loop for this try looping through all the rows on the database and checking if they are visible or not.
If they are visible then copy the relevant data to the other sheet.
Sub Generate()
Dim rngDst As Range
Dim rngSrc As Range
Dim datemin As String
Dim datemax As String
Dim g As Integer
Dim h As Integer
datemin = CDbl(CDate(Sheets("start").Cells(15, 8)))
datemax = CDbl(CDate(Sheets("start").Cells(15, 9)))
Worksheets("Database").Range("A1").AutoFilter Field:=10, Criteria1:=">=" & datemin, _
Operator:=xlAnd, Criteria2:="<=" & datemax
Set rngSrc = Worksheets("Database").Range("A2")
Set rngDst = Worksheets("Product").Range("A11")
Do
If Not rngSrc.EntireRow.Hidden And rngSrc.Value <> "" Then
'fill KPI
rngDst.Value = Format(rngSrc.Value, "mm/dd/yyyy") 'Date1
rngDst.Offset(, 1).Value = Format(rngSrc.Offset(, 1).Value, "mm/dd/yyyy") 'Date2
rngDst.Offset(, 2).Value = rngSrc.Offset(, 2).Value 'value1
rngDst.Offset(, 3).Value = rngSrc.Offset(, 3).Value 'value2
rngDst.Offset(, 5).Value = rngSrc.Offset(, 4).Value 'value3
rngDst.Offset(, 8).Value = rngSrc.Offset(, 7).Value 'comment
rngDst.Offset(, 12).Value = rngSrc.Offset(, 5).Value 'person
Set rngDst = rngDst.Offset(1, 0)
End If
Set rngSrc = rngSrc.Offset(1, 0)
Loop Until rngSrc = ""
End Sub

VBA value range doing strange

So how do i put this i am a vba rookie and i have been trying to make an excel file and the purpose is that it should be an inventory of all items one sheet is for putting items in and other is for giving them away. But that is not the problem, the thing is i wanted to have a page called "databaseinventory" where all products that are taken out are writen down but my value is doing strange. (look at the image)
So this is the input screen and if i type this
this is the output on a different sheet but i don't want it to be 0
I noticed if i change the input and add 3 rows it works but that prevents me of typing more then one product
this is the output that i want to have and i really don't know what is wrong with the code
Sub Btn_Clickweggegeven()
Dim x As Long
Dim Givenaway As Worksheet
Dim Inventory As Worksheet
Dim productn As String
Dim erow As Long
Dim rng As Range
Dim rownumber As Long
Dim row As Long
Dim wsData As Worksheet
Dim wsIn As Worksheet
Dim nextRow As Long
Dim BtnText As String
Dim BtnNum As Long
Dim strName As String
x = 2
Do While Cells(x, 1) <> ""
' go through each item on list
productn = Cells(x, 1)
' if item is not new then add quanity to total Inventory
With Worksheets("Inventory").Range("A:A")
Set rng = .Find(What:=productn, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
'if item is new add item to the bottom of Inventory list
If rng Is Nothing Then
erow = Worksheets("Inventory").Cells(1, 1).CurrentRegion.Rows.Count + 1
Worksheets("Inventory").Cells(erow, 1) = Worksheets("Givenaway").Cells(x, 1)
Worksheets("Inventory").Cells(erow, 2) = Worksheets("Givenaway").Cells(x, 2)
Worksheets("Inventory").Cells(erow, 3) = Worksheets("Givenaway").Cells(x, 3)
Worksheets("Inventory").Cells(erow, 4) = Worksheets("Givenaway").Cells(x, 4)
GoTo ende
Else
rownumber = rng.row
End If
End With
Worksheets("Inventory").Cells(rownumber, 2).Value = Worksheets("Inventory").Cells(rownumber, 2).Value _
- Worksheets("Givenaway").Cells(x, 2).Value
Worksheets("Inventory").Cells(rownumber, 4).Value = Worksheets("Inventory").Cells(rownumber, 4).Value _
+ Worksheets("Givenaway").Cells(x, 2).Value
ende:
x = x + 1
Loop
'after complete delete items from Givenaway list
Worksheets("Givenaway").Select
row = 2
Do While Cells(row, 1) <> ""
Range(Cells(row, 1), Cells(row, 3)).Select
Selection.Delete
Loop
Set wsIn = Worksheets("Givenaway")
Set wsData = Worksheets("Databaseinventory")
With wsData
nextRow = .Cells(.Rows.Count, "A") _
.End(xlUp).Offset(1, 0).row
End With
With wsData
With .Cells(nextRow, 1)
.Value = Now
.NumberFormat = "mm/dd/yyyy hh:mm:ss"
End With
.Cells(nextRow, 2).Value = productn
.Cells(nextRow, 3).Value = Worksheets("Databaseinventory").Cells(rownumber, 3).Value _
+ Worksheets("Givenaway").Cells(x, 2).Value
End With
End Sub
This code is deleting the value
Worksheets("Givenaway").Select
row = 2
Do While Cells(row, 1) <> ""
Range(Cells(row, 1), Cells(row, 3)).Select
Selection.Delete
Loop
before this line copies it to Databaseinventory
Cells(nextRow, 3).Value = Worksheets("Databaseinventory").Cells(rownumber, 3).Value _
+ Worksheets("Givenaway").Cells(x, 2).Value
It appears to work if you have 3 rows is because on exit from the Do While Cells(x, 1) <> "" loop the value of x will be 3. After deleting the first record then Worksheets("Givenaway").Cells(x, 2).Value will be the value for the third record.
The database update routine also need to be within the loop
Option Explicit
Sub Btn_Clickweggegeven()
Dim wb As Workbook, rng As Range
Dim wsInv As Worksheet, wsGiven As Worksheet, wsData As Worksheet
Dim iRow As Long, iDataRow As Long, iInvRow As Long
Dim sProduct As String, nValue As Single
Set wb = ThisWorkbook
Set wsGiven = wb.Sheets("GivenAway")
Set wsInv = wb.Sheets("Inventory")
Set wsData = wb.Sheets("Databaseinventory")
iDataRow = wsData.Cells(Rows.Count, 1).End(xlUp).row
iRow = 2
With wsGiven
Do While .Cells(iRow, 1) <> ""
sProduct = .Cells(iRow, 1)
nValue = .Cells(iRow, 2)
' if item is not new then add quanity to total Inventory
With wsInv.Range("A:A")
Set rng = .Find(What:=sProduct, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
End With
If rng Is Nothing Then
iInvRow = wsInv.Cells(Rows.Count, 1).End(xlUp).row + 1
wsInv.Cells(iInvRow, 1).Resize(1, 4).Value = .Cells(iRow, 1).Resize(1, 4).Value
Else
iInvRow = rng.row
wsInv.Cells(iInvRow, 2).Value = wsInv.Cells(iInvRow, 2).Value - nValue
wsInv.Cells(iInvRow, 4).Value = wsInv.Cells(iInvRow, 4).Value + nValue
End If
' write to database
iDataRow = iDataRow + 1
With wsData.Cells(iDataRow, 1)
.Value = Now
.NumberFormat = "mm/dd/yyyy hh:mm:ss"
.Offset(0, 1) = sProduct ' col B
.Offset(0, 2) = wsInv.Cells(iInvRow, 3).Value + nValue ' col C ??
End With
iRow = iRow + 1
Loop
End With
'delete from GivenAway
wsGiven.Range("A2").Resize(iRow, 3).Delete
MsgBox iRow - 2 & " records processed", vbInformation
End Sub

Subtract a textbox value from a cell

I have an inventory sheet.
When a researcher takes a quantity in a specific lot, the quantity is removed first from the stock quantity, the specific row of ComboBox1 in the column #8. It's okay for that part.
When a second quantity is taken and the row of the lot is not empty, I put the data in a row under but I want the quantity (textBox2) to be substract to the column #8 of the row of ComboBox1.
Private Sub CommandButton1_Enter()
Dim emptyRow As Long
Dim ws As Worksheet
Set ws = ActiveSheet
ActiveSheet.Name = "Micrux"
Dim iLastRow As Long, iFound As Long
Dim rng, bEmpty As Boolean, c As Integer
Dim Test As Boolean
bEmpty = True
With ws
iLastRow = .Range("A" & .Rows.Count).End(xlUp).Row
Set rng = .Range("A1:A" & iLastRow + 1).Find(ComboBox1.Value, _
After:=.Range("A" & iLastRow + 1), _
LookIn:=xlValues, _
lookat:=xlWhole, _
searchorder:=xlByRows, _
SearchDirection:=xlPrevious)
Test = (TextBox2.Text) > ws.Cells(iLastRow, 8)
If Test = True Then
MsgBox "Not enough quantity in stock!"
Else
If rng Is Nothing Then
iFound = iLastRow + 1
Else
iFound = rng.Row
For c = 4 To 7
If Len(.Cells(iFound, c)) > 0 Then bEmpty = False
Next
If bEmpty = False Then
iFound = iFound + 1
.Cells(iFound, 1).EntireRow.Insert xlShiftDown
.Cells(iFound, 7).Value = TextBox2.Text
.Cells(iFound, 6).Value = TextBox3.Text
.Cells(iFound, 5).Value = ComboBox2.Value
.Cells(iFound, 4).Value = TextBox1.Text
Else
.Cells(iFound, 7).Value = TextBox2.Text
.Cells(iFound, 6).Value = TextBox3.Text
.Cells(iFound, 5).Value = ComboBox2.Value
.Cells(iFound, 4).Value = TextBox1.Text
End If
End If
End If
End With
Unload Me
End Sub

Userform to search for two criteria, then paste row's data to userform textboxes

I am getting a run-time error '13': type mismatch for one of the lines marked below. I want to be able to have a userform that you can type two criteria into, then it will search for the row that has both of those criteria and paste the corresponding cells' values to the 11 userform textboxes. I'm not sure why it is giving me an error for this line, or if there is a better way to do this.
Private Sub CommandButton1_Click()
txt1.Visible = True
txt2.Visible = True
txt3.Visible = True
txt4.Visible = True
txt5.Visible = True
txt6.Visible = True
txt7.Visible = True
txt8.Visible = True
txt9.Visible = True
txt10.Visible = True
txt11.Visible = True
Dim ws As Worksheet
Set ws = Sheets("The Goods")
ws.Activate
Dim SearchSearch As Variant
SearchSearch = txtsearch.Value
Dim SearchName As Variant
SearchName = txtname.Value
If Trim(txtsearch.Value) = "" Then
MsgBox "Search can't be left blank.", vbOKOnly + vbInformation, "Search"
End If
If Trim(txtname.Value) = "" Then
MsgBox "Name can't be left blank.", vbOKOnly + vbInformation, "Name"
End If
Dim FirstAddress As String, cF As Range
With ThisWorkbook.Sheets("The Goods").Range("D:D") 'txtsearch will be in the range D:D
Set cF = .Find(What:=SearchSearch, _
after:=ActiveCell, _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False) ' line that is giving me an error
With ThisWorkbook.Sheets("The Goods").Range("B:B") 'txtname will be in the range B:B
Set cF = .Find(What:=SearchName, _
after:=ActiveCell, _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
txt1.Value = cF.(0, 5).Value
txt2.Value = cF(0, 3).Value
txt3.Value = cF(0, 6).Value
txt4.Value = cF(0, 7).Value
txt5.Value = cF(0, 8).Value
txt6.Value = cF(0, 9).Value
txt7.Value = cF(0, 10).Value
txt8.Value = cF(0, 11).Value
txt9.Value = cF(0, 12).Value
txt10.Value = cF(0, 13).Value
txt11.Value = cF(0, 14).Value
End With
End With
End Sub
Private Sub CommandButton3_Click()
Dim iExit As VbMsgBoxResult
iExit = MsgBox("Are you sure you want to exit?", vbQuestion + vbYesNo, "Search System")
If iExit = vbYes Then
Unload Me
End If
End Sub
The code below is a simple For Loop, which loops through each cel in Column B and checks for txtname.Value, and using offset to check if Column D value is equal to txtsearch.Value. If both match, then it will write the values for that row into the userform text boxes. You can change the TextBox1 to txt1, etc.
Private Sub CommandButton1_Click()
Dim ws As Worksheet, cel As Range
Set ws = Sheets("The Goods")
For Each cel In ws.Cells(2, 2).Resize(ws.Cells(Rows.Count, 2).End(xlUp).Row).Cells
If cel.Value = Me.txtname.Value And cel.Offset(, 2).Value = Me.txtsearch.Value Then
Me.TextBox1.Value = cel.Offset(, 3).Value 'Change to your textbox naming scheme
Me.TextBox2.Value = cel.Offset(, 1).Value
Me.TextBox3.Value = cel.Offset(, 4).Value
Me.TextBox4.Value = cel.Offset(, 5).Value
Me.TextBox5.Value = cel.Offset(, 6).Value
Me.TextBox6.Value = cel.Offset(, 7).Value
Me.TextBox7.Value = cel.Offset(, 8).Value
Me.TextBox8.Value = cel.Offset(, 9).Value
Me.TextBox9.Value = cel.Offset(, 10).Value
Me.TextBox10.Value = cel.Offset(, 11).Value
Me.TextBox11.Value = cel.Offset(, 12).Value
End If
Next cel
End Sub
I would go with something like this:
Private Sub CommandButton1_Click()
Dim i As Long, rngB As Range, n As Long, arrB, arrD
Dim ws As Worksheet
Dim SearchSearch As Variant, SearchName As Variant
For i = 1 To 11
Me.Controls("txt" & i).Visible = True
Next i
Set ws = ThisWorkbook.Sheets("The Goods")
ws.Parent.Activate
ws.Activate
SearchSearch = Trim(txtsearch.Value)
SearchName = Trim(txtname.Value)
'check the trimmed values
If Len(SearchSearch) = 0 Or Len(SearchName) = 0 Then
MsgBox "'Search' and 'Name' can't be left blank.", vbOKOnly + vbInformation, "Search"
Exit Sub
End If
'get search ranges
Set rngB = ws.Range(ws.Range("B1"), ws.Cells(ws.Rows.Count, "B").End(xlUp))
Set rngD = rngB.Offset(0, 2)
'pull the values into a couple of arrays for faster searching
arrB = rngB.Value
arrD = rngD.Value
'loop over the arrays
For n = 1 To UBound(arrB, 1)
If arrB(n, 1) = SearchName And arrD(n, 1) = SearchSearch Then
'got a hit - populate your textboxes
Set cF = rngB.Cells(n, 1)
txt1.Value = cF.Offset(0, 1).Value 'Col C same row
txt2.Value = cF.Offset(0, 2).Value 'Col D same row
txt3.Value = cF.Offset(0, 3).Value 'Col E same row
'etc etc
'OR do something like this:
With rngB.Cells(n, 1).EntireRow
txt1.Value = .Cells(1, "C").Value
txt1.Value = .Cells(1, "D").Value
txt1.Value = .Cells(1, "E").Value
'etc etc
End With
Exit For
End If
Next
If cF Is Nothing Then MsgBox "No match!"
End Sub

Use findnext to fill multidimensional array VBA Excel

My question actually concerns a matter that extends on EXCEL VBA Store search results in an array?
Here Andreas tried to search through a column and save hits to an array. I am trying the same. But differing in that on (1) finding a value (2) I want to copy different value types from (3) cells in the same row as where the searched value was found, (4) to a two dimensional array.
So the array would (conceptually) look something like:
Searchresult.1st SameRow.Cell1.Value1 SameRow.Cell2.Value2 SameRow.Cell3.Value3
Searchresult.2nd SameRow.Cell1.Value1 SameRow.Cell2.Value2 SameRow.Cell3.Value3
Searchresult.3rd SameRow.Cell1.Value1 SameRow.Cell2.Value2 SameRow.Cell3.Value3
Etc.
The code I use looks like this:
Sub fillArray()
Dim i As Integer
Dim aCell, bCell As Range
Dim arr As Variant
i = 0
Set aCell = Sheets("Log").UsedRange.Find(What:=("string"), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
If Not aCell Is Nothing Then
Set bCell = aCell
ReDim Preserve arr(i, 5)
arr(i, 0) = True 'Boolean
arr(i, 1) = aCell.Value 'String
arr(i, 2) = aCell.Cells.Offset(0, 1).Value
arr(i, 3) = aCell.Cells.Offset(0, 3).Value
arr(i, 4) = aCell.Cells.Offset(0, 4).Value
arr(i, 5) = Year(aCell.Cells.Offset(0, 3).Value)
i = i + 1
Do While exitLoop = False
Set aCell = Sheets("Log").UsedRange.FindNext(after:=aCell)
If Not aCell Is Nothing Then
If aCell.Address = bCell.Address Then Exit Do
'ReDim Preserve arrSwUb(i, 5)
arr(i, 0) = True
arr(i, 1) = aCell.Value
arr(i, 2) = aCell.Cells.Offset(0, 1).Value
arr(i, 3) = aCell.Cells.Offset(0, 3).Value
arr(i, 4) = aCell.Cells.Offset(0, 4).Value
arr(i, 5) = Year(aCell.Cells.Offset(0, 3).Value)
i = i + 1
Else
exitLoop = True
End If
Loop
End If
End Sub
It seems to go wrong on redimming the array in the loop. I get a Subscript out of range error. I guess I can't redim the array as I'm doing now, but I can't figure out how it is supposed to be done.
I’d be greatful for any clues as to what I’m doing wrong.
ReDim Preserve can only resize the last dimension of your array:
http://msdn.microsoft.com/en-us/library/w8k3cys2(v=vs.71).aspx
From the above link:
Preserve
Optional. Keyword used to preserve the data in the existing array when you change the size of only the last dimension.
Edit:
That's not enormously helpful, is it. I suggest you transpose your array. Also, those error messages from the array functions are AWFUL.
At the suggestion of Siddarth, try this. Let me know if you have any problems:
Sub fillArray()
Dim i As Integer
Dim aCell As Range, bCell As Range
Dim arr As Variant
i = 0
Set aCell = Sheets("Log").UsedRange.Find(What:=("string"), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
If Not aCell Is Nothing Then
Set bCell = aCell
ReDim Preserve arr(0 To 5, 0 To i)
arr(0, i) = True 'Boolean
arr(1, i) = aCell.Value 'String
arr(2, i) = aCell.Cells.Offset(0, 1).Value
arr(3, i) = aCell.Cells.Offset(0, 3).Value
arr(4, i) = aCell.Cells.Offset(0, 4).Value
arr(5, i) = Year(aCell.Cells.Offset(0, 3).Value)
i = i + 1
Do While exitLoop = False
Set aCell = Sheets("Log").UsedRange.FindNext(after:=aCell)
If Not aCell Is Nothing Then
If aCell.Address = bCell.Address Then Exit Do
ReDim Preserve arrSwUb(0 To 5, 0 To i)
arr(0, i) = True
arr(1, i) = aCell.Value
arr(2, i) = aCell.Cells.Offset(0, 1).Value
arr(3, i) = aCell.Cells.Offset(0, 3).Value
arr(4, i) = aCell.Cells.Offset(0, 4).Value
arr(5, i) = Year(aCell.Cells.Offset(0, 3).Value)
i = i + 1
Else
exitLoop = True
End If
Loop
End If
End Sub
Note: in the declarations, you had:
Dim aCell, bCell as Range
Which is the same as:
Dim aCell as Variant, bCell as Range
Some test code to demonstrate the above:
Sub testTypes()
Dim a, b As Integer
Debug.Print VarType(a)
Debug.Print VarType(b)
End Sub
Here's an option that assumes you can dimension the array at the beginning. I used a WorsheetFunction.Countif on the UsedRange for "string," which seems like it should work:
Option Explicit
Sub fillArray()
Dim i As Long
Dim aCell As Range, bCell As Range
Dim arr() As Variant
Dim SheetToSearch As Excel.Worksheet
Dim StringCount As Long
Set SheetToSearch = ThisWorkbook.Worksheets("log")
i = 1
With SheetToSearch
StringCount = Application.WorksheetFunction.CountIf(.Cells, "string")
ReDim Preserve arr(1 To StringCount, 1 To 6)
Set aCell = .UsedRange.Find(What:=("string"), LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
If Not aCell Is Nothing Then
arr(i, 1) = True 'Boolean
arr(i, 2) = aCell.Value 'String
arr(i, 3) = aCell.Cells.Offset(0, 1).Value
arr(i, 4) = aCell.Cells.Offset(0, 3).Value
arr(i, 5) = aCell.Cells.Offset(0, 4).Value
arr(i, 6) = Year(aCell.Cells.Offset(0, 3).Value)
Set bCell = aCell
i = i + 1
Do Until i > StringCount
Set bCell = .UsedRange.FindNext(after:=bCell)
If Not bCell Is Nothing Then
arr(i, 1) = True 'Boolean
arr(i, 2) = bCell.Value 'String
arr(i, 3) = bCell.Cells.Offset(0, 1).Value
arr(i, 4) = bCell.Cells.Offset(0, 3).Value
arr(i, 5) = bCell.Cells.Offset(0, 4).Value
arr(i, 6) = Year(bCell.Cells.Offset(0, 3).Value)
i = i + 1
End If
Loop
End If
End With
End Sub
Note that I fixed some issues in your declarations. I added Option Explicit, which forces you to declare your variables - exitLoop was undeclared. Now both aCell and bCell are ranges - previously only bCell was (scroll down to "Pay Attention To Variables Declared With One Dim Statement"). I also created a worksheet variable and surrounded it in a With statement. Also, I started both dimensions of the array at 1 because... well because I wanted to I guess :). I also simplified some of the loop exiting logic - I don't think you needed all that to tell when to exit.
You cannot Redim Preserve a multi dimensional array like this. In a multidimensional array, you can change only the last dimension when you use Preserve. If you attempt to change any of the other dimensions, a run-time error occurs. I would recommend reading this msdn link
having said that I can think of 2 options
Option 1
Store the results in a new temp sheet
Option 2
Declare a 1D array and then concatenate your results using a unique delimiter for example "#Evert_Van_Steen#"
At the top of the code
Const Delim As String = "#Evert_Van_Steen#"
Then use it like this
ReDim Preserve arr(i)
arr(i) = True & Delim & aCell.Value & Delim & aCell.Cells.Offset(0, 1).Value & Delim & _
aCell.Cells.Offset(0, 3).Value & Delim & aCell.Cells.Offset(0, 4).Value & Delim & _
Year(aCell.Cells.Offset(0, 3).Value)

Resources