coding a VBA excel function to search a string in a range - vba

I am a rookie on excel...
I am trying to create a function that takes a text string as parameter, trims it (ie removes the extra spaces at the end and at the beginning), searches for the first occurrence of the string in a range (on another spreadsheet), and returns the actual content of that cell..
I've written the code below but however I tweak it, it never returns anything!!
Any help would be much appreciated !
Note: online I've found several examples of "subs" that do similar things, but when I try to convert them to a "function", they never work...
Public Function Find_First2(FindString As String) As String
Dim Rng As Range
If Trim(FindString) <> "" Then
With Sheets("Sheet1").Range("A:A")
Set Rng = .Find(What:=FindString, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not Rng Is Nothing Then
Find_First2 = Rng.Value
Else
Find_First2 = ""
End If
End With
End If
End Function

You verify that trimming won't empty the whole string but you still use it as is. I changed a few things, but I don't get what this is supposed to do. You search for a string and if you find it, you return the same string? In any case, here is the code. I tested it and it works. It will look in column A of sheet Feuil1 right now. Modify to suit your needs.
Sub test()
MsgBox Find_First2("aa")
End Sub
Public Function Find_First2(FindString As String) As String
Dim Rng As Range
Dim TrimString As String
TrimString = Trim(FindString)
If TrimString <> "" Then
With Sheets("Feuil1").Range("A:A") 'This is what you need to modify
Set Rng = .Find(What:=TrimString, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not Rng Is Nothing Then
Find_First2 = Rng.Value
MsgBox ("Found at: " & Rng.Address)
Else
Find_First2 = ""
End If
End With
End If
End Function

Related

Transfer macro to UDF

I want the macro below transferred to a UDF but I do not know how.
I want a udf where I select the Findstring and return it in the cell where is place the udf.
Can someone help me?
Sub Find_pipe()
Dim Findstring As String
Dim Location As String
Dim Rng As Range
Sub Find_First()
Dim Findstring As String
Dim Rng As Range
Findstring = InputBox("vul naam van leiding in")
If Trim(Findstring) <> "" Then
With Sheets("scenario 1V2").Range("A1:BP150")
Set Rng = .Find(What:=Findstring, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not Rng Is Nothing Then
Application.Goto Rng.Offset(1), True
Application.Goto ThisWorkbook.Worksheets("D en L berekening").Range("A1"), True
ThisWorkbook.Worksheets("D en L berekening").Range("U10").Value = Rng.Offset(1).Value
Else
MsgBox "Nothing found"
End If
End With
End If
End Sub
Try this:
Function FindPipe(Findstring As String)
Application.Volatile 'You need this if your UDF needs to update after changes in
' the search range
Dim f As Range
If Trim(Findstring) <> "" Then
With ThisWorkbook.Sheets("scenario 1V2").Range("A1:BP150")
Set f = .Find(What:=Findstring, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
End With
If Not f Is Nothing Then
FindPipe = f.Offset(1).Value
Else
FindPipe = "Not found"
End If
Else
FindPipe = ""
End If
End Function
Note the range to be searched is hard-coded in the UDF, so Excel doesn't know to recalculate your UDF if the search range is updated. I added Application.Volatile to take care of that but it may slow your workbook if you have a lot of formulas pointing to that UDF.

How to get column number in Excel VBA using regex?

I know how to return column number using .Match, .Find and with iteration (yuck).
Is it possible to have it done more quickly using regex?
Try Debug.Print Cells(5,7).Column, it'll output 7!
Here is an example of a .Find method and it'll output the column of the found cells!
Sub test_Eswemenasja()
Dim FirstAddress As String, cF As Range, SearchedStr as String
SearchedStr = "Cat"
ActiveSheet.Range("A1").Activate
With ActiveSheet.Cells
'First, define properly the Find method
Set cF = .Find(What:=SearchedStr, _
After:=ActiveCell, _
LookIn:=xlFormulas, _
LookAt:=xlPart, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
'If there is a result, keep looking with FindNext method
If Not cF Is Nothing Then
FirstAddress = cF.Address
Do
'Message with the column number!
MsgBox "Found in column : " & cF.column
Set cF = .FindNext(cF)
'Look until you find again the first result
Loop While Not cF Is Nothing And cF.Address <> FirstAddress
End If
End With
End Sub

Subroutine to fill in values in a range?

I've been trying to write a subroutine to find the first and last instances of a value in a row and fill the middle with the same value. However, I have trouble getting to work. I think the problem is with my range.find method to find the last instance. Can someone help me identify the error?
Sub ChangeBetween()
Dim FirstCell As Range
Dim LastCell As Range
Dim SearchTerm As String
Dim ChangeRange As Range
Dim ChangeCell As Range
'Define search term
SearchTerm = Range("A1").Value
'Find beginning and end of replacement range
Set FirstCell = Range("B2:I2").Find( _
What:=SearchTerm, _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlNext)
Set LastCell = Range("B2:I2").Find( _
What:=SeartTerm, _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious)
'Set replacement range
Set ChangeRange = Range(FirstCell, LastCell)
'Replace in range
For Each ChangeCell In ChangeRange
ChangeCell.Value = SearchTerm
Next ChangeCell
End Sub
I picked up a couple of (what I believe to be) logic holes by specifying the After:= parameter of the .Find method.
Option Explicit 'see footnote ¹
Sub ChangeBetween()
Dim firstCell As Range, lastCell As Range, searchTerm As String
With Worksheets("Sheet3")
'Define search term
searchTerm = .Range("A1").Value
'Find beginning and end of replacement range
With .Range("B2:I2")
'make sure there is at least one SearchTerm
If CBool(Application.CountIf(.Cells, searchTerm)) Then
Set firstCell = .Find(What:=searchTerm, _
After:=.Cells(.Columns.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlNext)
Set lastCell = .Find(What:=searchTerm, _
After:=.Cells(1), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious)
.Parent.Range(firstCell, lastCell) = searchTerm
End If
End With
End With
End Sub
¹ Setting Require Variable Declaration within the VBE's Tools ► Options ► Editor property page will put the Option
Explicit statement at the top of each newly created code sheet. This
will avoid silly coding mistakes like misspellings as well as influencing you to use the correct variable type in the variable
declaration. Variables created on-the-fly without declaration are all of the variant/object type. Using Option Explicit is
widely considered 'best practice'.
Quoting you
Set LastCell = Range("B2:I2").Find( _
What:=SeartTerm, _ 'this is a typo your variable is SearchTerm
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious)
If I may, why to loop thorugh each cell from final and first?
'Replace in range
For Each ChangeCell In ChangeRange
ChangeCell.Value = SearchTerm
Next ChangeCell
Try:
ChangeRange.Value = SearchTerm

Problems passing parameters between subs

I have my main sub as per below:
Option Explicit
Public y As String
Public xCell As Range
Sub BenAppMr()
Call SearchFor("Award")
Call ConvertToLetter(xCell)
MsgBox "The column letter is " & y
End Sub
and then my two other subs which I call from above:
Sub SearchFor(z As String)
xCell = Cells.Find(What:=z, After:=ActiveCell, LookIn:= _
xlFormulas, LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:= _
xlNext, MatchCase:=False, SearchFormat:=False)
End Sub
and
Sub ConvertToLetter(x As Range)
y = Split(x.Address(1, 0), "$")(0)
End Sub
Am I missing something here? I can't quite understand why this won't work.
I'm looking to search of "Award" in my excel sheet and convert the column number to a letter. I'm looking to pass these parameters as I'll be calling a few searches and a few conversions in my main sub (once it's working)
It's been a long time since I used this kind of set up, normally I just call procedures without passing a parameter but it would be a lot cleaner this way.
Any help would be much appreciated.
Using Sub's to set Global variables is not a good coding pattern - you would be much better off using functions to return the values directly to the calling code:
Sub BenAppMr()
Dim y As String, xCell As Range
Set xCell = SearchFor("Award")
If Not xCell Is Nothing Then
y = ConvertToLetter(xCell)
MsgBox "The column letter is " & y
Else
MsgBox "Search value not found!"
End If
End Sub
Function SearchFor(z As String) As Range
Dim xCell As Range
Set xCell = ActiveSheet.Cells.Find(What:=z, After:=ActiveCell, LookIn:= _
xlFormulas, LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:= _
xlNext, MatchCase:=False, SearchFormat:=False)
Set SearchFor = xCell
End Function
Function ConvertToLetter(x As Range) As String
ConvertToLetter = Split(x.Address(1, 0), "$")(0)
End Function
...and use Set for object-type variables as Rory pointed out.

With Sheets("").Range("B:B"); Variable range when searching

Here´s the thing, I have a code that uses a fixed range in a fixed sheet to search for a value. I need now to make the sheet variable. I´ve tried couple of things with no luck so far.
The name of the sheet where I need to search is determined by a cell in another sheet. Replacing the 3rd line with something like: "With Sheets("Sheets("asd").range("A1")").Range("B:B") does not work.
My code:
FindString = W
If Trim(FindString) <> "" Then
With Sheets(**"CARS"**).Range("B:B")
Set Rng = Cells.Find(What:=FindString, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not Rng Is Nothing Then
Application.Goto Rng, True
Else
MsgBox "Nothing found"
End If
End With
End If
Let me know if more info is required.
Thanks!
Try omitting some of the doublequotes: With Sheets(Sheets("asd").range("A1")).Range("B:B")
Explanation
When you do this, you'll get a compile error: Expected list separator or ):
With Sheets("Sheets("asd").range("A1")").Range("B:B")
This is because the double-quotes encapsulate a string literal, so in this case the string literal is "Sheets(", which raises an error at asd (and subsequent errors, too).
The solution is to simply refer to the Sheets("asd") object, there is no need to qualify that object within quotes :)
NOTE Brad identifies another potential error in your code, see his answer below.
replace "With Sheets("Sheets("asd").range("A1")").Range("B:B")
by:
dim VAR_Sheet as string
Var_Sheet=Sheets("asd").range("A1").value
With Sheets(Var_Sheet).Range("B:B")
...code...
end with
works also like this (same result):
With Sheets(Sheets("asd").range("A1").value).Range("B:B") 'removed quotes that made error
It looks like, while you types With you are actually not using it.
With Sheets("CARS").Range("B:B")
Set Rng = Cells.Find(What:=FindString, _ <~~ Cells references the active sheet
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not Rng Is Nothing Then
Application.Goto Rng, True
Else
MsgBox "Nothing found"
End If
End With
You need to start the statement with a dot to signify you are continuing the reference established int eh With
With Sheets("CARS").Range("B:B")
Set Rng = .Find(What:=FindString, _ <~~ .Cells
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not Rng Is Nothing Then
Application.Goto Rng, True
Else
MsgBox "Nothing found"
End If
End With
I assume the double * was for emphasis (Sheets(**"CARS"**) --> Sheets("CARS"))