Search for all values between 2 values in a column and loop till last one found - excel

Lets start with I am self taught in Excel VBA and have a question that might seem stupid or basic:
I have the following information on a sheet:
[ConfBlastPlan]
DRB1065
PU1962;427;05_37_OB;A;2;2;1
PU1963;364;05_37_OB;B;2;2;1
PU1959;373;05_37_OB;C;2;2;1
-
[FiringProcedure]11:55:21;MULTI
What I want to do is combine all strings between with "PU" and the first ";" that is found between the
"[ConfBlastPlan]" and [FiringProcedure] into one cell.
I have read up about the loop function but seems I have confused myself terribly.
How do I loop this and combine the strings found?
I have started the function using the following code:
Sub DRBEquipNumberPU() 'GET THE PU#s
Dim WSFrom As Worksheet
Dim WSTo As Worksheet
Dim RngFrom As Range
Dim RngTo As Range
Dim BlastNumber As String
Dim BlastNumberStep As Long
Dim SearchString As String
Dim SearchStringStart As String
Dim SearchStringEnd As String
Dim LineStep As Long
Dim Blastedrng As Range
Dim BlastedFoundrng As Range
Dim closePos As Integer
BlastNumberStep = 1
LineStep = 1
Set Blastedrng = ThisWorkbook.Worksheets("Blast Summary Sheet").Range("A2", Range("A2").End(xlDown))
For Each BlastedFoundrng In Blastedrng.Cells
On Error Resume Next
SearchString = "[ConfBlastPlan]"
SearchStringStart = "PU"
SearchStringEnd = "[FiringProcedure]"
BlastNumber = CStr("Blasted " & BlastNumberStep)
Set WSFrom = Worksheets(CStr(BlastNumber))
Set RngFrom = WSFrom.Cells.Find(What:=SearchString, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
Set RngFrom1 = WSFrom.Cells.Find(What:=SearchStringStart, After:=RngFrom, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
Set WSTo = ThisWorkbook.Worksheets("Blast Summary Sheet")
Set RngTo = WSTo.Cells.Find(What:=(CStr(BlastNumber)), LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
closePos = InStr(1, RngFrom.Cells.Value, ";")
If RngTo.Cells.Offset(0, 4).Value = "INCOMPLT" Then
RngTo.Cells.Offset(0, 7).Value = "INCOMPLT"
ElseIf RngFrom.Cells.Value Is Nothing Then
RngTo.Cells.Offset(0, 7).Value = "NO PU #s"
ElseIf RngFrom.Cells.Value Like SearchStringStart Then
RngTo.Cells.Offset(0, 7).Value = Mid(RngFrom.Cells.Value, 0, closePos)
ElseIf RngFrom.Cells.Value = SearchStringEnd Then
End If
BlastNumberStep = BlastNumberStep + 1
Next BlastedFoundrng
End Sub
All it returns at the moment is INCOMPL or NO PU #s
There can be a maximum of 48 instances of PU
Please help
Blasted 23:
Blasted 26:
Blasted 27:

Option Explicit
' Major changes: make it two steps-- 1)Get all Sheet names, 2)Process all Lines on one sheet
Sub StepThruBlastedSheetNames() 'GET THE PU#s
Dim WSSummary As Worksheet, rowSummary As Long
Set WSSummary = ThisWorkbook.Worksheets("Blast Summary Sheet")
rowSummary = 1
Dim WSFrom As Worksheet
For Each WSFrom In ThisWorkbook.Worksheets
If InStr(WSFrom.Name, "Blasted ") > 0 Then
StepThruBlastedLines WSSummary, rowSummary, WSFrom
End If
Next
End Sub
Sub StepThruBlastedLines(WSSummary As Worksheet, rowSummary As Long, WSFrom As Worksheet)
' these never change, ergo do not put inside loop
Const SearchStringStart As String = "[ConfBlastPlan]"
Const SearchStringFindPU As String = "PU"
Const SearchStringEnd As String = "[FiringProcedure]"
Dim rowFrom As Long
Dim rowMax As Long
rowMax = WSFrom.Cells(WSFrom.Rows.Count, "A").End(xlUp).Row
Dim IsBetween As String, PUlist As String, posSemi As Long, DRBname As String
IsBetween = "N"
PUlist = ""
DRBname = ""
For rowFrom = 1 To rowMax
If IsBetween = "Y" Then
If InStr(WSFrom.Cells(rowFrom, "A"), "DRB") > 0 Then
DRBname = WSFrom.Cells(rowFrom, "A")
End If
If InStr(WSFrom.Cells(rowFrom, "A"), SearchStringFindPU) > 0 Then
posSemi = InStr(WSFrom.Cells(rowFrom, "A"), ";")
PUlist = PUlist & Mid(WSFrom.Cells(rowFrom, "A"), 1, posSemi)
End If
If InStr(WSFrom.Cells(rowFrom, "A"), SearchStringEnd) > 0 Then
IsBetween = "N"
rowSummary = rowSummary + 1
WSSummary.Cells(rowSummary, "A") = WSFrom.Name
WSSummary.Cells(rowSummary, "B") = DRBname
If PUlist <> "" Then
WSSummary.Cells(rowSummary, "C") = PUlist
PUlist = ""
Else
'<< add put empty notice
WSSummary.Cells(rowSummary, "C") = "INCOMPL"
End If
DRBname = "" '<<added
End If
ElseIf WSFrom.Cells(rowFrom, "A") = SearchStringStart Then
IsBetween = "Y"
End If
Next rowFrom
End Sub

Here's code that extracts the PU-values from a worksheet like the one you posted. I couldn't figure out why you called this worksheet WsTo and perhaps that's the reason why I also couldn't guess at your intention for what to do with the result. Your question is mute on the point. So I left the project at that point. I'm sure you will be able to pick it up from the two ways I'm displaying the Output array.
Sub DRBEquipNumberPU()
' 134
' Get the PU#s
Const Blast As String = "[ConfBlastPlan]"
Const BlastEnd As String = "-"
Const Marker As String = "PU"
Dim WsTo As Worksheet
Dim BlastFound As Range
Dim CellVal As String ' loop variable: Cell.Value
Dim R As Long ' loop counter: rows
Dim Output As Variant ' array of found values
Dim i As Long ' index to Output
Set WsTo = ThisWorkbook.Worksheets("Blast Summary Sheet")
With WsTo.Columns(1)
Set BlastFound = .Find(What:=Blast, _
LookIn:=xlValues, _
Lookat:=xlWhole, _
MatchCase:=False)
If BlastFound Is Nothing Then
MsgBox """" & Blast & """ wasn't found.", _
vbInformation, "No data to process"
Else
ReDim Output(1 To 100) ' choose UBound larger than you ever need
R = BlastFound.Row
Do
R = R + 1
CellVal = .Cells(R).Value
If InStr(1, Trim(CellVal), Marker, vbTextCompare) = 1 Then
i = i + 1
Output(i) = CellVal
End If
Loop While Len(CellVal) And CellVal <> BlastEnd
If i Then
ReDim Preserve Output(1 To i)
MsgBox "Found values = " & vbCr & _
Join(Output, Chr(13))
For i = LBound(Output) To UBound(Output)
Debug.Print Output(i)
Next i
End If
End If
End With
End Sub
It just occurs to me that the end marker you suggested ("FiringProcedure]") may be more reliable than my choice ("-"). If so, just change it at the top of the code where the constants are declared. If that marker is missed the code might continue to include the "PU" line below the [Blasting Plan] row.

Related

Finding key words in Excel with VBA

The file I am working on is picking the selected words from all the comments, colors them and segregates them into the dedicated tabs.
All keywords have been coded into the macro itself. Instead of writing the keywords to the macro, I want to tell the macro the keywords are located in an array in an excel sheet so everybody can use the file according to their needs.
When I made below changes for keywords to an array, I am getting below error on the screenshot that I do not know why.
Satellite:
KeyW = Array("Satellite", "image", "blacks out", "resolution")
Satellite:
KeyW = Array(Worksheets("MAIN").Range("N5:N15"))
The code below was not written by me. I just made some modifications.
Error that I am getting:
runtime error 13, Type mismatch
when I click debug it shows this yellow line
Sub sort()
Dim KeyW()
Dim cnt_Rows As Long, cnt_Columns As Long, curr_Row As Long, i As Long, x As Long
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Sheets(Array("Television", "Satellite", "News", "Sports", "Movies", "Key2", "Key3", "Error", "Commercial", "Key4", "TV", "Key5", "Key6", "Signal", "Key1", "Key7", "Design", "Hardware")).Select
Satellite:
KeyW = Array("Satellite", "image", "blacks out", "resolution")
KeyWLen = UBound(KeyW, 1)
j = 2
For i = 0 To KeyWLen
With Worksheets(1).Range("c4:e7000")
Set c = .Find(KeyW(i), LookIn:=xlValues, LookAt:=xlPart)
If Not c Is Nothing Then
firstAddress = c.Address
Do
Sheets("Satellite").Range("b" & j).Value = Worksheets(1).Range("a" & c.Row).Value
Worksheets(1).Range(c.Address).Copy
Sheets("Satellite").Activate
Range("a" & j).Select
Selection.PasteSpecial Paste:=xlPasteAllUsingSourceTheme, Operation:=xlNone _
, SkipBlanks:=False, Transpose:=False
Range("a" & j).Select
WordPos = 1
StartPos = 1
SearchStr = KeyW(i)
While WordPos <> 0
WordPos = InStr(StartPos + 1, Range("a" & j).Value, SearchStr, 1)
If WordPos > 0 _
Then
With ActiveCell.Characters(Start:=WordPos, Length:=Len(SearchStr)).Font
.FontStyle = "Bold"
.Color = -16727809
End With
StartPos = WordPos
End If
Wend
Worksheets(1).Activate
j = j + 1
Set c = .FindNext(c)
Loop While Not c Is Nothing And c.Address <> firstAddress
End If
End With
Next i
I'd start by splitting out some of the logic into standalone methods, and calling them from your main code: this makes it easier to see what's going on and allows some re-use of your code later on.
For example:
Sub sort()
Dim wb As Workbook
Dim txt As String, allCells As Collection, c As Range, w As Range, rngDest As Range
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Set wb = ThisWorkbook
'(removed sheet selection code - not needed here)
Set rngDest = wb.Worksheets("Satellite").Range("A2") 'start listing matches here
For Each w In wb.Worksheets("MAIN").Range("N5:N15").Cells 'loop over possible search terms
txt = Trim(w.Value)
If Len(txt) > 0 Then
Set allCells = FindAll(wb.Worksheets(1).Range("c4:e7000"), txt) 'get all matches
For Each c In allCells
c.Copy rngDest 'copy matched cell
BoldWord rngDest, txt 'bold matched text
rngDest.Offset(0, 1) = _
c.EntireRow.Columns("A").Value 'copy colA from matched cell
Set rngDest = rngDest.Offset(1) 'next result row
Next c
End If
Next w
End Sub
'return a Collection of all cells in `rng` which contain `txt`
Public Function FindAll(rng As Range, txt As String) As Collection
Dim rv As New Collection, f As Range
Dim addr As String
Set f = rng.Find(what:=txt, after:=rng.Cells(rng.Cells.Count), _
LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, MatchCase:=False)
If Not f Is Nothing Then addr = f.Address()
Do Until f Is Nothing
rv.Add f
Set f = rng.FindNext(after:=f)
If f.Address() = addr Then Exit Do
Loop
Set FindAll = rv
End Function
'Bold all instances of `wrd` in cell `c`
Sub BoldWord(c As Range, txt As String)
Dim pos As Long, start As Long
start = 1
Do
pos = InStr(start, c.Value, txt, vbTextCompare)
If pos = 0 Then Exit Do
With c.Characters(pos, Len(txt))
.Font.Bold = True
.Font.Color = vbRed
End With
start = pos + Len(txt)
Loop
End Sub

Search two words at once

I can search two words for example: LED LIGHT.
I want to search at once "LED LIGHT" or "LIGHT LED" no matter where "LED" or "LIGHT" is within the text.
Replacing the "space" between the words with wildcard "*" does help searching "LED LIGHT" regardless of the words position in text, but would not search in reverse "LIGHT LED".
Dim ws As Worksheet
Dim firstWord As String
Dim secondWord As String
Dim thirdWord As String
Dim LastRow1 As Long
Dim LastRow2 As Long
Dim LastRow3 As Long
On Error GoTo Whoa
Set ws = Sheet1
firstWord = InputBox("Enter word for bullet_points", "Keyword BOX")
secondWord = InputBox("Enter word for item_name", "Keyword BOX")
thirdWord = InputBox("Enter word for product_description", "Keyword BOX")
LastRow1 = Cells(Rows.Count, 8).End(xlUp).Row + 1
If firstWord = "" Then
ActiveSheet.Cells(LastRow1, 8).Value = "No INPUT"
Else
ActiveSheet.Cells(LastRow1, 8).Value = firstWord
End If
LastRow2 = Cells(Rows.Count, 9).End(xlUp).Row + 1
If secondWord = "" Then
ActiveSheet.Cells(LastRow2, 9).Value = "No INPUT"
Else
ActiveSheet.Cells(LastRow2, 9).Value = secondWord
End If
LastRow3 = Cells(Rows.Count, 10).End(xlUp).Row + 1
If thirdWord = "" Then
ActiveSheet.Cells(LastRow3, 10).Value = "No INPUT"
Else
ActiveSheet.Cells(LastRow3, 10).Value = thirdWord
End If
With ws
If firstWord <> "" Then ReplaceText ws.Range("B17:B4001"), firstWord
If secondWord <> "" Then ReplaceText ws.Range("C17:C4001"), secondWord
If thirdWord <> "" Then ReplaceText ws.Range("D17:D4001"), thirdWord
End With
Exit Sub
Whoa:
msgbox Err.Description
End Sub
Private Sub ReplaceText(rng As Range, txt As String)
Dim aCell As Range
Dim bCell As Range
Dim rngFound As Range
Set aCell = rng.Find(What:=txt, LookIn:=xlValues, _
LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not aCell Is Nothing Then
Set bCell = aCell
Set rngFound = aCell
Do
Set aCell = rng.FindNext(After:=aCell)
If Not aCell Is Nothing Then
If aCell.Address = bCell.Address Then Exit Do
Set rngFound = Union(rngFound, aCell)
Else
Exit Do
End If
Loop
End If
If Not rngFound Is Nothing Then
rngFound.Value = "XXXXXXXXXXXXX"
End If
I'm afraid your intention is only partially clear. Therefore my solution below consists of two unconnected parts. In the first part the user enters 3 search words and a record is made in Sheet1 of what was entered. In the second part, the function inaptly named ReplaceText, a cell is looked for that contains all the words the user entered. Note that "" will be found in every cell. Therefore, if the user entered blanks they will have no effect on the search. What will have an effect is that "LED Lighting" will be found if "LED Light" was looked for. Please bear that in mind.
Sub Test_Replace()
' 010
Dim searchWord(1 To 3) As String
Dim Clm As Long
Dim C As Long
Dim i As Integer
searchWord(1) = InputBox("Enter word for bullet_points", "Keyword BOX")
searchWord(2) = InputBox("Enter word for item_name", "Keyword BOX")
searchWord(3) = InputBox("Enter word for product_description", "Keyword BOX")
Clm = 2 ' first column to replace
With Sheet1
For C = 8 To 10
i = i + 1
If Len(searchWord(i)) = 0 Then searchWord(i) = "No INPUT"
.Cells(.Rows.Count, C).End(xlUp).Offset(1).Value = searchWord(i)
Clm = Clm + 1
Next C
' If firstWord <> "" Then ReplaceText Ws.Range("B17:B4001"), firstWord
' If secondWord <> "" Then ReplaceText Ws.Range("C17:C4001"), secondWord
' If thirdWord <> "" Then ReplaceText Ws.Range("D17:D4001"), thirdWord
End With
End Sub
Private Function ReplaceText(Rng As Range, _
searchWord() As String) As boolean
Dim Fnd As Range
Dim FndVal As String
Dim i As Integer
Set Fnd = Rng.Find(What:=searchWord(3), LookIn:=xlValues, _
LookAt:=xlPart, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not Fnd Is Nothing Then
Do Until Fnd Is Nothing
FndVal = Fnd.Value
' compare case-insensitive
For i = 2 To 1 Step -1
If InStr(1, FndVal, searchWord(i), vbTextCompare) = 0 Then Exit For
Next i
If i = 0 Then
Set Rng = Fnd
ReplaceText = True
Exit Do
End If
Set Fnd = Rng.FindNext(Fnd)
Loop
End If
End Function
In the first procedure the difference between my code and yours is in the replacement of the ActiveSheet with Sheet1. Observe that the variable Clm is set up to pass the range "B17:B4001", C and D, perhaps in the loop, but I didn't manage to logically connect this.
The function looks for the 3rd word first. If that is a blank the search can take a long time because every cell in the searched range qualifies. If searchWord(3) is found the code will look for (2) and (1) and return the cell as the result if all three are found. Else the function will look for the next pre-qualified cell. You can refine the qualifying process to make sure that Delight will not be mistaken for Light.
The function returns True or False, depending upon whether a match was found. If the answer is True the Rng variable passed to it as argument will contain the address of where the match was found. Here is the function call I used in my tests.
Private Sub TestFind()
Dim Rng As Range
Dim Sw() As String
Sw = Split(" One Two Three")
Set Rng = Range("A2:A25")
Debug.Print ReplaceText(Rng, Sw), Rng.Address
End Sub
If the function returned False Rng.Address will be "A2:A25"

Vba delete rows if cell in range is blank?

I have a worksheet like so:
Column A < - - - -
A |
B - - - - Range A30:A39
C |
|
< - - - -
Next Line
Text way down here
I am using this code to delete the empty cells in my range A30:39. This range sits above the 'Next Line' value.
wStemplaTE.Range("A30:A39").SpecialCells(xlCellTypeBlanks).EntireRow.Delete
In an ideal world, this code should cause this to happen:
Column A
A
B
C
Next Line
Text way down here
But instead it's causing the last bit of text to shift upwards like this:
Column A
A
B
C
Next Line
Text Way down here
Next Line and Text way down here are not even in this range.
Can someone show me what i am doing wrong?
My Entire code:
Sub Create()
'On Error GoTo Message
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Dim WbMaster As Workbook
Dim wbTemplate As Workbook
Dim wStemplaTE As Worksheet
Dim i As Long
Dim LastRow As Long
Dim rngToChk As Range
Dim rngToFill As Range
Dim rngToFill2 As Range
Dim rngToFill3 As Range
Dim rngToFill4 As Range
Dim rngToFill5 As Range
Dim rngToFill6 As Range
Dim rngToFill7 As Range
Dim rngToFill8 As Range
Dim rngToFill9 As Range
Dim rngToFil20 As Range
Dim CompName As String
Dim TreatedCompanies As String
Dim FirstAddress As String
'''Reference workbooks and worksheet
Set WbMaster = ThisWorkbook
'''Loop through Master Sheet to get company names
With WbMaster.Sheets(2)
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
'''Run Loop on Master
For i = 2 To LastRow
'''Company name
Set rngToChk = .Range("B" & i)
CompName = rngToChk.value
If InStr(1, TreatedCompanies, CompName) Or CompName = vbNullString Then
'''Company already treated, not doing it again
Else
'''Open a new template
Set wbTemplate = Workbooks.Open("G:\BUYING\Food Specials\2. Planning\3. Confirmation and Delivery\Announcements\Templates\template.xlsx")
Set wStemplaTE = wbTemplate.Sheets(1)
'''Set Company Name to Template
wStemplaTE.Range("C12").value = CompName
wStemplaTE.Range("C13").value = rngToChk.Offset(, 1).value
wStemplaTE.Range("C14").value = rngToChk.Offset(, 2).value
wStemplaTE.Range("C15").value = rngToChk.Offset(, 3).value
wStemplaTE.Range("C16").value = Application.UserName
wStemplaTE.Range("C17").value = Now()
wStemplaTE.Range("A20").value = "Announcement of Spot Buy Promotion - Week " & ThisWorkbook.Worksheets(1).Range("I8").value & " " & ThisWorkbook.Worksheets(1).Range("T8").value
Dim strDate
Dim strResult
strDate = rngToChk.Offset(, 14).value
wStemplaTE.Range("C25").value = "Week " & ThisWorkbook.Worksheets(1).Range("I8").value & " " & ThisWorkbook.Worksheets(1).Range("T8").value & " " & WeekdayName(Weekday(strDate)) & " (" & strDate & ")"
'Set Delivery Date
wStemplaTE.Range("C26").value = WeekdayName(Weekday(rngToChk.Offset(, 15).value)) & " (" & rngToChk.Offset(, 15).value & ")"
'''Add it to to the list of treated companies
TreatedCompanies = TreatedCompanies & "/" & CompName
'''Define the 1st cell to fill on the template
Set rngToFill = wStemplaTE.Range("A30")
Set rngToFill2 = wStemplaTE.Range("B30")
Set rngToFill3 = wStemplaTE.Range("C30")
Set rngToFill4 = wStemplaTE.Range("D30")
Set rngToFill5 = wStemplaTE.Range("E30")
Set rngToFill6 = wStemplaTE.Range("F30")
Set rngToFill7 = wStemplaTE.Range("G30")
Set rngToFill8 = wStemplaTE.Range("C13")
Set rngToFill9 = wStemplaTE.Range("C14")
Set rngToFil20 = wStemplaTE.Range("C15")
With .Columns(2)
'''Define properly the Find method to find all
Set rngToChk = .Find(What:=CompName, _
After:=rngToChk.Offset(-1, 0), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlNext, _
MatchCase:=False, _
SearchFormat:=False)
'''If there is a result, keep looking with FindNext method
If Not rngToChk Is Nothing Then
FirstAddress = rngToChk.Address
Do
'''Transfer the cell value to the template
rngToFill.value = rngToChk.Offset(, 7).value
rngToFill2.value = rngToChk.Offset(, 8).value
rngToFill3.value = rngToChk.Offset(, 9).value
rngToFill4.value = rngToChk.Offset(, 10).value
rngToFill5.value = rngToChk.Offset(, 11).value
rngToFill6.value = rngToChk.Offset(, 12).value
rngToFill7.value = rngToChk.Offset(, 13).value
'''Go to next row on the template for next Transfer
Set rngToFill = rngToFill.Offset(1, 0)
Set rngToFill2 = rngToFill.Offset(0, 1)
Set rngToFill3 = rngToFill.Offset(0, 2)
Set rngToFill4 = rngToFill.Offset(0, 3)
Set rngToFill5 = rngToFill.Offset(0, 4)
Set rngToFill6 = rngToFill.Offset(0, 5)
Set rngToFill7 = rngToFill.Offset(0, 6)
'''Look until you find again the first result
Set rngToChk = .FindNext(rngToChk)
Loop While Not rngToChk Is Nothing And rngToChk.Address <> FirstAddress
Else
End If
End With '.Columns(2)
Set Rng = Range("D30:G39")
Rng.Select
Set cell = Selection.Find(What:="#VALUE!", After:=ActiveCell, LookIn:=xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
If cell Is Nothing Then
'do it something
Else
For Each cell In Rng
cell.value = "TBC"
Next
'End For
wStemplaTE.Range("A41").value = "Please fill in the pallet factor and case size accordingly. Please amend total volume if necessary to accommodate full pallets."
End If
Rng.Select
Set cell = Selection.Find(What:="TBC", After:=ActiveCell, LookIn:=xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
If cell Is Nothing Then
'do it something
Else
wStemplaTE.Range("A41").value = "Please fill in the pallet factor and case size accordingly. Please amend total volume if necessary to accommodate full pallets."
End If
'Remove uneeded announcement rows
wStemplaTE.Range("A30:A39").SpecialCells(xlCellTypeBlanks).EntireRow.Delete
file = AlphaNumericOnly(CompName)
wbTemplate.SaveCopyAs filename:="G:\BUYING\Food Specials\2. Planning\3. Confirmation and Delivery\Announcements\2017\test\" & file & ".xlsx"
wbTemplate.Close False
End If
Next i
End With 'wbMaster.Sheets(2)
Application.DisplayAlerts = True
Application.ScreenUpdating = True
Dim answer As Integer
answer = MsgBox("Announcements Successfully Created." & vbNewLine & vbNewLine & "Would you like to view these now?", vbYesNo + vbQuestion, "Notice")
If answer = vbYes Then
Call List
Else
'do nothing
End If
Exit Sub
Message:
wbTemplate.Close savechanges:=False
MsgBox "One or more files are in use. Please make sure all Announcement files are closed and try again."
Exit Sub
End Sub
Function AlphaNumericOnly(strSource As String) As String
Dim i As Integer
Dim strResult As String
For i = 1 To Len(strSource)
Select Case Asc(Mid(strSource, i, 1))
Case 48 To 57, 65 To 90, 97 To 122: 'include 32 if you want to include space
strResult = strResult & Mid(strSource, i, 1)
End Select
Next
AlphaNumericOnly = strResult
End Function
Function FindAll(SearchRange As Range, _
FindWhat As Variant, _
Optional LookIn As XlFindLookIn = xlValues, _
Optional LookAt As XlLookAt = xlWhole, _
Optional SearchOrder As XlSearchOrder = xlByRows, _
Optional MatchCase As Boolean = False, _
Optional BeginsWith As String = vbNullString, _
Optional EndsWith As String = vbNullString, _
Optional BeginEndCompare As VbCompareMethod = vbTextCompare) As Range
End Function
Modify the column as you need. Right now it is working on column A. You can make it an argument to ask the user, like the second code
Public Sub DeleteRowOnCell()
'====================================================================================
'This macro will delete the entire row if a cell in the specified column is blank.
'Only one specified column is checked. Other columns are ignored.
'====================================================================================
On Error Resume Next
Range("A3:A" & Rows.count).SpecialCells(xlCellTypeBlanks).EntireRow.Delete
On Error GoTo 0
End Sub
Public Sub DeleteRowOnCellAsk()
'====================================================================================
'This macro will delete the entire row if a cell in the specified column is blank.
'Only one specified column is checked. Other columns are ignored.
'====================================================================================
Dim inp As String
inp = InputBox("Please enter a column name based on which blank rows will be deleted", "Which Column?")
Debug.Print inp & ":" & inp & Rows.count
On Error Resume Next
Range(inp & "1" & ":" & inp & Rows.count).SpecialCells(xlCellTypeBlanks).EntireRow.Delete
End Sub

How can I tell where Named Ranges are acutally used? [duplicate]

I have a list of 594 named ranges in a workbook with nearly 20 sheets, each sheet has about 200 columns of data. I need to find out where the named ranges are being used so as to remove irrelevant ones. I pasted a list of named ranges onto the sheet and then I tried to find if they were used in a formula by recording them, and then using the find method in all sheets and columns. The problem is despite using lookin xlformulas, it retrieves the named range even if it is just a text.
Here is my (updated) attempt (if it is not evident already, i am an amateur):
Application.ScreenUpdating = False
Count = ActiveWorkbook.Sheets.Count
Sheets(Count).Activate
Dim locr(1 To 595)
Dim locc(1 To 595)
Dim locn(1 To 595)
Dim nam(1 To 595)
Dim rng As Range
Range("a1").Select
For X = 1 To 595 'populate array with named ranges
ActiveCell.Offset(1, 0).Select
nam(X) = ActiveCell.Value
Next X
For i = 1 To 595 'name loop
For j = 1 To (Count - 1) 'sheet loop
Sheets(j).Activate
On Error Resume Next
Set orange = Sheets(j).Cells.SpecialCells(xlCellTypeFormulas) 'limit range to cells that only contain formulas
On Error GoTo 20 'if no formulas in sheet, go to next sheet
If Not orange Is Nothing Then
Set rng = orange.Find(What:=nam(i), _
LookIn:=xlFormulas, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False) 'find named range
If Not rng Is Nothing Then 'if named range found
Application.Goto rng, True 'go to cell where name range found and record address
locr(i) = ActiveCell.Row
locc(i) = ActiveCell.Column
locn(i) = ActiveSheet.Name
GoTo 10 'value found, go to next sheet
Else
End If
Else
End If
20 Next j
locr(i) = "" 'record empty since "rng" is empty
locr(i) = ""
locr(i) = ""
10 Next i
Sheets(Count).Activate
Range("c1").Select
b = 1
For a = 1 To 595 'populate addresses of named ranges
ActiveCell.Offset(b, 2).Value = locr(a)
ActiveCell.Offset(b, 1).Value = locc(a)
ActiveCell.Offset(b, 0).Value = locn(a)
b = b + 1
Next a
Here is one way I can think of. I will explain this in 2 parts.
PART 1
Let's say we have a named range Sid.
This word Sid can appear in any one of these forms as shown in the image below. Why does it start with =? That has been explained in Part2 below.
=Sid '<~~ 1
="Sid" '<~~ 2
=XSid '<~~ 3
=SidX '<~~ 4
=_Sid '<~~ 5
=Sid_ '<~~ 6
=(Sid) '<~~ 7
Any other scenarios, I guess will be a subset of the above. Now out of these the only valid find in our case is the first one and the last one since we are looking for our named range.
So here is a quick function to check if the cell formula has a named range or not. I am sure it can be made more efficient
Function isNamedRangePresent(rng As Range, s As String) As Boolean
Dim sFormula As String
Dim pos1 As Long, pos2 As Long, sLen As Long, i As Long
sFormula = rng.Formula: sLen = Len(sFormula)
pos2 = 1
Do
pos1 = InStr(pos2, sFormula, s) - 1
If pos1 < 1 Then Exit Do
isNamedRangePresent = True
For i = 65 To 90
'~~> A-Z before Sid for example XSid
If UCase(Mid(sFormula, pos1, 1)) = Chr(i) Then
isNamedRangePresent = False
Exit For
End If
Next i
'~~> Check for " for example "Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos1, 1)) = Chr(34) Then isNamedRangePresent = False
'~~> Check for underscore for example _Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos1, 1)) = Chr(95) Then isNamedRangePresent = False
pos2 = pos1 + Len(s) + 1
If pos2 <= sLen Then
For i = 65 To 90
'~~> A-Z after Sid for example SidX
If UCase(Mid(sFormula, pos2, 1)) = Chr(i) Then
isNamedRangePresent = False
Exit For
End If
Next i
'~~> "Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos2, 1)) = Chr(34) Then isNamedRangePresent = False
'~~> _Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos2, 1)) = Chr(95) Then isNamedRangePresent = False
End If
Loop
End Function
So in the first and the last case, Debug.Print isNamedRangePresent(Range("D2"), "Sid") will give you True See this
PART 2
Now coming to the .Find. I see that you are searching only once in the worksheet. Since you can have many scenarios of the word Sid being present, you cannot just have one .Find. You will have to use .FindNext. See THIS link on how to use that. I have explained it there so I won't bother explaining that here.
We can make our .Find more efficient by searching only those cells which has formulas. To do that we have to use .SpecialCells(xlCellTypeFormulas). This explains why we had "=" in our example in PART1. :)
Here is an example (PART1 Code added at the bottom)
Sub Sample()
Dim oRange As Range, aCell As Range, bCell As Range
Dim oSht As Worksheet
Dim strSearch As String, FoundAt As String
Set oSht = Worksheets("Sheet1")
'~~> Set your range where you need to find - Only Formula Cells
On Error Resume Next
Set oRange = oSht.Cells.SpecialCells(xlCellTypeFormulas)
On Error GoTo 0
If Not oRange Is Nothing Then
strSearch = "Sid"
Set aCell = oRange.Find(What:=strSearch, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not aCell Is Nothing Then
Set bCell = aCell
'~~> Check if the cell has named range
If isNamedRangePresent(aCell, strSearch) Then FoundAt = aCell.Address
Do
Set aCell = oRange.FindNext(After:=aCell)
If Not aCell Is Nothing Then
If aCell.Address = bCell.Address Then Exit Do
'~~> Check if the cell has named range
If isNamedRangePresent(aCell, strSearch) Then FoundAt = FoundAt & ", " & aCell.Address
Else
Exit Do
End If
Loop
Else
MsgBox SearchString & " not Found"
Exit Sub
End If
If FoundAt = "" Then
MsgBox "The Named Range was not found"
Else
MsgBox "The Named Range has been found these locations: " & FoundAt
End If
End If
End Sub
Function isNamedRangePresent(rng As Range, s As String) As Boolean
Dim sFormula As String
Dim pos1 As Long, pos2 As Long, sLen As Long, i As Long
sFormula = rng.Formula: sLen = Len(sFormula)
pos2 = 1
Do
pos1 = InStr(pos2, sFormula, s) - 1
If pos1 < 1 Then Exit Do
isNamedRangePresent = True
For i = 65 To 90
'~~> A-Z before Sid for example XSid
If UCase(Mid(sFormula, pos1, 1)) = Chr(i) Then
isNamedRangePresent = False
Exit For
End If
Next i
'~~> Check for " for example "Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos1, 1)) = Chr(34) Then isNamedRangePresent = False
'~~> Check for underscore for example _Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos1, 1)) = Chr(95) Then isNamedRangePresent = False
pos2 = pos1 + Len(s) + 1
If pos2 <= sLen Then
For i = 65 To 90
'~~> A-Z after Sid for example SidX
If UCase(Mid(sFormula, pos2, 1)) = Chr(i) Then
isNamedRangePresent = False
Exit For
End If
Next i
'~~> "Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos2, 1)) = Chr(34) Then isNamedRangePresent = False
'~~> _Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos2, 1)) = Chr(95) Then isNamedRangePresent = False
End If
Loop
End Function
Output
PHEW!!!
This code creates a copy of the workbook with the names. It then goes through and deletes each name in your list of names from the that copied workbook. It counts up the number of formula errors in the workbook before and after. If the error count is the same, the name wasn't used. If it's different, the name was used.
I like to do this kind of test for really complicated situations like this. It means you don't have to worry so much about complicated rules for testing. You can just base your answer on the results.
Since the testing is all done on a copy, it should be safe. Be sure to save all your work before though!
To use, put put your list of names in a workbook and name the range with that list "NamesToTest":
Then put this code in the same workbook and run it:
Sub CheckNameUsage()
Dim WorkbookWithList As Excel.Workbook
Dim WorkbookWithNames As Excel.Workbook
Dim TempWb As Excel.Workbook
Dim cell As Excel.Range
Dim NameToCheck As String
Dim ws As Excel.Worksheet
Dim ErrorRange As Excel.Range
Dim ErrorsBefore As Long
Dim ErrorsAfter As Long
Dim NameUsed As Boolean
Set WorkbookWithList = ThisWorkbook
Set WorkbookWithNames = Workbooks("SO - wb to test.xlsx") 'adjust to suit
WorkbookWithNames.Worksheets.Copy 'Workbooks.Add(WorkbookWithNames.FullName)
Set TempWb = ActiveWorkbook
For Each cell In WorkbookWithList.Names("NamesToTest").RefersToRange.Cells
NameToCheck = cell.Value
ErrorsBefore = 0
For Each ws In TempWb.Worksheets
Set ErrorRange = Nothing
On Error Resume Next
Set ErrorRange = ws.Cells.SpecialCells(xlCellTypeFormulas, 16)
On Error GoTo 0
If Not ErrorRange Is Nothing Then
ErrorsBefore = ErrorsBefore + ErrorRange.Cells.Count
End If
Next ws
TempWb.Names(NameToCheck).Delete
ErrorsAfter = 0
For Each ws In TempWb.Worksheets
Set ErrorRange = Nothing
On Error Resume Next
Set ErrorRange = ws.Cells.SpecialCells(xlCellTypeFormulas, 16)
On Error GoTo 0
If Not ErrorRange Is Nothing Then
ErrorsAfter = ErrorsAfter + ErrorRange.Cells.Count
End If
Next ws
NameUsed = True
If ErrorsBefore = ErrorsAfter Then
NameUsed = False
End If
Debug.Print NameToCheck; " - Errors Before = " & ErrorsBefore; ", Errors After = " & ErrorsAfter; ", Used = " & NameUsed; ""
Next cell
TempWb.Close False
End Sub
The results will show in the Debug window:
The code is hopefully fairly self-explanatory. SpecialCells is worth knowing about, so read up on it if necessary. In this case it identifies cells with errors - that's the 16 argument.
Note that this only checks for workbook-level names. You could add checks for worksheet-level if necessary.
The following code works for me. The interesting points are
1) You can use the method range.ShowDependents to draw arrows to cells that are dependent on that range. When you are done, use range.ShowDependents True to remove the arrows.
2) Once the arrows are drawn, range.NavigateArrow can follow those arrows, and return the resulting range. I was unable to find any documentation on what happens if there are no dependent ranges. By experimenting, I was able to determine, that it will return the original range if there are no dependents.
Sub test_for_dependents(nm As Name)
Dim nm_rng As Range, result As Range
Dim i As Long
Set nm_rng = nm.RefersToRange
nm_rng.ShowDependents
Set result = nm_rng.NavigateArrow(False, 1, 1)
If result.Parent.Name = nm_rng.Parent.Name And result.Row = nm_rng.Row _
And result.Column = nm_rng.Column Then
MsgBox "Named range """ & nm.Name & """ isn't used!"
End If
nm_rng.ShowDependents True
Set nm_rng = Nothing
Set result = Nothing
End Sub
Sub test_all_names()
Dim nm As Name
Dim sht As Worksheet
For Each nm In ThisWorkbook.Names
test_for_dependents nm
Next nm
For Each sht In ThisWorkbook.Sheets
For Each nm In sht.Names
test_for_dependents nm
Next nm
Next sht
Set nm = Nothing
Set sht = Nothing
End Sub
The following NamesInCells macro reports the number of formula cells referencing each defined name (named range) in the active workbook. Results are in columns A:D (Scope, Name, RefersTo, Cells) starting at row 1 of the workbook's NamesInCells worksheet. If that worksheet does not exist, it will be added after the last sheet.
For each Name that is Visible (not hidden), the macro uses Private Function Formula_Errors to determine how many formula cells have errors before and after the Name's RefersTo property is made invalid. The before and after difference is the number of cells referencing that Name in a formula. However, if a Name is used in a cell formula that produced an error before, the after result will be the same for that cell. This issue is resolved by Private Function Prior_Errors which determines if the Name appears in an error cell's formula before the Name was made invalid. The InStr method used by Prior_Errors is imperfect, but only for formulas that had errors before initiating the macro (hopefully few). Also, a Name with workbook scope and a duplicate Name with sheet scope might be extraneously counted if they are in separate formulas that had initial errors.
This macro was inspired by Doug Glancy's answer above: https://stackoverflow.com/a/26691025/10172433
Public Sub NamesInCells()
Const myName As String = "NamesInCells"
Dim WB As Workbook, oName As Name, A() As Variant, vCells As Variant
Dim sScope As String, sName As String, sRefersTo As String
Dim nRows As Long, nR As Long, nBase As Long, n As Integer
Set WB = ActiveWorkbook
nRows = WB.Names.Count
If nRows = 0 Then
MsgBox "There are no defined names in the active workbook", _
vbInformation, myName
Exit Sub
End If
nRows = nRows + 1
ReDim A(1 To 4, 1 To nRows)
nR = 1
A(1, 1) = "Scope"
A(2, 1) = "Name"
A(3, 1) = "RefersTo"
A(4, 1) = "Cells"
nBase = Formula_Errors(WB)
For Each oName In WB.Names
With oName
If .Visible Then 'skip hidden names
n = InStrRev(.Name, "!")
If n = 0 Then
sScope = "Workbook"
sName = .Name
ElseIf n > 1 Then
sScope = Left(.Name, (n - 1))
sName = Mid(.Name, (n + 1))
End If
sRefersTo = .RefersTo
If Left(sScope, 1) = "'" Then _
sScope = Mid(sScope, 2, (Len(sScope) - 2))
.RefersTo = "#REF!"
vCells = Formula_Errors(WB) - nBase
.RefersTo = sRefersTo
vCells = vCells + Prior_Errors(WB, .Name)
nR = nR + 1
A(1, nR) = sScope
A(2, nR) = sName
A(3, nR) = "'" & sRefersTo
A(4, nR) = vCells
End If
End With
Next oName
If nR < 2 Then
MsgBox "There are no visible defined names in the active workbook", _
vbInformation, myName
Exit Sub
ElseIf nR < nRows Then
ReDim Preserve A(1 To 4, 1 To nR)
End If
On Error Resume Next
With WB
.Worksheets(myName).Activate
If Err = 0 Then
Range("A:D").Clear
Else
.Worksheets.Add After:=.Sheets(.Sheets.Count)
ActiveSheet.Name = myName
End If
End With
On Error GoTo 0
Range("A1").Select
Selection.Resize(nR, 4).Value = Application.Transpose(A)
End Sub
Private Function Formula_Errors(WB As Workbook) As Long
Dim WS As Worksheet, R As Range, nCount As Long
For Each WS In WB.Worksheets
On Error Resume Next
Set R = WS.Cells.SpecialCells(xlCellTypeFormulas, xlErrors)
If Err = 0 Then nCount = nCount + R.Count
On Error GoTo 0
Next WS
Formula_Errors = nCount
End Function
Private Function Prior_Errors(WB As Workbook, Name As String) As Long
Dim WS As Worksheet, R As Range, rCell As Range, nCount As Long
Dim sWS As String, sN As String, sF As String, n As Integer
n = InStrRev(Name, "!")
If n > 1 Then
sN = Mid(Name, (n + 1))
sWS = Left(Name, (n - 1))
If Left(sWS, 1) = "'" Then sWS = Mid(sWS, 2, (Len(sWS) - 2))
End If
For Each WS In WB.Worksheets
On Error Resume Next
Set R = WS.Cells.SpecialCells(xlCellTypeFormulas, xlErrors)
If Err = 0 Then
For Each rCell In R
sF = rCell.Formula
If WS.Name = sWS Then
If InStr(1, sF, sN, vbBinaryCompare) > 0 Then
nCount = nCount + 1
End If
ElseIf InStr(1, sF, Name, vbBinaryCompare) > 0 Then
nCount = nCount + 1
End If
Next rCell
End If
On Error GoTo 0
Next WS
Prior_Errors = nCount
End Function

Find where named ranges are being used in big workbook

I have a list of 594 named ranges in a workbook with nearly 20 sheets, each sheet has about 200 columns of data. I need to find out where the named ranges are being used so as to remove irrelevant ones. I pasted a list of named ranges onto the sheet and then I tried to find if they were used in a formula by recording them, and then using the find method in all sheets and columns. The problem is despite using lookin xlformulas, it retrieves the named range even if it is just a text.
Here is my (updated) attempt (if it is not evident already, i am an amateur):
Application.ScreenUpdating = False
Count = ActiveWorkbook.Sheets.Count
Sheets(Count).Activate
Dim locr(1 To 595)
Dim locc(1 To 595)
Dim locn(1 To 595)
Dim nam(1 To 595)
Dim rng As Range
Range("a1").Select
For X = 1 To 595 'populate array with named ranges
ActiveCell.Offset(1, 0).Select
nam(X) = ActiveCell.Value
Next X
For i = 1 To 595 'name loop
For j = 1 To (Count - 1) 'sheet loop
Sheets(j).Activate
On Error Resume Next
Set orange = Sheets(j).Cells.SpecialCells(xlCellTypeFormulas) 'limit range to cells that only contain formulas
On Error GoTo 20 'if no formulas in sheet, go to next sheet
If Not orange Is Nothing Then
Set rng = orange.Find(What:=nam(i), _
LookIn:=xlFormulas, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False) 'find named range
If Not rng Is Nothing Then 'if named range found
Application.Goto rng, True 'go to cell where name range found and record address
locr(i) = ActiveCell.Row
locc(i) = ActiveCell.Column
locn(i) = ActiveSheet.Name
GoTo 10 'value found, go to next sheet
Else
End If
Else
End If
20 Next j
locr(i) = "" 'record empty since "rng" is empty
locr(i) = ""
locr(i) = ""
10 Next i
Sheets(Count).Activate
Range("c1").Select
b = 1
For a = 1 To 595 'populate addresses of named ranges
ActiveCell.Offset(b, 2).Value = locr(a)
ActiveCell.Offset(b, 1).Value = locc(a)
ActiveCell.Offset(b, 0).Value = locn(a)
b = b + 1
Next a
Here is one way I can think of. I will explain this in 2 parts.
PART 1
Let's say we have a named range Sid.
This word Sid can appear in any one of these forms as shown in the image below. Why does it start with =? That has been explained in Part2 below.
=Sid '<~~ 1
="Sid" '<~~ 2
=XSid '<~~ 3
=SidX '<~~ 4
=_Sid '<~~ 5
=Sid_ '<~~ 6
=(Sid) '<~~ 7
Any other scenarios, I guess will be a subset of the above. Now out of these the only valid find in our case is the first one and the last one since we are looking for our named range.
So here is a quick function to check if the cell formula has a named range or not. I am sure it can be made more efficient
Function isNamedRangePresent(rng As Range, s As String) As Boolean
Dim sFormula As String
Dim pos1 As Long, pos2 As Long, sLen As Long, i As Long
sFormula = rng.Formula: sLen = Len(sFormula)
pos2 = 1
Do
pos1 = InStr(pos2, sFormula, s) - 1
If pos1 < 1 Then Exit Do
isNamedRangePresent = True
For i = 65 To 90
'~~> A-Z before Sid for example XSid
If UCase(Mid(sFormula, pos1, 1)) = Chr(i) Then
isNamedRangePresent = False
Exit For
End If
Next i
'~~> Check for " for example "Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos1, 1)) = Chr(34) Then isNamedRangePresent = False
'~~> Check for underscore for example _Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos1, 1)) = Chr(95) Then isNamedRangePresent = False
pos2 = pos1 + Len(s) + 1
If pos2 <= sLen Then
For i = 65 To 90
'~~> A-Z after Sid for example SidX
If UCase(Mid(sFormula, pos2, 1)) = Chr(i) Then
isNamedRangePresent = False
Exit For
End If
Next i
'~~> "Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos2, 1)) = Chr(34) Then isNamedRangePresent = False
'~~> _Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos2, 1)) = Chr(95) Then isNamedRangePresent = False
End If
Loop
End Function
So in the first and the last case, Debug.Print isNamedRangePresent(Range("D2"), "Sid") will give you True See this
PART 2
Now coming to the .Find. I see that you are searching only once in the worksheet. Since you can have many scenarios of the word Sid being present, you cannot just have one .Find. You will have to use .FindNext. See THIS link on how to use that. I have explained it there so I won't bother explaining that here.
We can make our .Find more efficient by searching only those cells which has formulas. To do that we have to use .SpecialCells(xlCellTypeFormulas). This explains why we had "=" in our example in PART1. :)
Here is an example (PART1 Code added at the bottom)
Sub Sample()
Dim oRange As Range, aCell As Range, bCell As Range
Dim oSht As Worksheet
Dim strSearch As String, FoundAt As String
Set oSht = Worksheets("Sheet1")
'~~> Set your range where you need to find - Only Formula Cells
On Error Resume Next
Set oRange = oSht.Cells.SpecialCells(xlCellTypeFormulas)
On Error GoTo 0
If Not oRange Is Nothing Then
strSearch = "Sid"
Set aCell = oRange.Find(What:=strSearch, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not aCell Is Nothing Then
Set bCell = aCell
'~~> Check if the cell has named range
If isNamedRangePresent(aCell, strSearch) Then FoundAt = aCell.Address
Do
Set aCell = oRange.FindNext(After:=aCell)
If Not aCell Is Nothing Then
If aCell.Address = bCell.Address Then Exit Do
'~~> Check if the cell has named range
If isNamedRangePresent(aCell, strSearch) Then FoundAt = FoundAt & ", " & aCell.Address
Else
Exit Do
End If
Loop
Else
MsgBox SearchString & " not Found"
Exit Sub
End If
If FoundAt = "" Then
MsgBox "The Named Range was not found"
Else
MsgBox "The Named Range has been found these locations: " & FoundAt
End If
End If
End Sub
Function isNamedRangePresent(rng As Range, s As String) As Boolean
Dim sFormula As String
Dim pos1 As Long, pos2 As Long, sLen As Long, i As Long
sFormula = rng.Formula: sLen = Len(sFormula)
pos2 = 1
Do
pos1 = InStr(pos2, sFormula, s) - 1
If pos1 < 1 Then Exit Do
isNamedRangePresent = True
For i = 65 To 90
'~~> A-Z before Sid for example XSid
If UCase(Mid(sFormula, pos1, 1)) = Chr(i) Then
isNamedRangePresent = False
Exit For
End If
Next i
'~~> Check for " for example "Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos1, 1)) = Chr(34) Then isNamedRangePresent = False
'~~> Check for underscore for example _Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos1, 1)) = Chr(95) Then isNamedRangePresent = False
pos2 = pos1 + Len(s) + 1
If pos2 <= sLen Then
For i = 65 To 90
'~~> A-Z after Sid for example SidX
If UCase(Mid(sFormula, pos2, 1)) = Chr(i) Then
isNamedRangePresent = False
Exit For
End If
Next i
'~~> "Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos2, 1)) = Chr(34) Then isNamedRangePresent = False
'~~> _Sid
If isNamedRangePresent = True Then _
If UCase(Mid(sFormula, pos2, 1)) = Chr(95) Then isNamedRangePresent = False
End If
Loop
End Function
Output
PHEW!!!
This code creates a copy of the workbook with the names. It then goes through and deletes each name in your list of names from the that copied workbook. It counts up the number of formula errors in the workbook before and after. If the error count is the same, the name wasn't used. If it's different, the name was used.
I like to do this kind of test for really complicated situations like this. It means you don't have to worry so much about complicated rules for testing. You can just base your answer on the results.
Since the testing is all done on a copy, it should be safe. Be sure to save all your work before though!
To use, put put your list of names in a workbook and name the range with that list "NamesToTest":
Then put this code in the same workbook and run it:
Sub CheckNameUsage()
Dim WorkbookWithList As Excel.Workbook
Dim WorkbookWithNames As Excel.Workbook
Dim TempWb As Excel.Workbook
Dim cell As Excel.Range
Dim NameToCheck As String
Dim ws As Excel.Worksheet
Dim ErrorRange As Excel.Range
Dim ErrorsBefore As Long
Dim ErrorsAfter As Long
Dim NameUsed As Boolean
Set WorkbookWithList = ThisWorkbook
Set WorkbookWithNames = Workbooks("SO - wb to test.xlsx") 'adjust to suit
WorkbookWithNames.Worksheets.Copy 'Workbooks.Add(WorkbookWithNames.FullName)
Set TempWb = ActiveWorkbook
For Each cell In WorkbookWithList.Names("NamesToTest").RefersToRange.Cells
NameToCheck = cell.Value
ErrorsBefore = 0
For Each ws In TempWb.Worksheets
Set ErrorRange = Nothing
On Error Resume Next
Set ErrorRange = ws.Cells.SpecialCells(xlCellTypeFormulas, 16)
On Error GoTo 0
If Not ErrorRange Is Nothing Then
ErrorsBefore = ErrorsBefore + ErrorRange.Cells.Count
End If
Next ws
TempWb.Names(NameToCheck).Delete
ErrorsAfter = 0
For Each ws In TempWb.Worksheets
Set ErrorRange = Nothing
On Error Resume Next
Set ErrorRange = ws.Cells.SpecialCells(xlCellTypeFormulas, 16)
On Error GoTo 0
If Not ErrorRange Is Nothing Then
ErrorsAfter = ErrorsAfter + ErrorRange.Cells.Count
End If
Next ws
NameUsed = True
If ErrorsBefore = ErrorsAfter Then
NameUsed = False
End If
Debug.Print NameToCheck; " - Errors Before = " & ErrorsBefore; ", Errors After = " & ErrorsAfter; ", Used = " & NameUsed; ""
Next cell
TempWb.Close False
End Sub
The results will show in the Debug window:
The code is hopefully fairly self-explanatory. SpecialCells is worth knowing about, so read up on it if necessary. In this case it identifies cells with errors - that's the 16 argument.
Note that this only checks for workbook-level names. You could add checks for worksheet-level if necessary.
The following code works for me. The interesting points are
1) You can use the method range.ShowDependents to draw arrows to cells that are dependent on that range. When you are done, use range.ShowDependents True to remove the arrows.
2) Once the arrows are drawn, range.NavigateArrow can follow those arrows, and return the resulting range. I was unable to find any documentation on what happens if there are no dependent ranges. By experimenting, I was able to determine, that it will return the original range if there are no dependents.
Sub test_for_dependents(nm As Name)
Dim nm_rng As Range, result As Range
Dim i As Long
Set nm_rng = nm.RefersToRange
nm_rng.ShowDependents
Set result = nm_rng.NavigateArrow(False, 1, 1)
If result.Parent.Name = nm_rng.Parent.Name And result.Row = nm_rng.Row _
And result.Column = nm_rng.Column Then
MsgBox "Named range """ & nm.Name & """ isn't used!"
End If
nm_rng.ShowDependents True
Set nm_rng = Nothing
Set result = Nothing
End Sub
Sub test_all_names()
Dim nm As Name
Dim sht As Worksheet
For Each nm In ThisWorkbook.Names
test_for_dependents nm
Next nm
For Each sht In ThisWorkbook.Sheets
For Each nm In sht.Names
test_for_dependents nm
Next nm
Next sht
Set nm = Nothing
Set sht = Nothing
End Sub
The following NamesInCells macro reports the number of formula cells referencing each defined name (named range) in the active workbook. Results are in columns A:D (Scope, Name, RefersTo, Cells) starting at row 1 of the workbook's NamesInCells worksheet. If that worksheet does not exist, it will be added after the last sheet.
For each Name that is Visible (not hidden), the macro uses Private Function Formula_Errors to determine how many formula cells have errors before and after the Name's RefersTo property is made invalid. The before and after difference is the number of cells referencing that Name in a formula. However, if a Name is used in a cell formula that produced an error before, the after result will be the same for that cell. This issue is resolved by Private Function Prior_Errors which determines if the Name appears in an error cell's formula before the Name was made invalid. The InStr method used by Prior_Errors is imperfect, but only for formulas that had errors before initiating the macro (hopefully few). Also, a Name with workbook scope and a duplicate Name with sheet scope might be extraneously counted if they are in separate formulas that had initial errors.
This macro was inspired by Doug Glancy's answer above: https://stackoverflow.com/a/26691025/10172433
Public Sub NamesInCells()
Const myName As String = "NamesInCells"
Dim WB As Workbook, oName As Name, A() As Variant, vCells As Variant
Dim sScope As String, sName As String, sRefersTo As String
Dim nRows As Long, nR As Long, nBase As Long, n As Integer
Set WB = ActiveWorkbook
nRows = WB.Names.Count
If nRows = 0 Then
MsgBox "There are no defined names in the active workbook", _
vbInformation, myName
Exit Sub
End If
nRows = nRows + 1
ReDim A(1 To 4, 1 To nRows)
nR = 1
A(1, 1) = "Scope"
A(2, 1) = "Name"
A(3, 1) = "RefersTo"
A(4, 1) = "Cells"
nBase = Formula_Errors(WB)
For Each oName In WB.Names
With oName
If .Visible Then 'skip hidden names
n = InStrRev(.Name, "!")
If n = 0 Then
sScope = "Workbook"
sName = .Name
ElseIf n > 1 Then
sScope = Left(.Name, (n - 1))
sName = Mid(.Name, (n + 1))
End If
sRefersTo = .RefersTo
If Left(sScope, 1) = "'" Then _
sScope = Mid(sScope, 2, (Len(sScope) - 2))
.RefersTo = "#REF!"
vCells = Formula_Errors(WB) - nBase
.RefersTo = sRefersTo
vCells = vCells + Prior_Errors(WB, .Name)
nR = nR + 1
A(1, nR) = sScope
A(2, nR) = sName
A(3, nR) = "'" & sRefersTo
A(4, nR) = vCells
End If
End With
Next oName
If nR < 2 Then
MsgBox "There are no visible defined names in the active workbook", _
vbInformation, myName
Exit Sub
ElseIf nR < nRows Then
ReDim Preserve A(1 To 4, 1 To nR)
End If
On Error Resume Next
With WB
.Worksheets(myName).Activate
If Err = 0 Then
Range("A:D").Clear
Else
.Worksheets.Add After:=.Sheets(.Sheets.Count)
ActiveSheet.Name = myName
End If
End With
On Error GoTo 0
Range("A1").Select
Selection.Resize(nR, 4).Value = Application.Transpose(A)
End Sub
Private Function Formula_Errors(WB As Workbook) As Long
Dim WS As Worksheet, R As Range, nCount As Long
For Each WS In WB.Worksheets
On Error Resume Next
Set R = WS.Cells.SpecialCells(xlCellTypeFormulas, xlErrors)
If Err = 0 Then nCount = nCount + R.Count
On Error GoTo 0
Next WS
Formula_Errors = nCount
End Function
Private Function Prior_Errors(WB As Workbook, Name As String) As Long
Dim WS As Worksheet, R As Range, rCell As Range, nCount As Long
Dim sWS As String, sN As String, sF As String, n As Integer
n = InStrRev(Name, "!")
If n > 1 Then
sN = Mid(Name, (n + 1))
sWS = Left(Name, (n - 1))
If Left(sWS, 1) = "'" Then sWS = Mid(sWS, 2, (Len(sWS) - 2))
End If
For Each WS In WB.Worksheets
On Error Resume Next
Set R = WS.Cells.SpecialCells(xlCellTypeFormulas, xlErrors)
If Err = 0 Then
For Each rCell In R
sF = rCell.Formula
If WS.Name = sWS Then
If InStr(1, sF, sN, vbBinaryCompare) > 0 Then
nCount = nCount + 1
End If
ElseIf InStr(1, sF, Name, vbBinaryCompare) > 0 Then
nCount = nCount + 1
End If
Next rCell
End If
On Error GoTo 0
Next WS
Prior_Errors = nCount
End Function

Resources