Find range between two words and iterate through it with loop - excel

I have created a method for defining range between two words and iterate through it to copy paste values from one worksheet to another. There is some strange reason it does not work.
I specify row, it is 18, my code starts from row 20? So it copies everything starting from row 20. O_o
It does not detect range correctly as it copies values below my words as well? I have checked that I don't have same words elsewhere.
Any suggestions?
Here is code for calling method:
Sub dsfdsfdsfds()
copyOptionsToTable 18, CalculationItemOM1
End Sub
Here is method:
Private Sub copyOptionsToTable(RowToPaste As Integer, OperatingWorksheet As Worksheet)
'Dim FirstWord, SecondWord
Dim OptionsRange As Range
Dim cell, x
'Set FirstWord = OperatingWorksheet.Range("W:W").Find("OPTIOONS START", LookIn:=xlValues, lookat:=xlWhole)
'Set SecondWord = OperatingWorksheet.Range("W:W").Find("OPTIOONS END", LookIn:=xlValues, lookat:=xlWhole)
Set OptionsRange = OperatingWorksheet.Range(OperatingWorksheet.Cells.Find("[OPTIOONS START]"), OperatingWorksheet.Cells.Find("[OPTIOONS END]"))
x = 0
' Copy - Paste process
For Each cell In OptionsRange
If Not IsEmpty(cell.Value) Or cell.Value <> "OPT" Then
ThisWorkbook.Worksheets("TableForOL").Range("B" & RowToPaste).Offset(0 + x, 0).Value = cell.Offset(0 + x, -20).Value
ThisWorkbook.Worksheets("TableForOL").Range("B" & RowToPaste).Offset(0 + x, 3).Value = cell.Offset(0 + x, 2).Value
End If
x = x + 1
Next cell
End Sub
Source sheet:
Output sheet:
EDIT:
Output still looks like this?

You're already incrementing cell by one row inside the loop - you don't need to further offset that using x
Set OptionsRange = OperatingWorksheet.Range( _
OperatingWorksheet.Cells.Find("[OPTIOONS START]").Offset(1,0), _
OperatingWorksheet.Cells.Find("[OPTIOONS END]").Offset(-1, 0))
x = 0
' Copy - Paste process
For Each cell In OptionsRange.Cells
If Not IsEmpty(cell.Value) Or cell.Value <> "OPT" Then
With ThisWorkbook.Worksheets("TableForOL").Range("B" & RowToPaste)
.Offset(x, 0).Value = cell.Offset(0, -20).Value
.Offset(x, 3).Value = cell.Offset(0, 2).Value
End With
x = x + 1 '<< only increment if you copied values...
End If
Next cell
Also I'm not sure this line does what you intend?
If Not IsEmpty(cell.Value) Or cell.Value <> "OPT" Then
maybe
If Not IsEmpty(cell.Value) And cell.Value <> "OPT" Then

Related

Offset the x in a 'For each x' loop

I am desperate now. :(
I have a list of activities in a column in a sheet. In another sheet I have another list of activities, some of which match entries in the list in the first sheet. The code goes through the first list and finds a match in the second list. Then it checks how many outputs this match has, and if there are more than one outputs, it adds another row in the first list of data, right below the last checked cell of that list. On that new row an entry based on the second output should get written. If there is a further output, another new row gets added etc. until there are no more outputs of the same activity. Then it shall continue with the next activity from the first list. That next activity cell shall be therefore moved with the number of rows added additionally during the check.
The problem is, sometimes that moving with the number of additional rows seems to not be enough, so it happens that the next cell is actually a previous one from the list, i.e. an already checked one, and not a new one. And thus an indefinite cycle occurs. To bypass this, I even try to save the last populated row to a value, so that an additional check gets performed if an earlier row gets calculated, but this does not seem to work either :(
What I have is:
…
For Each a In activity_list
previousAddress = 0
If flagOffset > 0 Then
If rows_to_offset <> 0 Or flagsame > 0 Then
Set canda = a.Offset(rows_to_offset, 0) 'check if the offset is enough
If canda.Row <= lastR Then
Set a = Sheets("Sheet1").Cells(lastR + 1, 3) 'if not enough, go to the last result populated row
Else
Set a = canda
End If
rows_to_offset = 0
End If
End If
activityRow = a.Row
activityValue = a.Value
If activityValue <> 0 And Not activity_to_match_list.Find(activityValue, lookin:=xlValues) Is Nothing Then
Set found_act_match = activity_to_match_list.Find(activityValue, lookin:=xlValues)
Sheets("Sheet2").Activate
Set range_to_search_for_outputs = Sheets("Sheet2").Range(Cells(found_act_match.Row, 2), Cells(found_act_match.Row, 500))
If Not range_to_search_for_outputs.Find("o", lookat:=xlPart, lookin:=xlValues, SearchDirection:=xlNext) Is Nothing Then
Set found_output = range_to_search_for_outputs.Find("o", lookin:=xlValues, SearchDirection:=xlNext)
If found_output.Column <> 1
firstAddress = found_output.Address
Do
… do something with the output value…
' Then take the found output from the match and take its status from the Sheet1:
previousAddress = found_output.Address
If op <> "" Then
If Not op_list.Find(op, lookin:=xlValues) Is Nothing Then
Set found_output_match = op_list.Find(op, lookin:=xlValues)
Sheets("Sheet1").Activate
op_result = Cells(found_output_match.Row, "Y").Value
If Worksheets("Sheet1").Cells(activityRow + rows_to_offset, "Y").Value = "" Then
Worksheets("Sheet1").Cells(activityRow + rows_to_offset, "Y").Value = "? " & Format(op_result, "Percent")
lastR = Cells(activityRow + rows_to_offset, "Y").Row
End If
Else:
If Worksheets("Sheet1").Cells(activityRow + rows_to_offset, "Y").Value = "" Then
Worksheets("Sheet1").Cells(activityRow + rows_to_offset, "Y").Value = "Nothing in Sheet1"
lastR = Cells(activityRow + rows_to_offset, "Y").Row
End If
End If
Sheets("Sheet2").Activate
Set another = range_to_search_for_outputs.Find("o", after:=found_output, SearchDirection:=xlNext)
If Not another Is Nothing And another.Address <> found_output.Address Then 'if there is another output for the same activity, go to its output and continue as above
If another.Address <> firstAddress Then
Set found_output = another
Sheets("Sheet1").Activate
If Sheets("Sheet1").Cells(activityRow + rows_to_offset + 1, "C").Value <> activityValue Then 'if there isn't another row for the same activity yet
Sheets("Sheet1").Rows(activityRow + 1).Insert
Sheets("Sheet1").Cells(activityRow + 1, "C").Value = activityValue
rows_to_offset = rows_to_offset + 1
flagOffset = flagOffset + 1
Else:
flagsame = flagsame + 1 'if there is already another row for the same activity
rows_to_offset = rows_to_offset + 1
End If
End If
End If
Sheets("Sheet1").Activate
End If
Loop While (found_output.Address <> previousAddress) And (found_output.Address <> firstAddress)
End If
Else:
Worksheets("Sheet1").Cells(activityRow, "Y").Value = "no Output"
lastR = Cells(activityRow, "Y").Row
End If
ElseIf activity_to_match_list.Find(activityValue, lookin:=xlValues) Is Nothing Then
Worksheets("Sheet1").Cells(activityRow, "Y").Value = "Nothing in Sheet1"
lastR = Cells(activityRow, "Y").Row
ElseIf a.Offset(1, 0).Value <> 0 Then
Set a = a.Offset(1, 0)
Else:
Sheets("Sheet1").Activate
…
End If
Set … to Nothing
Next a
In principle use a dictionary with the key as the sheet2 activity and the value as a collection of row numbers for that activity. Scan down sheet1 and use the dictionary to find matching rows. Search along the matched row for cells with "o" and copy values back to sheet1 Column Y (inserting rows as required).
Sub FindOutputs()
Const COL_OUT = "Y"
Dim wb As Workbook, ws1 As Worksheet, ws2 As Worksheet
Dim rng As Range, fnd As Range, sFirst As String
Dim dict As Object, key, count As Integer
Dim iLastRow As Long, i As Long, n As Long
Set dict = CreateObject("Scripting.Dictionary")
Set wb = ThisWorkbook
' sheet 2 - Activities to Search in Column A
Set ws2 = wb.Sheets("Sheet2")
iLastRow = ws2.Cells(Rows.count, "A").End(xlUp).Row
For i = 1 To iLastRow
key = Trim(ws2.Cells(i, "A"))
If Len(key) > 0 Then
If Not dict.exists(key) Then
' collection holds row numbers for each activity
dict.Add key, New Collection
End If
dict(key).Add CStr(i) ' add row
End If
Next
' sheet 1 - Activities in column A
Set ws1 = wb.Sheets("Sheet1")
Set cell = ws1.Range("A1")
Do While Len(cell.value) > 0
key = Trim(cell.Value)
count = 0
' does activity exist on sheet2?
If dict.exists(key) Then
n = dict(key).count
' loop through matching rows
For i = 1 To n
r = dict(key).Item(i)
' search along the row for "o"
Set rng = ws2.Cells(r, "B").Resize(1, 500)
Set fnd = rng.Find("o", lookat:=xlPart, LookIn:=xlValues, SearchDirection:=xlNext)
If Not fnd Is Nothing Then
sFirst = fnd.Address
' do something with output value
Do
count = count + 1
If count > 1 Then
' insert row
cell.Offset(1).EntireRow.Insert _
CopyOrigin:=xlFormatFromLeftOrAbove
Set cell = cell.Offset(1)
cell.Value = key
End If
ws1.Range(COL_OUT & cell.Row).Value = fnd.Value
Set fnd = rng.FindNext(fnd)
Loop While fnd.Address <> sFirst
End If
Next
If count = 0 Then
ws1.Range(COL_OUT & cell.Row).Value = "No Output"
End If
Else
ws1.Range(COL_OUT & cell.Row).Value = "Nothing in Sheet1"
End If
Set cell = cell.Offset(1)
Loop
MsgBox "Done"
End Sub

Excel VBA - Find a value and place in a different sheet

I am an absolute novice trying to make a macro that takes an item from cell A2 in sheet "WHO", assigns the value from cell B2 from the same sheet. Inserts a new column in sheet "BO" with name from cell B1 of sheet "WHO". Finds a match of the item from cell A2/ sheet "WHO" in sheet "BO", checks the quantity corresponding to the item, if it is equal to the value of cell B2 from sheet "WHO" and puts it in the new column if not, puts the found quantity of value from sheet "WHO" and continues to search for the next match of an item until you have distributed all the pieces. Now even I was confused, so I attach the code that I managed to assemble from different places :)
Sub BO_WHO_Format()
Dim I As Integer
Dim rngFound As Range, strFirst, Name As String
Dim pNum, vNum, lr As Long
Name = Worksheets("WHO").Range("B1")
lr = Worksheets("WHO").Cells(Rows.Count, "A").End(xlUp).Row ' Find the last row with data in column A..
With Worksheets("BO").Columns(16)
Application.CutCopyMode = FALSE
Sheets("BO").Select
Columns("AC:AC").Select
Selection.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
Range("AC1").Select
ActiveCell.FormulaR1C1 = "На път"
Range("AC2").Value = Name
For I = 2 To lr
strFirst = "" 'Clear the value assigned to strFirst.
Dim Check As Boolean, Counter As Long, Total As Long
Check = False: Counter = 0: Total = 0 ' Initialize variables.
Do ' Outer loop.
pNum = Sheets("WHO").Range("A" & I).Value
vNum = Sheets("WHO").Range("B" & I).Value
If IsNumeric(pNum) Then pNum = Val(pNum)
If IsNumeric(vNum) Then vNum = Val(vNum)
Set rngFound = .Find(what:=pNum, LookAt:=xlWhole, SearchDirection:=xlNext, After:=.Cells(1), MatchCase:=False)
If rngFound Is Nothing Then
MsgBox "Номер " & pNum & " не е намерен! Проверете и започнете отново!"
Sheets("BO").Select
Columns("AB:AB").Select
Selection.Delete Shift:=xlToLeft
Exit Sub
ElseIf rngFound.Offset(, 11).Value = 0 Then GoTo NextIteration 'If value is 0
MsgBox "Виж си кода за грешки"
ElseIf rngFound.Offset(, 11).Value >= vNum Then 'If value is the same
rngFound.Offset(, 13) = vNum
Else
rngFound.Offset(, 13) = rngFound.Offset(, 11).Value
Counter = Counter + rngFound.Offset(, 11).Value ' Increment Counter.
NextIteration:
strFirst = rngFound.Address ' Assign the address of the first item found, so code will know if it has finished looking.
Do While Counter < vNum ' Inner Loop
Total = vNum - Counter
Set rngFound = .FindNext(rngFound)
If Not rngFound Is Nothing And strFirst <> rngFound.Address Then 'strFirst = rngFound.Address ' Assign the address of the first item found, so code will know if it has finished looking.
If rngFound.Offset(, 11).Value = 0 Then GoTo NextError
If rngFound.Offset(, 11).Value <= Total Then
rngFound.Offset(, 13) = rngFound.Offset(, 11).Value
Counter = Counter + rngFound.Offset(, 11).Value ' Increment Counter.
Else
rngFound.Offset(, 13) = Total
Counter = Counter + rngFound.Offset(, 11).Value ' Increment Counter.
End If
Else
NextError:
MsgBox "Номер " & pNum & " не е намерен! Проверете и започнете отново!"
Sheets("BO").Select
Columns("AB:AB").Select
Selection.Delete Shift:=xlToLeft
Exit Sub
End If
Loop ' Inner Loop
End If
Loop Until Check = FALSE ' Exit outer loop immediately.
Next I
End With
End Sub
If the number is not found, the quantity in the sheet "WHO" is greater than the sheet "BO" to delete the newly created column in the sheet "BO" and the macro to terminate with a message. There are no duplicate item in a sheet "WHO", unlike the "BO" sheet.
But I'm totally stuck, please help.
sheet "WHO"
sheet "BO"
I hope I understood all what you need. Have a try of the code:
Option Explicit
Sub BO_WHO_Format()
'worksheets
Dim boSht As Worksheet, whoSht As Worksheet
Set boSht = ThisWorkbook.Sheets("BO")
Set whoSht = ThisWorkbook.Sheets("WHO")
'search ranges
Dim boRange As Range, boCell As Range, whoRange As Range, whoCell As Range
With boSht
'column A, starting from 2-d row
Set boRange = Range(.Cells(2, 1), .Cells(Rows.Count, 1).End(xlUp))
End With
With whoSht
'column P,starting from 2-d row
Set whoRange = Range(.Cells(2, 16), .Cells(Rows.Count, 16).End(xlUp))
End With
'other variables
Dim hasMatch As Boolean
Dim row As Long
'taking each value in column A of the WHO sheet
For Each whoCell In whoRange
'and comparing to each values in column P of the BO sheet
For Each boCell In boRange
If whoCell = boCell Then
row = boCell.row
If Not hasMatch Then
'set the AB column name of the sheet BO as like the name of column B of the WHO sheet
boSht.Cells(1, 28) = whoSht.Cells(1, 2)
hasResult = True
End If
'if value from column B of the sheet WHO equals to value from column AA of the sheet BO
If whoCell.Offset(0, 1).Value = boSht.Cells(row, 27).Value Then
'put this value to column AB
boSht.Cells(row, 28).Value = whoCell.Offset(0, 1).Value
Else
'otherwise if value is not 0
If Not boSht.Cells(row, 27).Value = 0 Then
'put the value from column AA to column AB
boSht.Cells(row, 28).Value = boSht.Cells(row, 27).Value
End If
End If
End If
Next
Next
'check whether there is a match
If Not hasMatch Then
boSht.Cells(1, 28) = ""
MsgBox "No matches!", vbInformation, "Result"
End If
End Sub
See comments in code, in case something is not exactly what you wanted - I pointed an idea, so you can modify it for your needs.

Can you use the Value of a Combo Box to select a Range of Cells

I have a ComboBox that has a Value of "ConcretePad". I also have a Range named "ConcretePad".
i am trying to Select Range based off of ComboBox Value.
***Private Sub CatagoryCB_Change()
Dim rg As String
rg = (CatagoryCB.Value)
Worksheets("Data").Select
If (CatagoryCB.Value = "") Then
GoTo Line2
ElseIf (CatagoryCB.Value <> "") Then
Range(rg).Select
Line2:
End If
End Sub***
Trying to make rg represent the Value of CatagoryCB.Value, which i did but when i put it in the cell reference for range i get an error
You're probably looking for something like this (provided you're using a ListFillRange):
Private Sub CatagoryCB_Change()
If (CatagoryCB.ListIndex <> -1) Then
Worksheets("Data").Select
Range(CatagoryCB.ListFillRange).Cells(CatagoryCB.ListIndex + 1, 1).Select
End If
End Sub
This just grabs the ListFillRange, navigates to the ListIndex which is in sync with it and selects it.
CatagoryCB.ListIndex will return the index of the selected item in the list.
If a value that isn't in the list is selected, it will return -1.
So, for example, if I set my ListFillRange to A1:A3 and select the first option, I will do a Range("A1:A3").Cells(1, 1).Select because the ListIndex of the selected item is 0 (first item) and .Cells(0 + 1, 1) = .Cells(1, 1).
If you're populating the ComboBox manually, you'd need to give it the range you want to link to or perform a find operation.
It's hard to tell from your code.
I figured it out. My (CatagoryCB.Value) was not equal to my Range Name. This is the code i was able to produce to add a part to my datasheet on my current worksheet. This also adds the new row to my range
Dim i As String
Dim c As Integer
Dim g As Integer
i = CatagoryCB.Value
Worksheets("Data").Select
If i = "" Then
GoTo Line2
ElseIf i <> "" Then
Range(i).Select
c = Range(i).Count
Range(i).Activate
ActiveCell.Offset(c, 0).Select
g = ActiveCell.Row
Worksheets("Data").Rows(g).Insert
Range(i).Resize(c + 1).Name = i
Cells(g, 1).FormulaR1C1 = Cells(g - 1, 1).FormulaR1C1
Cells(g, 3) = (Part_NumberTB.Value)
Cells(g, 4) = (VendorCB.Value)
Cells(g, 5) = (DescriptionTB.Value)
Cells(g, 7) = (CostTB.Value)
Cells(g, 8) = (CostTB.Value * 1.35)
Cells(g, 9) = (CostTB.Value * 1.35)
Cells(g, 10).FormulaR1C1 = Cells(g - 1, 10).FormulaR1C1
Cells(g, 11).FormulaR1C1 = Cells(g - 1, 11).FormulaR1C1
Line2:
End If

R1C1 Formula Format with Variable Fixed Cell

I am working on a code that automatically inserts formulas to perform calculations. The range that these formuals are inserted constantly changes and I need to use R1C1 formula references. I cannot figure out how to have "g" set as an absolute cell reference in the second For Each R1C1 formula. Any help is much appreciated.
Sub FindRow1()
Dim t As Range
Dim c As Range
Dim d As Range
Dim e As Range
Dim f As Range
Dim g As Range
With Worksheets("Recap Sheet").Cells
Set t = .Find("Year of Tax Return", After:=.Range("A1"),
LookIn:=xlValues).Cells
Set c = .Find("12. Total Gross Annual Cash Flow", After:=.Range("A1"),
LookIn:=xlValues).Cells
Set d = .Find("15. Total Annual Cash Flow Available to Service Debt",
After:=.Range("A1"), LookIn:=xlValues)
Set e = Range(t.Offset(1, 0), c.Offset(-1, 0))
Set f = Range(c.Offset(1, 0), d.Offset(-1, 0))
Set g = c.Offset(0, 9).Cells
For Each cell In e
If cell.Value <> "" Then
cell.Offset(0, 9).FormulaR1C1 =
"=average(R[0]C[-2],R[0]C[-4],R[0]C[-6],R[0]C[-8])"
End If
Next
For Each cell In e
If cell.Value <> "" Then
'& g & in the formula below does not equal the cell location set above
cell.Offset(0, 10).FormulaR1C1 = "=R[0]C[-1]/" & g & ""
End If
Next
For Each cell In f
If cell.Value <> "" Then
cell.Offset(0, 10).FormulaR1C1 =
"=average(R[0]C[-2],R[0]C[-4],R[0]C[-6],R[0]C[-8])"
End If
Next
For Each cell In f
If cell.Value <> "" Then
cell.Offset(0, 9).FormulaR1C1 = "=R[-2]C[0]*(R[0]C[1]/100)"
End If
Next
End With
End Sub
cell.Offset(0, 10).FormulaR1C1 = "=R[0]C[-1]/" & g.Address(True, True, xlR1C1)

VBA Excel: What is the object type when doing a For/Each of a range?

I am trying to do a loop through the columns of two rows to add data from one sheet to another. This is additional data that was not dealt with in the previous code (since the previous code had specific logic to populate it).
I tried creating a sub to loop through the current rows on each page, but I am getting a byreference error. One of the ranges is the whole destination row, which is offset (incremented) every time it is populated. The other range is the source material, and covers a column in the source sheet. Other columns are accessed via offset. It am doing much of my work in a For/Each of the source range.
When I create the sub and try to pass the second range (bar), I get the error. I am trying to access the 'bar' object in the For/Each, so that both pages are dealing with the same row. This doesn't appear to be working.
Do I need to reDim, or find some other way to pass the appropriate range to the looping function?
Relevant code:
looping sub (very simple) -
Private Sub LoopThru38(thisRow As Range, sourceRow As Range)
Dim counter As Integer
For counter = 1 To 35
thisRow.Cells(1, 8 + counter).Value = sourceRow.Cells(1, 19 + counter)
Next counter
End Sub
Where I pass it -
ElseIf bar.Cells(1, 19) = prevComp And bar.Cells(1, 19).Value = foo.Cells(1, 2).Value Then ' compare if prev and current comp match
' add other DTCs of this component
destRange.Cells(1, 1).Value = idNumber
destRange.Cells(1, 2).NumberFormat = "#"
destRange.Cells(1, 2).Value = CStr(objectNumber + dotNumber - 0.01) & "-" & CStr(dashNumber)
destRange.Cells(1, 3).Value = "3"
destRange.Cells(1, 5).Value = bar.Cells(1, 6).Value '
destRange.Cells(1, 6).Value = bar.Cells(1, 10) ' foo.Cells(1, 3).Value & " - " & foo.Cells(1, 4)
destRange.Cells(1, 7).Value = bar.Cells(1, 11)
destRange.Cells(1, 8).Value = "FMI " & bar.Cells(1, 11) & ": " & bar.Cells(1, 13)
LoopThru38 destRange, bar ' loops through rest of 38 col to populate export sheet
Set destRange = destRange.Offset(1, 0)
idNumber = idNumber + 1
dashNumber = dashNumber + 1
End If
Original declarations of the ranges -
With ThisWorkbook
Set WS = .Worksheets.Add(After:=.Sheets(.Sheets.Count))
Set Columns_38 = .Worksheets("Joe")
Set dtcList = .Worksheets("Fred")
Set spnList = .Worksheets("Martha")
End With
'...cont
Set srcRange = dtcList.Range(dtcList.Cells(2, "A"), dtcList.Cells(lastRowSrc, "A"))
Set destRange = WS.Range(WS.Cells(2, 1), WS.Cells(2, 42))
Set spnRange = spnList.Range(spnList.Cells(6, 1), spnList.Cells(lastRowSPN, 1))
spnRange is where I seem to be having issues. It is in the second For loop (bar), and that is where I get the source data for the output. It doesn't want to pass 'bar' into the sub though. Do I need to pass the whole range in there, and figure out where I am at?
Thanks
You need to explicitly declare bar as a Range in the calling loop. If you do not, then it isn't a Range object, but rather a Variant that contains a Range object. This works the same until you try pass it to a function/subroutine argument that is declared ByRef as a specific object-type, like Range.
This will throw an error, because the compiler cannot tell if it really will be a Range type at run-time.
The object type when using a For Each loop on a Range is Range.
Sub DisplayFirstTextInRange(WithinThisRange as Range)
Dim rng As Range
For Each rng in WithinThisRange
If rng.Text <> "" Then
MsgBox rng.Text
Exit Sub
End If
Next rng
End Sub
A more useful way of iterating through ranges may be by Row:
Sub DisplayFirstTextInFirstColumnOfRows(WithinThisRange as Range)
Dim rng As Range
For Each rng in WithinThisRange.Rows
If rng.Cells(1,1).Text <> "" Then
MsgBox rng.Cells(1,1).Text
Exit Sub
End If
Next rng
End Sub

Resources