Determine which column .find finds result in - excel

I am using .find to search the entire workbook and displaying results with a hyperlink to the match. But since the searched word can be found in any column I need to know which column the word is found in to make the search result appear correct.
This is my code as it is today, I am using a slightly modified example that I found:
Sub Set_Hyper()
' Object variables
Dim wks As Excel.Worksheet
Dim rCell As Excel.Range
Dim fFirst As String
' {i} will act as our counter
Dim i As Long
' Use an input box to type in the search criteria
Dim MyVal As String
MyVal = ActiveSheet.Range("D9")
Application.ScreenUpdating = False
Application.DisplayAlerts = False
i = 19
' Begin looping:
' We are checking all the Worksheets in the Workbook
For Each wks In ActiveWorkbook.Worksheets
If wks.Name <> "Start" Then
' We are checking all cells, we don't need the SpecialCells method
' the Find method is fast enough
With wks.Range("A:B")
' Using the find method is faster:
' Here we are checking column "A" that only have {myVal} explicitly
Set rCell = .Find(MyVal, , , xlPart, xlByColumns, xlNext, False)
' If something is found, then we keep going
If Not rCell Is Nothing Then
' Store the first address
fFirst = rCell.Address
Do
' Link to each cell with an occurence of {MyVal}
rCell.Hyperlinks.Add Cells(i, 4), "", "'" & wks.Name & "'!" & rCell.Address, TextToDisplay:=rCell.Value
wks.Range("B" & rCell.Row & ":R" & rCell.Row).Copy Destination:=Cells(i, 5)
Set rCell = .FindNext(rCell)
i = i + 1 'Increment our counter
End If
Loop While Not rCell Is Nothing And rCell.Address <> fFirst
End If
End With
End If
Next wks
' Explicitly clear memory
Set rCell = Nothing
' Reset application settings
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
I'm thinking that I want to add something like this:
If rCell.Column() = A Then
' Link to each cell with an occurence of {MyVal}
rCell.Hyperlinks.Add Cells(i, 4), "", "'" & wks.Name & "'!" & rCell.Address, TextToDisplay:=rCell.Value
wks.Range("B" & rCell.Row & ":R" & rCell.Row).Copy Destination:=Cells(i, 5)
Set rCell = .FindNext(rCell)
i = i + 1 'Increment our counter
End If
If rCell.Column() = B Then
' Link to each cell with an occurence of {MyVal}
rCell.Hyperlinks.Add Cells(i, 4), "", "'" & wks.Name & "'!" & rCell(0, -1).Address, TextToDisplay:=rCell(0, -1).Value
wks.Range("B" & rCell.Row & ":R" & rCell.Row).Copy Destination:=Cells(i, 5)
Set rCell = .FindNext(rCell)
i = i + 1 'Increment our counter
End If
Problem is that it doesn't work the way I want. I've tried to modify it in some ways, but either it just seems to skip the whole If part or I never get a result at all.
Can't I use the column comparison this way, or what is the problem?

Use something like this for Column A, where the column is defined by its position (1) rather than a letter (A). As you are searching a two column range A:B then
If rCell.Column = 1 Then
`do code for A
Else
`do code for B
End If

Based on the code sample you pasted, it appears you can simply offset directly based on the column number:
' Link to each cell with an occurence of {MyVal}
rcell.Hyperlinks.Add Cells(i, 4), "", "'" & wks.Name & "'!" & rcell.Offset(, 1 - rcell.Column).Address, TextToDisplay:=rcell.Offset(, 1 - rcell.Column).Value
wks.Range("B" & rcell.Row & ":R" & rcell.Row).Copy Destination:=Cells(i, 5)
Set rcell = .FindNext(rcell)
i = i + 1 'Increment our counter
End If

Related

VBA - Copying Data with Autofilter into multiple Worksheets

I have a macro that, from one worksheet, copies and seperates data, depending on their value in a certain column, into multiple worksheets based on an autofilter. After doing so, I format all sheets into a table. But because there are also other worksheets (always with the names "Such..." and "Tabelle...") in these workbooks, I want to exclude these when doing my worksheet loop. Here is the VBA:
Sub TechfelderBlätter()
Dim i As Integer
Set Eingaben = ThisWorkbook.Worksheets("Eingaben")
Set MainWS = ActiveWorkbook.Worksheets(Eingaben.Cells(3, 3).Value)
LastRow = MainWS.Range((Eingaben.Cells(4, 3).Value) & Rows.Count).End(xlUp).Row
TFS = Eingaben.Cells(12, 3).Value
myarray = uniqueValues(MainWS.Range(TFS & "2:" & TFS & LastRow))
For i = LBound(myarray) To UBound(myarray)
TFname = Left(myarray(i), 30)
Sheets.Add.Name = TFname
MainWS.Range("A1:" & TFS & LastRow).AutoFilter Field:=16, Criteria1:=myarray(i)
MainWS.Range("A1:" & TFS & LastRow).Cells.Copy _
Sheets(Left(myarray(i), 30)).Range("A1")
MainWS.Range("A1:" & TFS & LastRow).AutoFilter
Sheets(TFname).Range("A1").CurrentRegion.Select
Sheets(TFname).ListObjects.Add.Name = TFname & "_Table"
Sheets(TFname).ListObjects(TFname & "_Table").TableStyle = "TableStyleLight11"
Next i
MainWS.Activate
MainWS.Range("A1").CurrentRegion.Select
MainWS.ListObjects.Add.Name = MainWS.Name & "_Table"
MainWS.ListObjects(MainWS.Name & "_Table").TableStyle = "TableStyleLight11"
End Sub
Private Function uniqueValues(InputRange As Range)
Dim cell As Range
Dim tempList As Variant: tempList = ""
For Each cell In InputRange
cell.Value = Replace(cell.Value, "/", " ")
cell.Value = Replace(cell.Value, "\", " ")
cell.Value = Replace(cell.Value, "?", " ")
cell.Value = Replace(cell.Value, "*", " ")
cell.Value = Replace(cell.Value, "[", " ")
cell.Value = Replace(cell.Value, "]", " ")
If cell.Value <> "" Then
If InStr(1, tempList, cell.Value) = 0 Then
If tempList = "" Then tempList = Trim(CStr(cell.Value)) Else tempList = tempList & "|" & Trim(CStr(cell.Value))
End If
End If
Next cell
uniqueValues = Split(tempList, "|")
End Function
Problem1: The cell formatting, specifically the column width, does not get copied, resulting in most columns in the new worksheets being too narrow.
I would appreciate some help. Thanks in advance!
P.S.: I apologise if its a messy code, I know using activate and select isn´t good, but I didn´t know how else to code it.
Edit: I have solved one of the problems. The problem was: The Autofilter command is not dynamic because of the "Field:=16". Ideally, I would like to replace "16" with the TFS variable, in which the column letter is typed in by hand, in this case "P".
This is the solution I came up with by myself now. Instead of:
MainWS.Range("A1:" & TFS & LastRow).Cells.Copy _
Sheets(Left(myarray(i), 30)).Range("A1")
I now use this:
TFname = Left(myarray(i), 30)
MainWs.Range("A1:" & TFS & LastRow).Cells.Copy
With Sheets(TFname).Range("A1")
.PasteSpecial xlPasteColumnWidths
.PasteSpecial xlPasteValues, , False, False
.PasteSpecial xlPasteFormats, , False, False
End With

VBA Excel InStr(myString) returns no result

I want to multiply my range of cells by -1 when they matches the Instr criteria.
but I still get positive values, which are based on my else statement.
Basically, I use the ConverdDecimal function from this link:
https://www.extendoffice.com/documents/excel/1497-excel-convert-decimal-degrees-to-degrees-minutes-seconds.html
with doesn't cope well with negative values. Hence I have to modify my code.
Sub Sun()
Dim rng As Range, cell As Range, rngB As Range, rngC As Range
Dim wors As Worksheet
Dim myString As String
Set wors = ThisWorkbook.ActiveSheet
Dim lastRow As Long, LastRow2 As Long
wors.Columns("E").Copy
wors.Columns("P").PasteSpecial xlPasteValues
wors.Columns("F").Copy
wors.Columns("R").PasteSpecial xlPasteValues
lastRow = wors.Range("P" & wors.Rows.Count).End(xlUp).Row
LastRow2 = wors.Range("Q" & wors.Rows.Count).End(xlUp).Row
Set rng = wors.Range("P1:P" & lastRow)
Set rngB = wors.Range("R1:R" & lastRow)
Set rngC = wors.Range("F1:F" & lastRow)
For Each cell In rng
cell = WorksheetFunction.Substitute(cell, " ", "° ", 1)
cell = WorksheetFunction.Substitute(cell, " ", "' ", 2)
Next
For Each cell In rngB
cell = WorksheetFunction.Substitute(cell, "-", "")
cell = WorksheetFunction.Substitute(cell, " ", "° ", 1)
cell = WorksheetFunction.Substitute(cell, " ", "' ", 2)
Next
With words
Range("Q2:Q" & lastRow).Formula = "=ConvertDecimal(P2)"
Columns("Q").Copy
Columns("X").PasteSpecial xlPasteValues
Range("S2:S" & lastRow).Formula = "=ConvertDecimal(R2)"
End With
With rngC
If InStr(myString, "-") <> 0 Then
Range("T2:T" & lastRow).Formula = "=S2 * -1 "
Else
Range("T2:T" & lastRow).Formula = "=S2"
End If
End With
End Sub
I tried:
Range("T2:T" & lastRow).Formula = "=S2 * (-1) "
Range("T2:T" & lastRow).Formula = "=-S2"
Range("T2:T" & lastRow).Formula = "=-1 * S2"
Nothing works
What is wrong with my code? Why does it cove only the Else situation? I need negative formulas wherever the "-" appears in column F.
Is there any reason why this would not work: For cell T2 use the formula
=IF(LEFT(TRIM(F2), 1)="-", -1, 1)*S2
or if you want to do it in VBA
Range("T2:T" & lastRow).FormulaR1C1="=IF(LEFT(TRIM(RC6), 1)=""-"", -1, 1)*RC[-1]"
is it failing because of this typo and you do not have option explicit?
With words
words is not defined but wors is so if no option explcit a variable words is created at runtime but is set to nothing so nothing happens inside the with block

Why are there inconsistent and broken cell formulas for some rows and not others?

Goal: Populate F and G columns with proper formulas depending on total PROD-TIME for a block
This is another issue that has come up after one of my previous questions:
How to loop through "blocks" of rows in excel dataset instead of each row individually (VBA)?
I have been able to loop through blocks of rows and can now get the sum of the PROD-TIME for that particular block. This sum is necessary to determine which formula needs to be used in the F and G columns.
This is best illustrated in this workbook,
https://www.dropbox.com/s/vgnqi00h8xosja3/wip%20Gantt%20Template.xlsx?dl=0 , where I have shown how I want the formulas to end up in the F and G columns. But for some reason when I run the macro, it just completely breaks. Some of the formulas don't even use reference cells and use the cell value instead, or reference cells don't even appear. Are the blank F and G columns confusing the macro? How can I make sure that every F and G cell gets filled with something? Errors are fine
Sub getStartEndDate()
Dim WrkSht As Excel.Worksheet
Dim RngColumn As Range, RngBlock As Range
Dim totalHrs As Integer 'total PROD-TIME for the given RngBlock
Dim lastRow As Long
lastRow = ActiveSheet.Cells.SpecialCells(xlCellTypeLastCell).Row
Set WrkSht = ActiveWorkbook.Worksheets(1)
' Populate the last row by itself first since the With-statement below needs to reference rows below current row
Range("E" & lastRow).Formula = "=ROUNDUP(D" & lastRow & "/12,0)"
Range("G" & lastRow).Value = Range("C" & lastRow).Value
Range("F" & lastRow).Formula = "=WORKDAY(G" & lastRow & ", -E" & lastRow & ")"
Columns("F:F").NumberFormat = "yyyy-mm-dd"
With WrkSht
Set RngColumn = .Range("B2:B" & lastRow)
'Starts the first block with the first cell in a column.
Set RngBlock = RngColumn.Cells(1)
'Checks every cell in a column.
For Each rngcell In RngColumn
If rngcell.Offset(0,1).Value <> "" Then
'Checks whether a cell's value equals the cell below it.
If rngcell.Value = rngcell.Offset(1, 0).Value Then
'If equal, includes the cell below in the block.
Set RngBlock = Union(RngBlock, rngcell.Offset(1, 0))
Else
'If not equal, that means the block RngBlock ends
' totalHrs is the sum of the "PROD-TIME" for that particular block
totalHrs = WorksheetFunction.Sum(Range(CStr(Trim(Chr(64 + RngBlock.Column + 2))) _
& CStr(Trim(Str(RngBlock.Row))) & ":" _
& CStr(Trim(Chr(64 + 2 + RngBlock.Column + RngBlock.Columns.Count - 1))) _
& CStr(Trim(Str(RngBlock.Row + RngBlock.Rows.Count - 1)))))
If totalHrs < 12 Then
' If total production time (PROD-TIME) is less than 12 hours, then the start and end date should be the same for all rows in that block
rngcell.Offset(0, 4).Value = rngcell.Offset(0, 1).Value
rngcell.Offset(0, 5).Value = rngcell.Offset(0, 1).Value
Else
' If total production time is greater than 12 hours, then start and end dates are based on a different formula
' e.g. Given row 11, F column formula looks like: =WORKDAY(G11, -E11), G column looks like: =IF(B11=B12,F12,C11)
rngcell.Offset(0, 4).Formula = "=WORKDAY(" & rngcell.Offset(0, 5) & ", -" & rngcell.Offset(0, 3) & ")"
rngcell.Offset(0, 5).Formula = "=IF(" & rngcell & "=" & rngcell.Offset(1, 0) & "," & rngcell.Offset(1, 4) & "," & rngcell.Offset(0, 1) & ")"
End If
'Starts the next block with the cell below.
Set RngBlock = rngcell.Offset(1, 0)
End If
End If
Next rngcell
End With
End Sub

VBA Check if Value is in the list with condition

Sub PullUniques()
LastRowColumnA = Cells(Rows.Count, 1).End(xlUp).Row
Dim rngCell As Range
For Each rngCell In Range("A1:A" & LastRowColumnA)
If WorksheetFunction.CountIf(Range("B1:B" & LastRowColumnA), rngCell) <> 0 And _
Range(WorksheetFunction.CountIf(Range("B1:B" & LastRowColumnA), rngCell) <> 0).Offset(0, 1).Row <= 0 Then
MsgBox "Please correct Item" & rngCell & " Amount Data"
End If
Next
This code is looking up Column B to see if column A has any same values.
The code works fine if I do not use second line of if statement, but when I tried to add second condition column A is matching B and column C's value is not greater than 0, it does not work. How can I make this code work?
I think you are trying to check,
1) if the value in A exist in column B and
2) if TRUE, is the value in column C less than or equal to 0.
If that's what you need, the code below should work.
Sub PullUniques()
LastRowColumnA = Cells(Rows.Count, 1).End(xlUp).Row
Dim rngCell As Range
For Each rngCell In Range("A1:A" & LastRowColumnA)
If WorksheetFunction.CountIf(Range("B1:B" & LastRowColumnA), rngCell) <> 0 And _
Range("C" & rngCell.Row).Value <= 0 Then
MsgBox "Please correct Item" & rngCell & " Amount Data"
End If
Next
End Sub
However, if your condition is,
1) if the value in A the same as value in B and
2) if TRUE, is the value in column C less than or equal to 0.
then you don't need to use countif
Sub PullUniques()
LastRowColumnA = Cells(Rows.Count, 1).End(xlUp).Row
Dim rngCell As Range
For Each rngCell In Range("A1:A" & LastRowColumnA)
Debug.Print Range("B" & rngCell.Row).Value & "/" & Range("C" & rngCell.Row).Value
If Range("B" & rngCell.Row).Value = rngCell And _
Range("C" & rngCell.Row).Value <= 0 Then
MsgBox "Please correct Item" & rngCell & " Amount Data"
End If
Next
End Sub

A faster way to compare text from different columns

Is there a faster way too compare text/data from different columns? It seems to take longer that desired to execute.
Sub StringCom2()
For Each C In Range("M2:M" & Range("M" & Rows.Count).End(xlUp).Row)
For Each L In Range("X2:X" & Range("X" & Rows.Count).End(xlUp).Row)
If C.Cells.Value = "Audio Accessories" And L.Cells.Value = "Headsets" Then
L.Cells.Offset(0, 18).Value = "Headphones"
End If
Next
Next
For Each C In Range("M2:M" & Range("M" & Rows.Count).End(xlUp).Row)
For Each L In Range("X2:X" & Range("X" & Rows.Count).End(xlUp).Row)
If C.Cells.Value = "Headsets & Car Kits" And L.Cells.Value = "Headsets" Then
L.Cells.Offset(0, 18).Value = "Headsets & Car Kits"
End If
Next
Next
End Sub
You could use "Autofilter()" method of "Range" object
like follows (not by my PC so there may be some typos and or range references/offset to adjust...):
Option Explicit
Sub StringCom2()
With Worksheets("Sheet1") '<--| '<-- change "Sheet1" with your actual sheet name
With .Range("M1:X" & .Cells(.Rows.Count, "M").End(xlUp).Row) '<--| reference its range in columns M:X from row 1 to column "M" last non empty cell row
.AutoFilter field:=1, Criteria1:="Headsets" '<--| filter referenced range on its 1st column ("M") with "Headsets"
.AutoFilter field:=12, Criteria1:="Audio Accessories" '<--|filter referenced range again on its 12th column ("X") with "Audio Accessories"
If Application.WorksheetFunction.Subtotal(103, .Resize(, 1)) > 1 Then .Offset(1, 19).Resize(.Rows.Count - 1, 1).SpecialCells(xlCellTypeVisible) = "Headphones"'<--| write in cells offsetted 19 columns right of the matching ones
.AutoFilter field:=12, Criteria1:="Headsets & Car Kits" '<--|filter referenced range again on its 12th column ("X") with "Headsets & Car Kits"
If Application.WorksheetFunction.Subtotal(103, .Resize(, 1)) > 1 Then .Offset(1, 19).Resize(.Rows.Count - 1, 1).SpecialCells(xlCellTypeVisible) = "Headsets & Car Kits"'<--| write in cells offsetted 19 columns right of the matching ones
End With
.AutoFilterMode = False '<--| show all rows back
End With
End Sub
Give this a try and let me know if it terminates faster:
Option Explicit
Sub StringCom_SlightlyImproved()
Dim C As Range, L As Range
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets(1)
With Application
.EnableEvents = False
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
For Each C In ws.Range("M2:M" & ws.Range("M" & ws.Rows.Count).End(xlUp).Row)
For Each L In ws.Range("X2:X" & ws.Range("X" & ws.Rows.Count).End(xlUp).Row)
If C.Value2 = "Headsets" Then
If L.Value2 = "Audio Accessories" Then L.Offset(0, 18).Value2 = "Headphones"
If L.Value2 = "Headsets & Car Kits" Then L.Offset(0, 18).Value2 = "Headsets & Car Kits"
End If
Next L
Next C
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
.EnableEvents = True
End With
End Sub
Changes:
declare all variables to avoid Variants which are slower in performance
turn off unnecessary Excel events, calculation, screen-updating for the sub
bring the two loops together to keep the iterations down
code explicitly
Update:
The following solution should be substantially faster as sheet-access has been limited to a bare minimum. Instead, all calculations / comparisons are completed in memory with variables:
Sub StringCom_Improved()
Dim ws As Worksheet
Dim arrResult As Variant
Dim arrHeadset As Variant
Dim arrAccessories As Variant
Dim i As Long, j As Long, maxM As Long, maxX As Long
Set ws = ThisWorkbook.Worksheets(1)
maxM = ws.Range("M" & ws.Rows.Count).End(xlUp).Row
arrHeadset = ws.Range("M2:M" & maxM).Value2
arrResult = ws.Range("AD2:AD" & maxM).Value2 ' column AD is column M with an offset of 18 columns
maxX = ws.Range("X" & ws.Rows.Count).End(xlUp).Row
arrAccessories = ws.Range("X2:X" & maxX).Value2
For i = LBound(arrHeadset) To UBound(arrHeadset)
For j = LBound(arrAccessories) To UBound(arrAccessories)
If arrHeadset(i, 1) = "Headsets" Then
If arrAccessories(j, 1) = "Audio Accessories" Then arrResult(i, 1) = "Headphones"
If arrAccessories(j, 1) = "Headsets & Car Kits" Then arrResult(i, 1) = "Headsets & Car Kits"
End If
Next j
Next i
ws.Range("AD2:AD" & maxM).Value2 = arrResult
End Sub
The faster way is to use Excel formulas
Sub StringCom2()
m = Range("M" & Rows.Count).End(xlUp).Row
x = Range("X" & Rows.Count).End(xlUp).Row
Set r = Range("X2:X" & x).Offset(, 18)
r.Formula = "= If( CountIf( M2:M" & m & " , ""Headsets"" ) , " & _
" If( X2 = ""Audio Accessories"" , ""Headphones"", " & _
" If( X2 = ""Headsets & Car Kits"" , X2 , """" ) , """" ) , """" ) "
r.Value2 = r.Value2 ' optional to replace the formulas with the values
End Sub

Resources