Appropriate Error Control for a Find & Delete statement - vba

currently I am trying to scan my book for an if statement I wrote, where it identifies unique ID's then returns "Not in Book" if that id no longer exists.
To clean the book of all the area's where I see "Not in Book" I created a small bit of code to purge all of those entries.
Dim MWS As Worksheet
Set MWS = Sheets("Marks")
MWS.Cells.Find(what:="Not in Book", LookIn:=xlValues).EntireRow.Delete
What is the appropriate way for this statement to...
A. Scan the entire book for this phrase and delete that row as opposed to deleting one row at a time.
B. To error control this statement so when "Not in Book" does not exist the user will not get an error, rather nothing will happen or a msgbox will appear stating the book is clean.
Thanks,

You can do it like this. Assign the search to a range variable and check if it's Nothing.
Sub x()
Dim rFind As Range, MWS As Worksheet
Set MWS = Sheets("Marks")
With MWS
Set rFind = .Cells.Find(what:="Not in Book", LookIn:=xlValues, _
Lookat:=xlWhole, MatchCase:=False, SearchFormat:=False)
If Not rFind Is Nothing Then
Do
rFind.EntireRow.Delete
Set rFind = .Cells.Find("Not in Book")
Loop Until rFind Is Nothing
Else
msgbox "Term not found"
End If
End With
End Sub

I think the simplest way is a loop through your cells and check if the cell value equals your word then delete the row. Here is the code.
Sub CleanTheSheet()
dim rng as range
for each rng in activesheet.usedrange.cells ' Here you can specify name of your sheet or the range if you have such a info
if rng.value ="Not in Book" then rng.entirerow.delete
next
if worksheetfunction.countif(activesheet.usedrange,"Not in Book") = 0 then msgbox "The Sheet is Clean Now!"
END SUB

Related

How do I change my macro into a Worksheet_Change event Excel VBA

I have a macro that I call when the workbook closes. It checks the columns in two tables on separate worksheets and assigns row numbers based on what it finds.
Worksheet_Change handler is located on the sheet with Projects range. Database range is located on another worksheet in the same workbook.
Whenever I call the macro anywhere else, it either generates an error or causes an usual bug where excel is partially frozen (anyone know what the hell this is?!?!?!).
Anyway, my last resort before giving up is to change the macro into a worksheet change event and I was wondering if I could get some help creating this.
The original macro:
Sub FindRow()
'This module verifies row numbers in the database by matching them to the opportunities in the Projects
'worksheet. It then assigns row numbers in the Projects worksheet.
Application.ScreenUpdating = False
Dim LastRow As Long
LastRow = Application.ThisWorkbook.Sheets("Projects").Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
Dim rng As Range
Dim foundRng As Range
For Each rng In Sheets("Projects").Range("B2:B" & LastRow)
Set foundRng = Sheets("Database").Range("C:C").Find(rng, LookIn:=xlValues, lookat:=xlWhole)
If Not foundRng Is Nothing Then
rng.Offset(0, -1) = foundRng.Row
End If
Next rng
Application.ScreenUpdating = True
End Sub
My proposed change:
Public Sub Worksheet_Change(ByVal Target As Range)
Dim Records As Range
Set Records = Range("Records")
If Not Application.Intersect(Records, Range(Target.Address)) Is Nothing Then
Application.ScreenUpdating = False
Dim LastRow As Long
LastRow = Application.ThisWorkbook.Sheets("Projects").Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
Dim rng As Range
Dim foundRng As Range
For Each rng In Sheets("Projects").Range("B2:B" & LastRow)
Set foundRng = Sheets("Database").Range("C:C").Find(rng, LookIn:=xlValues, lookat:=xlWhole)
If Not foundRng Is Nothing Then
rng.Offset(0, -1) = foundRng.Row
End If
Next rng
Application.ScreenUpdating = True
End If
End Sub
However, I keep getting an error on the line that defines the variable LastRow.
I get an application-defined error even though everything is defined properly before.
Thanks in advance.
Worksheet_Change handles is located on the sheet with Projects range. Database range is located on another worksheet in the same workbook. – Remi 1 min ago
This means rng is also on the Projects sheet:
For Each rng In Sheets("Projects").Range("B2:B" & LastRow)
(BTW Me.Range("B2:B" & LastRow) would have been much less ambiguous here)
You're handling a Worksheet_Change event on the Projects sheet, which Excel fires whenever a cell value changes on the Projects sheet. Then inside that handler, you do this:
rng.Offset(0, -1) = foundRng.Row
With rng being a range on the Projects sheet, you're entering a recursive cycle of sheet changes, and that is likely what's crashing your code.
When you make worksheet changes while handling worksheet changes, you need to tell Excel "it's okay, I got this", by preventing it from re-firing the Worksheet.Change event every time:
Application.EnableEvents = False
'...code...
Application.EnableEvents = True
Moreover, when you toggle Application.ScreenUpdating = False, you're telling Excel "don't repaint yourself until I say so" - that can speed things up considerably, in a lot of cases, however it also means you need to toggle it back on manually if something bad happens.
You can avoid this by implementing an error handler - here's the principle:
Sub DoSomething()
On Error GoTo CleanFail
Application.EnableEvents = False
Application.ScreenUpdating = False
'...code...
CleanExit:
Application.EnableEvents = True
Application.ScreenUpdating = True
Exit Sub
CleanFail:
Debug.Print Err.Description
Stop
Resume CleanExit
Resume 'F8 takes you to the error-throwing statement
End Sub
You have not indicated what sheet is the activesheet that has the worksheet_change event.
Any way, here is a code to find the last row in column B sheet "Projects"
Dim LastRow As Long, sh As Worksheet
Set sh = Sheets("Projects")
LastRow = sh.Cells(sh.Rows.Count, "B").End(xlUp).Row
MsgBox LastRow & " is the last row in Column B Sheet Projects!"

Find cell containing greater 255 characters

My code below works perfectly to find a cell on a different worksheet when the string is small, however large text strings pull up an error. I have tried using error handling even just to give a MsgBox rather than open a VBA window when it errors.
Can anyone help, preferably find the cell with many characters or if not possible, put an error handler in to say something like, too large to search.
What the code does, is a have a range of cells with text in each cell. I can click on that cell, or a cell 2 columns to the right, then click the FIND button, to go in the next worksheet to find the exact same cell value. All cells are unique.
Sub Find_Cell()
Dim NA As Worksheet
Set NA = Worksheets("Notes Analysis")
LastRow = NA.Cells(Rows.Count, 2).End(xlUp).Row
On Error Resume Next
If Not Intersect(ActiveCell, Range("G19:G" & LastRow)) Is Nothing Then
Dim value As String 'Declare a string
value = ActiveCell.Offset(, -2) 'Get the value of the selected Cell
Dim ws As Worksheet
'ws is the worksheet from we are searching the value
'You have to change myWorkSheetName for you worksheet name
Set ws = ThisWorkbook.Worksheets("DEBT_SALE_ACTIVITY")
ws.Activate
Dim c As Range 'Declare a cell
Set c = ws.Cells.Find(value, LookIn:=xlValues) 'Search the value
If Not c Is Nothing Then 'If value found
c.Activate 'Activate the cell, select it
Else
MsgBox "Not found" 'shows a message "Not Found"
End If
Else
If Not Intersect(ActiveCell, Range("E19:E" & LastRow)) Is Nothing Then
Dim value2 As String 'Declare a string
value2 = ActiveCell 'Get the value of the selected Cell
Dim ws2 As Worksheet
'ws is the worksheet from we are searching the value
'You have to change myWorkSheetName for you worksheet name
Set ws2 = ThisWorkbook.Worksheets("DEBT_SALE_ACTIVITY")
ws2.Activate
Dim c2 As Range 'Declare a cell
Set c2 = ws2.Cells.Find(value2, LookIn:=xlValues) 'Search the value
If Not c2 Is Nothing Then 'If value found
c2.Activate 'Activate the cell, select it
Else
MsgBox "Not found" 'shows a message "Not Found"
End If
Else
MsgBox "Select an Account Note"
End If 'end the If for if active cell is in our notes
End If 'end the If for if active cell is in Account note
End Sub
To provide an error message indicating the text is too long you could do the following:
Add this after each statement where you assign value its value:
value = ActiveCell.Offset(, -2) 'Get the value of the selected Cell
If Len(value) > 255 Then
MsgBox "Text in cell " & CStr(ActiveCell.Address) & " is too long", vbOKOnly, "Search Text Too Long"
Exit Sub
End If
Also, you might want to change your if...then...else code structure.
Currently your code is operating like this:
If Not Intersect(ActiveCell, Range("G19:G" & LastRow)) Is Nothing Then
do things
exit sub
Else
If Not Intersect(ActiveCell, Range("E19:E" & LastRow)) Is Nothing Then
do things
exit sub
Else
MsgBox "Select an Account Note"
exit sub
Which, based on your comments for your End If's isn't exactly what your message box says. If your first if statement is Account Notes and your second if statement is notes, then a better structure would be the following.
Change this code
Else
If Not Intersect(ActiveCell, Range("E19:E" & LastRow)) Is Nothing Then
To look like this
ElseIf Not Intersect(ActiveCell, Range("E19:E" & LastRow)) Is Nothing Then
Then the statement `MsgBox "Select an Account Note" will be accurate. You also be able to delete one of your End If statements.
Your code will operate like this:
If Not Intersect(ActiveCell, Range("G19:G" & LastRow)) Is Nothing Then
do things
exit sub
ElseIf Not Intersect(ActiveCell, Range("E19:E" & LastRow)) Is Nothing Then
do things
exit sub
Else
MsgBox "Select an Account Note"
exit sub

Targetting a Range With Dynamic Row Reference Based On Search

I'm real new to VBA coding and have been doing alright but I have now hit a wall with my final (and probably more complex than it needs to be) macro of the worksheet. I've been trying to make it work all weekend through multiple google searches and using various answers from stackoverflow's other questions to compile my own script, but to no avail. This is what I have so far (apologies coders, I know this will look like it was written by a 3 year-old):
Sub Build_Delete()
Dim rngA As Range
Dim cell As Range
Set rngA = Worksheets("Database").Range("D9:D177").End(xlUp)
For Each cell In rngA
If cell.Value = Range("A2").Value Then
cell.Select
Range("D" & ActiveCell.Row & ":AB" & ActiveCell.Row).Select
Selection.Delete
End If
Next cell
End Sub
The above works, no errors are returned, however it doesn't do anything noticeable.
I'm aware this is most likely atrocious, so this is what I am trying to do:
Database!D9:D177 contains the titles for a set of data in columns D to AB (4 to 28) .
There is an ActiveX Search Box that populates cell Database!A2 in real time with whatever is searched (eg. "Test" typed into Search Box, "Test" appears in cell Database!A2).
When I run the macro, I want it to check range Database!D9:D177 for the text string found in Database!A2, then delete the contents of columns D to AB for that row (eg. A2 = "test", Found "test" in cell D21, Delete D21:AB21).
The row is a dynamic value which is what is throwing me mostly with this, but the columns are fixed.
Also, the button for the macro is located on a separate worksheet (Front Page!), but the script will run solely on the Database! page.
Only needs to work in excel, not open office.
Only other thing I can think of that is relevant is that the cells can be left blank after deletion, they do not need to be filled, and the worksheet will never need to be printed so margins aren't an issue.
Optionally I would like to add an "Are You Sure? 'Yes' 'No' Msgbox at the start of the script, but I can play with that later as I know I am pushing my luck with this.
Any help would be greatly appreciated!
I always find it faster to use FIND rather than check the value of each cell.
If you want to find all values in case of duplicates you can go on to use .FINDNEXT(rFound) - https://msdn.microsoft.com/en-us/library/office/ff839746.aspx
Public Sub Build_Delete()
Dim rngA As Range
Dim rFound As Range
Dim wrkSht As Worksheet
Set wrkSht = ThisWorkbook.Worksheets("Database")
Set rngA = wrkSht.Range("D9:D177")
With rngA
Set rFound = .Find(wrkSht.Range("A2"), LookIn:=xlValues, LookAt:=xlWhole)
If Not rFound Is Nothing Then
If MsgBox(rFound.Value & " found on row " & rFound.Row & "." & vbCr & _
"Delete?", vbInformation + vbYesNo) = vbYes Then
rFound.EntireRow.Delete Shift:=xlUp
End If
End If
End With
End Sub

Search Excel workbook tabs for all entries which match a criteria and produce a list?

I have large spreadsheet with over 20+ tabs. each tab has the same structure
A=ref,B= Discipline, C=Location, D=item/location, E=Defect, F=Date, G=%complete
What i want to do is get the search for G column throughout the entire workbook and return a list of all items which are not listed "100". The result would appear on the summary page.
Over time the list of items would become smaller as more 100 are entered in to the G column.
Could you advise on how I could do this?
Thanks
You can use the Range.Find method in VBA.
https://msdn.microsoft.com/en-us/library/office/ff839746.aspx
This assumes there'll always be something in column A and copies column A:G on to a sheet called Summary
Sub Find100Percent()
Dim wrkSht As Worksheet
Dim rFoundCell As Range
Dim sFirstAddress As String
Dim shtSummary As Worksheet
Set shtSummary = ThisWorkbook.Worksheets("Summary")
For Each wrkSht In ThisWorkbook.Worksheets
If wrkSht.Name <> shtSummary.Name Then
With wrkSht.Columns(7)
Set rFoundCell = .Find(1, LookIn:=xlValues)
If Not rFoundCell Is Nothing Then
sFirstAddress = rFoundCell.Address
Do
rFoundCell.Offset(, -6).Resize(, 7).Copy _
shtSummary.Cells(shtSummary.Rows.Count, 1).End(xlUp).Offset(1)
Set rFoundCell = .FindNext(rFoundCell)
Loop While Not rFoundCell Is Nothing And rFoundCell.Address <> sFirstAddress
End If
End With
End If
Next wrkSht
End Sub

My range.find() is empty

I searched for the answer to this question, and came VERY CLOSE with
VBA: need decide if result of FIND method is NOTHING or "" (empty)
but I couldn't quite understand how to fix my problem. It is pretty much the same, I am using find to look up something in another workbook, and if the find() can't find what I'm looking for, it throws an error. I tried the suggestions in the link above, but I agree with https://stackoverflow.com/users/478884/tim-williams that since my object is still empty, that the IIF() will error still.
Do Until Row > LastRow
On Error GoTo MFGPNError
PLRow = Workbooks(WB2).Sheets("5727").Range("C:C").Find(what:=Workbooks(WB1).Main.Cells(Row, 2).Value2, lookat:=xlWhole, searchorder:=xlByRows, MatchCase:=True).row
VREFLookup:
On Error GoTo VREFError
PLRow = Workbooks(WB2).Sheets("5727").Range("D:D").Find(what:=Workbooks(WB1).Main.Cells(Row, 2).Value2, lookat:=xlWhole, searchorder:=xlByRows, MatchCase:=True).row
Then my error trapping:
Exit Sub
MFGPNError:
If PLRow Is Empty Then
Workbooks(WB1).Main.Cells(Row, 3) = ""
'If IIf(PLRow Is Empty, "", PLRow) = "" Then
'setting MPN to be "" and moving to VREF lookup
On Error GoTo -1
GoTo VREFLookup
End If
'-----
Exit Sub
VREFError:
If IIf(PLRow Is Nothing, "", PLRow) = "" Then
'setting MPN to be "" and then adding cleaned up pn to array?
WB1.Main.Cells(Row, 3) = ""
On Error GoTo -1
GoTo CleanPN
End If
On my first Error Trap, I commented out the IIF() because it threw an error, and tried a regular IF(), still an error..
My question is, how can I keep going through my macro, if my Find() throws an error? I would like to just skip that particular Find(), and move on to the next row.
Also, is my error-handling any good? I've never really had to do much of it (mainly do to my macros being very simple)
I think this is your problem: the result of the .Find method is a range object, which can be Nothing. You are trying to evaluate Nothing.Row which raises an error.
Rather than deal with messy error handlers and confusing GoTo statements, it's best to simply trap that error and deal with it properly.
First, declare a range object and use that to return the result of the .Find.
Dim rngFound as Range
'## Attempt the lookup in Column C:
Set rngFound = Workbooks(WB2).Sheets("5727").Range("C:C").Find( _
what:=Workbooks(WB1).Main.Cells(Row, 2).Value2, _
lookat:=xlWhole, searchorder:=xlByRows, MatchCase:=True)
Then, you can deal with this rngFound variable, and test whether it's Nothing. If it is, then do another Find against column D:
'## If not found, look for it in column D:
If rngFound Is Nothing Then
Set rngFound = Workbooks(WB2).Sheets("5727").Range("D:D").Find( _
what:=Workbooks(WB1).Main.Cells(Row, 2).Value2, _
lookat:=xlWhole, searchorder:=xlByRows, MatchCase:=True)
End If
If the second find also fails, then you do something else which you already know how to do:
If rngFound Is Nothing Then
'## DO SOMETHING ELSE ##
End If
Then, you can assign to your PLRow variable
If rngFound Is Nothing then
PLRow = Empty '## Or modify as needed.
Else:
PLRow = rngFound.Row
End If
As a best practice, you should avoid using On Error GoTo... statements whenever possible, especially when the error can be suitably trapped without an error handler. Also, within your error handlers (if you absolutely must use them for some other reason), you should probably do Err.Clear and also Resume Next instead of GoTo VREFLookup.