Subroutine to fill in values in a range? - vba

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

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 select a date with a set cell color in Excel?

I am trying to create a macro in excel VBA, that searches the Range (B1:B30) of the value of the ActiveCell in Column “B” by a loop. Along with the search of Column, I also want to check if the date’s cell is colored with a particular color. If the date's cell equals the set color "Good", then I want it to change the color of the cell in Column H of the same row as selected to red.
When I run the code, I get an error message of “Run-time error ‘424’: Object required.” When I go to debug the problem, it highlights the .Find function I have and points to the last line of the search which is “SearchFormat:=False).Activate” What should I do to fix this problem?
Any improvement with my overall code will be very much appreciated.
Sub Find()
Dim FirstAddress As String
Dim MySearch As Variant
Dim Rng As Range
Dim I As Long
MySearch = Array(ActiveCell)
With Sheets("Sheet1").Range("B1:B30")
For I = LBound(MySearch) To UBound(MySearch)
Set Rng = .Find(What:=MySearch(I), _
After:=ActiveCell, _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
SearchFormat:=False).Activate
If Not Rng Is Nothing Then
FirstAddress = Rng.Address
Do
If ActiveCell.Style.Name = "Good" Then
Rng("H" & ActiveCell.Row).Select
Rng.Interior.ColorIndex = xlColorIndexRed
End If
Set Rng = .FindNext(Rng)
Loop While Not Rng Is Nothing And Rng.Address <> FirstAddress
End If
Next I
End With
End Sub
Showing the Debug mode of the run-time error.
Screenshot of the Spreadsheet for reference
Code Review:
You have several problems here.
MySearch = Array(ActiveCell) will always be a single value. So why bother looping through it
You cannot set a range to equal range.activate. Searching Sheets("Sheet1").Range("B1:B30") implies that you are searching a worksheet other that the ActiveSheet. If this is the case than .Find(After:=Activecell) suggests that you are looking for a value after the ActiveCell of another worksheet.
Set Rng = .Find(What:=MySearch(I), _
After:=ActiveCell, _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
SearchFormat:=False).Activate
Rng("H" & ActiveCell.Row) Rng is a Range object. It doesn't work like Range. You cannot pass it a cell address. You can do this Rng(1,"H") which is really shorthand for Rng.cells(1,"H") bit that is misleading because Rng is in column 2 Rng(1,"H") will reference the value in column I.
Sub Find()
Dim FirstAddress As String
Dim MySearch As Variant
Dim Rng As Range
Dim I As Long
MySearch = ActiveCell 'This is the ActiveCell of the ActiveSheet not necessarily Sheets("Sheet1")
With Sheets("Sheet1").Range("B1:B30")
Set Rng = .Find(What:=MySearch, _
After:=.Range("B1"), _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
SearchFormat:=False)
If Not Rng Is Nothing Then
FirstAddress = Rng.Address
Do
If Rng.Style.Name = "Good" Then
.Range("H" & Rng.Row).Interior.ColorIndex = xlColorIndexRed
End If
Set Rng = .FindNext(Rng)
Loop While Not Rng Is Nothing And Rng.Address <> FirstAddress
End If
End With
End Sub
UPDATE:
Here is the actual answer to your question:
Sub FindMatchingValue()
Const AllUsedCellsColumnB = False
Dim rFound As Range, SearchRange As Range
If AllUsedCellsColumnB Then
Set SearchRange = Range("B1", Range("B" & Rows.count).End(xlUp))
Else
Set SearchRange = Range("B1:B30")
End If
If Intersect(SearchRange, ActiveCell) Is Nothing Then
SearchRange.Select
MsgBox "You must select a cell in the highlighted area before continuing", vbInformation, "Action Cancelled"
Exit Sub
End If
Set rFound = SearchRange.Find(What:=ActiveCell.Value, _
After:=ActiveCell, _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
SearchFormat:=False)
If Not rFound Is Nothing Then
Do
If rFound.Style.Name = "Good" Then
Range("H" & rFound.Row).Interior.Color = vbRed
End If
Set rFound = SearchRange.FindNext(rFound)
Loop While Not rFound Is Nothing And rFound.Address <> ActiveCell.Address
End If
End Sub
You can't put Activate at the end of the findthe way you are trying to do.
Try this as you find statement.
Set Rng = .Find(What:=MySearch(I), _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
Rng.Activate
Then if you want to Activate the range, do that. But, it is best to stay away from Select, Activate etc in VBA code. I strongly suggest not using that last line of code and adjust you code to not rely on Select and Activate.
you may want to consider an Autofilter approach so as to loop only through relevant cells, as follows:
Option Explicit
Sub Find()
Dim cell As Range
With Sheets("Sheet1").Range("B1:B30")
.Rows(1).Insert '<--| insert a dummy header cell to exploit Autofilter. it'll be removed by the end
With .Offset(-1).Resize(.Rows.Count + 1) '<--| consider the range expanded up to the dummy header cell
.Rows(1) = "header" '<--| give the dummy header cell a dummy name
.AutoFilter field:=1, Criteria1:=ActiveCell '<--| filter range on the wanted criteria
If Application.WorksheetFunction.Subtotal(103, .Cells) > 1 Then '<--| if any cell other than "header" one has been filtered...
For Each cell In .Offset(1).Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible) '<--| ... loop through filtered cells only
If cell.Style.Name = "Good" Then cell.Offset(, 6).Interior.ColorIndex = 3 '<--| ... and color only properly styled cells
Next cell
End If
.AutoFilter '<--| .. show all rows back...
End With
.Offset(-1).Resize(1).Delete '<--|delete dummy header cell
End With
End Sub

VBA .Find, dates, serial number dates, and the data type properties (.Text, .Value, .Value2)

Using the Range.Find Method to iterate through each value in one range to see if it exists in the other. Issue is that the format for both ranges is different (but all the values are ostensibly dates), which results in many "false negatives" (i.e., values are not matching when they should be). is it possible to control the data type for both the input and search ranges so that the system will compare apples-to-apples and successfully recognize matches?
Here is the data and code:
Sheet1 (custom format, "yyyy-mm-dd")
A1 2016-01-01
A2 2016-01-02
A3 2016-01-03
A4 2016-01-04
A5 2016-01-05
Sheet2 (text format)
A1 2016-01-01
A2 2016-01-03
A3 2016-01-05
Sheet3 (display as "yyyy-mm-dd")
[NO DATA]
Sheet4 (display as "yyyy-mm-dd")
[NO DATA]
code:
Sub FindTest()
Dim inputRange As Range
Dim searchRange As Range
Dim found As Range
Set inputRange = Worksheets(1).Cells(1, 1).Resize(7, 1)
Set searchRange = Worksheets(2).Cells(1, 1).Resize(5, 1)
For Each i In inputRange
Set found = searchRange.Find _
(What:=i, _
after:=Cells(1, 1), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
If Not (found Is Nothing) Then
Worksheets(3).Cells(i.Row, i.Column) = i
Else
Worksheets(4).Cells(i.Row, i.Column) = i
End If
Next i
End Sub
For this to work, I assume that I need all the value in inputRange and searchRange to be compared as .Text, but I'm not sure to get there.
If you need to keep your data as true dates and text-that-looks-like-dates then format the true date values on the he fly to locate matches within the 'text-dates'.
Sub FindTest()
Dim inputRange As Range, i As Range
Dim searchRange As Range, found As Range
Set inputRange = Worksheets(1).Cells(2, 1).Resize(99, 1)
Set searchRange = Worksheets(2).Columns(1)
For Each i In inputRange
If IsDate(i) Then ' ▼ format into TXT here ▼
Set found = searchRange.Find(What:=Format(i.Value2, "yyyy-mm-dd"), _
after:=Cells(1), LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not (found Is Nothing) Then
With Worksheets(3)
.Cells(i.Row, i.Column) = i.Address(external:=True)
.Cells(i.Row, i.Column + 1) = i.Address(external:=True)
End With
Else
With Worksheets(4)
.Cells(i.Row, i.Column) = i.Address(external:=True)
.Cells(i.Row, i.Column + 1) = i.Address(external:=True)
End With
End If
End If
Next i
End Sub
Alternately, the Range.Text property should provide the same answer. Apply the Trim command to make sure that the inputRange dates are not formatted with a trailing _) in the format mask.
Set found = searchRange.Find(What:=Trim(i.Text), _
after:=Cells(1), LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
Addendum:
If there is a possibility of leading or trailing spaces in the searchRange, then change the parameters of your search to LookAt:=xlPart. While repairing the data beforehand would still be the better option, it will ignore extraneous characters that could interfere with a successful match.
If repairing the data can be considered an option then a blanket Range.TextToColumns method on the text values with a YMD xlColumnDataType will quickly convert your yyyy-mm-dd 'text-dates' into real dates.
With searchRange
.TextToColumns Destination: .Cells (1), DataType:=xlFixedWidth, _
FieldInfo:=Array(0, 5)
End With
This will remove leading/trailing extraneous characters and leave you with a column of true dates.
TextToColumn code (unsuccessful)
Sub FindTest()
Dim inputRange As Range
Dim i As Range
Dim searchRange As Range
Dim found As Range
Set inputRange = Worksheets(1).Cells(1, 1).Resize(7, 1)
Set searchRange = Worksheets(2).Cells(1, 1).Resize(5, 1)
searchRange.TextToColumns Destination:=Worksheets(2).Cells(1), DataType:=xlDelimited, FieldInfo:=Array(1, xlTextFormat)
For Each i In inputRange
Set found = searchRange.Find _
(What:=i, _
after:=Cells(1, 1), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
If Not (found Is Nothing) Then
Worksheets(3).Cells(i.Row, i.Column) = i
Else
Worksheets(4).Cells(i.Row, i.Column) = i
End If
Next i
End Sub

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

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

Select a column based on various possible header names

What's the most efficient way of selecting a column based on a variety of different possible header names? For example, the following gives me the column with header "school":
Rows("1:1").Select
Selection.Find(What:="School", _
After:=ActiveCell, _
LookIn:=xlFormulas, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False).Activate
ActiveCell.Select
Range(ActiveCell, ActiveCell.Offset(6536, 0)).Select
However, "school" could be "college" in another workbook, or "institution" in another. Should I just place the above code within an if-then-else statement and replace "school" with the other possibilities, or is there a more efficient way? And yes, this assumes that none of the possible header names co-exist within the same workbook.
Find is already very efficient. What's not efficient is all those Select's.
I suggest you wrap your Find Header logic into a Function, and refactor your code to avoid Select.
Private Function GetColumn(Header() As Variant, _
Optional NumRows As Long = 0, _
Optional ws As Worksheet = Nothing, _
Optional wb As Workbook = Nothing) As Range
Dim rng As Range, cl As Range
Dim i As Long
If wb Is Nothing Then
Set wb = ActiveWorkbook
End If
If ws Is Nothing Then
Set ws = wb.ActiveSheet
End If
Set rng = ws.UsedRange.Rows(1)
For i = LBound(Header) To UBound(Header)
Set cl = rng.Find(What:=Header(i), _
After:=rng.Cells(1, 1), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not cl Is Nothing Then
With ws
If NumRows = 0 Then
Set GetColumn = Range(cl, .Cells(.Rows.Count, cl.Column).End(xlUp))
Else
Set GetColumn = Range(cl, .Cells(NumRows, cl.Column))
End If
Exit Function
End With
End If
Next
Set GetColumn = Nothing
End Function
Call it like this
Dim rng As Range
Dim Headers() As Variant
Headers = Array("School", "Institution", "College")
' Active Workbook, Active Sheet
Set rng = GetColumn(Headers, 6536)
' All rows in specified column
' Specified sheet in Active workbook
Set rng = GetColumn(Headers, , Worksheets("SomeSheetName"))