I would like to delete a certain range (3 rows & 19 columns) in excel that contains specific string (lns) on the top left of the range, repeatedly. They appear in different rows and columns, but the range size is always the same.
I have written a following code but nothing happens:
For Each vCell In ActiveSheet.UsedRange
If InStr(vCell.Value, "*lns*") Then
Range(Cells(vCell.Row, vCell.Column), Cells(vCell.Row + 2, vCell.Column + 18)).Delete shift:=xlShiftUp
End If
Next
It might be faster to locate the cells with Find
Option Explicit
Sub MyMacro()
Const ROW_SIZE = 3
Const COL_SIZE = 19
Const SEARCH = "lns"
Dim rng As Range, cel As Range
Dim n As Integer, s As Long
Set rng = ActiveSheet.UsedRange
Set cel = rng.Find(SEARCH, LookIn:=xlValues, lookat:=xlPart, _
searchdirection:=xlPrevious)
Do While Not cel Is Nothing
cel.Resize(ROW_SIZE, COL_SIZE).Delete shift:=xlShiftUp
n = n + 1
Set cel = rng.FindPrevious
If n > 1000 Then MsgBox "Code Error in Do Loop", vbCritical: Exit Sub
Loop
MsgBox n & " blocks deleted", vbInformation
End Sub
Delete Range 'Blocks'
Option Explicit
Sub DeleteBlocks()
Const rCount As Long = 3
Const cCount As Long = 19
Const Criteria As String = "lns"
Dim ws As Worksheet: Set ws = ActiveSheet
Dim rg As Range: Set rg = ActiveSheet.UsedRange
Dim fCell As Range
Set fCell = rg.Find(Criteria, rg.Cells(rg.Rows.Count, rg.Columns.Count), _
xlFormulas, xlPart, xlByRows)
Dim drg As Range ' Delete Range
Dim brg As Range ' Block Range
Dim fCount As Long ' Found Count
Dim FirstAddress As String
If Not fCell Is Nothing Then
FirstAddress = fCell.Address
Do
Set brg = Nothing
On Error Resume Next ' if in last 2 rows or 18 last columns
Set brg = Intersect(rg, fCell.Resize(rCount, cCount))
On Error GoTo 0
If Not brg Is Nothing Then
fCount = fCount + 1
Set drg = GetCombinedRange(drg, brg)
Set fCell = rg.FindNext(fCell)
End If
Loop Until fCell.Address = FirstAddress
If Not drg Is Nothing Then
drg.Delete Shift:=xlShiftUp
End If
If fCount = 1 Then
MsgBox "1 block deleted.", vbInformation, "DeleteBlocks"
Else
MsgBox fCount & " blocks deleted", vbInformation, "DeleteBlocks"
End If
Else
MsgBox "No blocks found.", vbExclamation, "DeleteBlocks"
End If
End Sub
Function GetCombinedRange( _
ByVal BuiltRange As Range, _
ByVal AddRange As Range) _
As Range
If BuiltRange Is Nothing Then
Set GetCombinedRange = AddRange
Else
Set GetCombinedRange = Union(BuiltRange, AddRange)
End If
End Function
Related
I'm using a macro that is running into an error
Invalid qualifier
pointing to the i variable.
Sub Macro6()
Dim last As Long
Dim i As Long
With ActiveSheet
last = .Cells(.Rows.Count, 1).End(xlDown).Row
For i = last To 1 Step -1
If .Cells(i, 1).Value Like "X" Then
.Cells(i.End(xlDown), 1).EntireRow.Delete
End If
Next i
This macro is supposed to identify cell with value "X" (that will be located at the end of column A) and then delete all rows below that are empty.
Delete Below String
Application.Match
If you are expecting one occurrence of the string or you're after the first occurrence, then the safer and more efficient choice is using Application.Match.
Sub DeleteBelowFirst()
' Uses 'Application.Match'.
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
If ws.AutoFilterMode Then ws.AutoFilterMode = False (optionally)
Dim rg As Range: Set rg = ws.UsedRange
DeleteBelowFirstString rg, 1, "x" ' , True ' True would keep the found row
End Sub
Sub DeleteBelowFirstString( _
ByVal rg As Range, _
ByVal ColumnIndex As Long, _
ByVal CriteriaString As String, _
Optional ByVal ExcludeFoundRow As Boolean = False)
' If the worksheet is filtered, only the filtered (visible) rows will be deleted.
Const ProcName As String = "DeleteBelowFirstString"
Dim crg As Range: Set crg = rg.Columns(ColumnIndex)
Dim rIndex As Variant: rIndex = Application.Match(CriteriaString, crg, 0)
If IsError(rIndex) Then
MsgBox "Value not found.", vbExclamation, ProcName
Exit Sub
End If
Dim rCount As Long: rCount = rg.Rows.Count
Dim rOffset As Long: rOffset = rIndex - 1
If ExcludeFoundRow Then
rOffset = rOffset + 1
If rCount = rOffset Then
MsgBox "There's nothing below.", vbExclamation, ProcName
Exit Sub
End If
End If
Dim drg As Range: Set drg = rg.Resize(rCount - rOffset).Offset(rOffset)
Debug.Print ProcName & ": " & drg.Address & " deleted."
drg.Delete xlShiftUp
End Sub
Range.Find
If you're after the last occurrence, then Application.Match does not work and you could e.g. use Range.Find with its limitations. It will also work for a single occurrence.
Sub DeleteBelowLast()
' Uses 'Range.Find'.
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
If ws.AutoFilterMode Then ws.AutoFilterMode = False (mandatory)
Dim rg As Range: Set rg = ws.UsedRange
DeleteBelowLastString rg, 1, "x" ' , True ' True would keep the found row
End Sub
Sub DeleteBelowLastString( _
ByVal rg As Range, _
ByVal ColumnIndex As Long, _
ByVal CriteriaString As String, _
Optional ByVal ExcludeFoundRow As Boolean = False)
' Make sure the worksheet is not filtered or the Find method will fail.
Const ProcName As String = "DeleteBelowLastString"
Dim crg As Range: Set crg = rg.Columns(ColumnIndex)
' If the column contains formulas, instead of 'xlFormulas', use 'xlValues'
' and additionally make sure that no rows are hidden
' or the Find method will fail (hidden rows don't affect 'xlFormulas').
Dim fCell As Range: Set fCell = crg.Find( _
What:=CriteriaString, After:=crg.Cells(1), LookIn:=xlFormulas, _
LookAt:=xlWhole, SearchDirection:=xlPrevious) ' last occurrence
If fCell Is Nothing Then
MsgBox "Value not found.", vbExclamation, ProcName
Exit Sub
End If
Dim rCount As Long: rCount = rg.Rows.Count
Dim rOffset As Long: rOffset = fCell.Row - rg.Row
If ExcludeFoundRow Then
rOffset = rOffset + 1
If rCount = rOffset Then
MsgBox "There's nothing below.", vbExclamation, ProcName
Exit Sub
End If
End If
Dim drg As Range: Set drg = rg.Resize(rCount - rOffset).Offset(rOffset)
Debug.Print ProcName & ": " & drg.Address & " deleted."
drg.Delete xlShiftUp
End Sub
Note that both methods support wild characters.
I'd suggest you skip the loop, and use Range.Find instead.
Sub DeleteAllAfterX()
With ActiveSheet
Dim rng As Range
Set rng = .Range("A:A").Find(What:="X", LookIn:=xlValues, Lookat:=xlWhole)
If Not rng Is Nothing Then
.Rows(rng.Row & ":" & .Rows.Count).ClearContents
End If
End With
End Sub
Tried doing a search tool to the excel sheet (VBA) I'm working on.
So far every time I search for the text, it ends up filtering only the first row and not any row that has the value I'm looking for. I added a picture to show what it returns and the code as well. Is there anything I need to change to the code to make it search for all the data in the sheet instead of having it to show only one row? Any help is appreciated.
Search result of only the first row:
Sub SearchAllSheets()
Dim ws As Worksheet, OutputWs As Worksheet
Dim rFound As Range
Dim strName As String
Dim count As Long, LastRow As Long
Dim IsValueFound As Boolean
IsValueFound = False
Set OutputWs = Worksheets("sheet1") '---->change the sheet name as required
LastRow = OutputWs.Cells(Rows.count, "A").End(xlUp).Row
On Error Resume Next
strName = InputBox("What are you looking for?")
If strName = "" Then Exit Sub
For Each ws In Worksheets
If ws.Name <> "Output" Then
With ws.UsedRange
Set rFound = .Find(What:=strName, After:=.Cells(1, 1), LookIn:=xlValues, LookAt:=xlWhole)
If Not rFound Is Nothing Then
Application.Goto rFound, True
IsValueFound = True
'MsgBox rFound.Row
rFound.EntireRow.Copy
OutputWs.Cells(LastRow + 1, 1).PasteSpecial xlPasteAll
Application.CutCopyMode = False
LastRow = LastRow + 1
End If
End With
End If
Next ws
On Error GoTo 0
If IsValueFound Then
OutputWs.Select
MsgBox "Result pasted to Sheet Output"
Else
MsgBox "Value not found"
End If
End Sub
Try this:
Sub SearchAllSheets()
Dim ws As Worksheet, OutputWs As Worksheet
Dim rFound As Range, IsValueFound As Boolean
Dim strName As String
Dim count As Long, LastRow As Long
Set OutputWs = Worksheets("Output") '---->change the sheet name as required
LastRow = OutputWs.Cells(Rows.count, "A").End(xlUp).row
strName = Trim(InputBox("What are you looking for?"))
If strName = "" Then Exit Sub
For Each ws In Worksheets
If ws.Name <> OutputWs.Name Then
Debug.Print "Checking " & ws.Name
Set rFound = FindAll(ws.UsedRange, strName)
If Not rFound Is Nothing Then
Set rFound = rFound.EntireRow
count = rFound.Cells.count / Columns.count 'how many matched rows?
Debug.Print "Found " & count & " rows"
rFound.Copy OutputWs.Cells(LastRow + 1, 1)
LastRow = LastRow + count
IsValueFound = True
End If
End If
Next ws
If IsValueFound Then
OutputWs.Select
MsgBox "Result(s) pasted to Sheet " & OutputWs.Name
Else
MsgBox "Value not found"
End If
End Sub
'find all cells in range `rng` with value `val` and return as a range
Public Function FindAll(rng As Range, val As String) As Range
Dim rv As Range, f As Range
Dim addr As String
Set f = rng.Find(what:=val, After:=rng.Cells(rng.Cells.count), _
LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, MatchCase:=False)
If Not f Is Nothing Then addr = f.Address()
Do Until f Is Nothing
If rv Is Nothing Then
Set rv = f
Else
Set rv = Application.Union(rv, f)
End If
Set f = rng.FindNext(After:=f)
If f.Address() = addr Then Exit Do
Loop
Set FindAll = rv
End Function
I tried to implement this but I have a compiler error ("wrong qualification", or something like this, it's not an English version of Excel I have). I suppose it has to do with range / string things ?
Function SearchForTotal(givenLocation As Range, searchText As String) As Range
Debug.Print givenLocation 'gives $U$83
Dim startSearchFrom As String
'-1 because it's from previous column you'll be searching in
startSearchFrom = givenLocation.Offset(0, -1).Address
Debug.Print startSearchFrom
Dim i As Integer: i = startSearchFrom.Row
Do While i > 0
If (searchText = ThisWorkbook.Sheets("Sheet1").Range(startSearchFrom.column & i).Value) Then
Set SearchForTotal= Range(startSearchFrom.column & i)
Exit Do
End If
i = i - 1
Loop
End Function
The error comes from the line "Dim i As Integer: i = startSearchFrom.Row"
I also tried with the variable startSearchFrom as a range instead of a string (and then with the Set) but with this code I have a compiler error too ("types do not match").
startSearchFrom.column is a number so use .Cells(rowno,colno) rather than .Range()
Option Explicit
Function SearchForTotal(givenLocation As Range, searchText As String) As Range
Dim ws As Worksheet, iCol As Long, iRow As Long
Set ws = ThisWorkbook.Sheets("Sheet1")
'-1 because it's from previous column you'll be searching in
iCol = givenLocation.Offset(0, -1).Column
iRow = givenLocation.Row
Do While iRow > 0
If (searchText = ws.Cells(iRow, iCol).Value) Then
Set SearchForTotal = ws.Cells(iRow, iCol)
Exit Do
End If
iRow = iRow - 1
Loop
End Function
Sub test()
Debug.Print SearchForTotal(Range("U83"), "test").Address
End Sub
Find Value Using Loop
Using the Find method would certainly be a better (more efficient) way.
Option Explicit
Function SearchForTotalLoop( _
ByVal GivenLocation As Range, _
ByVal SearchText As String) _
As Range
If GivenLocation Is Nothing Then Exit Function
' There's nothing to left of column `A`:
If GivenLocation.Column = 1 Then Exit Function
'-1 because it's from the previous column you'll be searching in
Dim rgStart As Range: Set rgStart = GivenLocation.Offset(0, -1)
Dim ws As Worksheet: Set ws = GivenLocation.Worksheet
Dim r As Long: r = rgStart.Row
Dim Col As Long: Col = rgStart.Column
Do While r > 0
If ws.Cells(r, Col).Value = SearchText Then ' A<>a
' To ignore case i.e. 'A = a', rather use the following:
'If StrComp(ws.Cells(r, Col).Value, SearchText, vbTextCompare) = 0 Then
Set SearchForTotal = ws.Cells(r, Col)
Exit Do
End If
r = r - 1
Loop
End Function
Sub SearchForTotalTEST()
' s - Start
' f - Found
Dim sCell As Range: Set sCell = Range("B83")
Dim fCell As Range: Set fCell = SearchForTotal(sCell, "Total")
If fCell Is Nothing Then Exit Sub
MsgBox "Starting Cell: " & sCell.Address & vbLf _
& "Found Cell: " & fCell.Address & vbLf _
& "Found Value: " & fCell.Value, vbInformation, "Find Total"
End Sub
EDIT
Using the Find method, you could do something like the following (not tested).
Function SearchForTotal( _
ByVal GivenLocation As Range, _
ByVal SearchText As String) _
As Range
' These two could be additionally used as arguments of the function.
Const FirstRow As Long = 1
Const ColOffset As Long = -1
If GivenLocation Is Nothing Then Exit Function
' There's nothing to left of column `A`:
If GivenLocation.Column + ColOffset < 1 Then Exit Function
If FirstRow > GivenLocation.Row Then Exit Function
Dim ws As Worksheet: Set ws = GivenLocation.Worksheet
If GivenLocation.Column + ColOffset > GivenLocation.Columns.Count _
Then Exit Function
If FirstRow > GivenLocation.Rows.Count Then Exit Function
Dim lCell As Range: Set lCell = GivenLocation.Cells(1).Offset(0, ColOffset)
Dim fCell As Range: Set fCell = ws.Cells(FirstRow, lCell.Column)
Dim rg As Range: Set rg = ws.Range(fCell, lCell)
Dim rCell As Range
Set rCell = rg.Find(SearchText, , xlFormulas, xlWhole, , xlPrevious)
If rCell Is Nothing Then Exit Function
Set SearchForTotal = rCell
End Function
I would like to loop all the worksheets of a workbook changing the color of a cell with a specific string in it.
I use .Replace (I need MatchCase and lookat).
It replaces the text without regarding Case. (e.g. if in the array it is lowercase and the string found is uppercase it will be changed to lowercase). The only way to bypass this is to use MatchCase:= false and list all options, and it could be really inefficient.
Could I perform the action using .Find or another function?
Sub CellMarked()
Dim fndlist As Variant, x As Integer, sht as worksheet
fndlist = Array("Column1", "Column2")
For Each sht In ActiveWorkbook.Worksheets
With sht
For x = LBound(fndlist) To UBound(fndlist)
.Cells.Replace What:=fndlist(x), Replacement:=fndlist(x), _
lookat:=xlPart, SearchOrder:=xlByRows, MatchCase:=True, _
SearchFormat:=False, ReplaceFormat:=True
Application.ReplaceFormat.Font.Color = 255
Next x
End With
next sht
End Sub
you could use Find() method and build a helper Function:
Function GetCellsWithValue(sht As Worksheet, val As Variant, foundCells As Range) As Boolean
Dim found As Range
Dim firstAddress As String
With sht.UsedRange
Set foundCells = .Resize(1, 1).Offset(.Rows.Count) ' fill foundCells with a "dummy" found one to avoid 'If Not foundCells Is Nothing' check before any 'Union()' method call
Set found = .Find(what:=val, lookat:=xlPart, LookIn:=xlValues)
If Not found Is Nothing Then
firstAddress = found.Address
Do
Set foundCells = Union(foundCells, found)
Set found = .FindNext(found)
Loop While found.Address <> firstAddress
End If
Set foundCells = Intersect(.Cells, foundCells) ' get rid of the "dummy" found cell
End With
GetCellsWithValue = Not foundCells Is Nothing
End Function
that you could use in your "main" sub as follows:
Option Explicit
Sub CellMarked()
Dim fndlist As Variant, val As Variant, sht As Worksheet
Dim foundCells As Range
fndlist = Array("Column1", "Column2")
For Each sht In ActiveWorkbook.Worksheets
With sht
For Each val In fndlist
If GetCellsWithValue(sht, val, foundCells) Then foundCells.Font.Color = 255
Next
End With
Next sht
End Sub
Find Text Apply Fill
Sub CellMarked()
Dim rngFind As Range, rngU As Range
Dim fndlist As Variant
Dim strFirst As String
Dim i As Integer, x As Integer
fndlist = Array("Column1", "Column2")
For i = 1 To Worksheets.Count
With Worksheets(i)
For x = 0 To UBound(fndlist)
' Check if worksheet has no values.
If Not .Cells.Find("*", .Cells(.Rows.Count, Columns.Count), -4163, 2, 1) _
Is Nothing Then
' Find string.
Set rngFind = .Cells.Find(fndlist(x), _
.Cells(.Rows.Count, Columns.Count))
If Not rngFind Is Nothing Then
If Not rngU Is Nothing Then
Set rngU = Union(rngU, rngFind) ' All other occurrences.
Else
Set rngU = rngFind ' First occurrence.
End If
strFirst = rngFind.Address
' Check for other occurrences.
Do
Set rngFind = .Cells.FindNext(rngFind)
If rngFind.Address <> strFirst Then
Set rngU = Union(rngU, rngFind)
Else
Exit Do
End If
Loop
End If
End If
Next
' Apply formatting.
If Not rngU Is Nothing Then
rngU.Interior.Color = 255
' rngU.Font.Color = 255
Set rngU = Nothing
End If
End With
Next
End Sub
Change "strToFind" and try:
Option Explicit
Sub test()
Dim strToFind As String
Dim rng As Range, cell As Range
Dim ws As Worksheet
'String to Find is "Test"
strToFind = "Test"
With ThisWorkbook
For Each ws In .Worksheets
With ws
Set rng = .UsedRange
For Each cell In rng
If cell.Value = strToFind Then
cell.Interior.Color = RGB(255, 0, 0)
End If
Next cell
End With
Next ws
End With
End Sub
i am trying to allow the user to search up to 6 different types of strings( text). However i have tried it for up to 2 ,
Problem
but my code only performs the search correctly for the first one. However any of the searches after fisrt string are not achieving the objective.
Objective
The objective of the code is for it to find the string in the speficied row, then search that coloumn for values greater than zero, if so copy the whole row.
Private Sub btnUpdateEntry_Click()
Dim StringToFind As String
Dim SringToFind2 As String
Dim i As Range
Dim cell As Range
StringToFind = Application.InputBox("Enter string to find", "Find string")
StringToFind2 = Application.InputBox("Enter string to find", "Find string")
With Worksheets("Skills Matrix")
Set cell = .Rows(1).Find(What:=StringToFind, LookAt:=xlWhole, _
MatchCase:=False, SearchFormat:=False)
If Not cell Is Nothing Then
For Each i In .Range(cell.Offset(1), .Cells(.Rows.Count, cell.Column).End(xlUp))
If IsNumeric(i.Value) Then
If i.Value > 0 Then
i.EntireRow.Copy
Sheets("Data").Range("A" & Rows.Count).End(xlUp).Offset(1, 0).PasteSpecial
End If
End If
Next i
Else
Worksheets("Data").Activate
MsgBox "String not found"
End If
End With
End Sub
Thank you
Similar solution, designed for flexibility and speed:
Sub tgr()
Dim wb As Workbook
Dim wsSearch As Worksheet
Dim wsData As Worksheet
Dim rFound As Range
Dim rCopy As Range
Dim rTemp As Range
Dim aFindStrings() As String
Dim vFindString As Variant
Dim sTemp As String
Dim sFirst As String
Dim i As Long, j As Long
Dim bExists As Boolean
Set wb = ActiveWorkbook
Set wsSearch = wb.Sheets("Skills Matrix")
Set wsData = wb.Sheets("Data")
ReDim aFindStrings(1 To 65000)
i = 0
Do
sTemp = vbNullString
sTemp = InputBox("Enter string to find", "Find string")
If Len(sTemp) > 0 Then
bExists = False
For j = 1 To i
If aFindStrings(j) = sTemp Then
bExists = True
Exit For
End If
Next j
If Not bExists Then
i = i + 1
aFindStrings(i) = sTemp
End If
Else
'User pressed cancel or left entry blank
Exit Do
End If
Loop
If i = 0 Then Exit Sub 'User pressed cancel or left entry blank on the first prompt
ReDim Preserve aFindStrings(1 To i)
For Each vFindString In aFindStrings
Set rFound = Nothing
Set rFound = wsSearch.Rows(1).Find(vFindString, wsSearch.Cells(1, wsSearch.Columns.Count), xlValues, xlWhole)
If Not rFound Is Nothing Then
sFirst = rFound.Address
Do
For Each rTemp In wsSearch.Range(rFound.Offset(1), wsSearch.Cells(wsSearch.Rows.Count, rFound.Column).End(xlUp)).Cells
If IsNumeric(rTemp) And rTemp.Value > 0 Then
If rCopy Is Nothing Then
Set rCopy = rTemp.EntireRow
Else
Set rCopy = Union(rCopy, rTemp.EntireRow)
End If
End If
Next rTemp
Set rFound = wsSearch.Rows(1).FindNext(rFound)
Loop While rFound.Address <> sFirst
Else
MsgBox "[" & vFindString & "] not found."
End If
Next vFindString
If Not rCopy Is Nothing Then rCopy.Copy wsData.Cells(wsData.Rows.Count, "A").End(xlUp).Offset(1)
End Sub
Instead of storing your string's to search in seperate variables, put them into an array. You can iterate through arrays using a For Each loop so it's a perfect fit:
Private Sub btnUpdateEntry_Click()
Dim StringsToFind(1 to 6) As String
Dim StringToFind as Variant 'Array's demand that their elements be declared as variants or objects, but we know that the element will be a string
Dim i As Range
Dim cell As Range
'Iterate through your empty array and ask for values:
For Each StringToFind in StringsToFind
StringsToFind(StringToFind) = Application.InputBox("Enter string to find", "Find string")
Next StringToFind
With Worksheets("Skills Matrix")
'Now iterate again to search:
For Each StringToFind in StringsToFinds
Set cell = .Rows(1).Find(What:=StringToFind, LookAt:=xlWhole, _
MatchCase:=False, SearchFormat:=False)
If Not cell Is Nothing Then
For Each i In .Range(cell.Offset(1), .Cells(.Rows.Count, cell.Column).End(xlUp))
If IsNumeric(i.Value) Then
If i.Value > 0 Then
i.EntireRow.Copy
Sheets("Data").Range("A" & Rows.Count).End(xlUp).Offset(1, 0).PasteSpecial
End If
End If
Next i
Else
Worksheets("Data").Activate
MsgBox "String not found"
End If
Next StringToFind
End With
End Sub
There's probably some other tweaks inside that second for loop to make so it makes sense when you iterate, but this will get you in the ballpark.