List Multiple Global Variables Into One Cell That Change In For Loop - excel

I have two global variables ErrorMsg and SectionName. What I want my macro to do is run through the code and if ErrorMsg is assigned a value, I want it to list the SectionName and then the ErrorMsg that was generated within that section. There are cases where ErrorMsg could appear in multiple different SectionNames thats why I want it to be labeled which Section generated the ErrorMsg.
There will be cases where there are more than two values for ErrorMsg so I need the macro to recognize all the values of ErrorMsg and SectionName list them.
So if errors are generated in lines wavelength_col = GetColumnIndex(ws, "Wavelength (nm)") and power_value = Getdata(ws, sysrow, power_col)
Then the output in With logsht should look like this with each new Section font bolded.
Complete with Error - Section: Wavelength - Wavelength column index could not be found, Section: Power - data could not be found
Here are the functions I mentioned above.
Global ErrorMsg As String, SectionName As String
Sub Main
Dim cell As Range, ws As Worksheet, sysnum As String, sysrow As Integer, wb As Workbook, logsht As Worksheet
Set wb = ActiveWorkbook
Set ws = ActiveWorksheet
Set logsht = wb.Worksheets("Log Sheet")
For Each cell In ws.Range("E2", ws.cells(ws.Rows.Count, "E").End(xlUp)).cells
sysnum = cell.Value
sysrow = cell.row
power_col = GetColumnIndex(ws, "Power (mW)")
power_value = GetJiraData(ws, sysrow, power_col)
Dim begincell As Long
With logsht
begincell = .cells(Rows.Count, 1).End(xlUp).row
.cells(begincell + 1, 2).Value = sysnum
.cells(begincell + 1, 2).Font.Bold = True
If Not ErrorMsg = "" Then
.cells(begincell + 1, 3).Value = "Complete with Erorr - " & ErrorMsg
.cells(begincell + 1, 3).Font.Bold = True
.cells(begincell + 1, 3).Interior.Color = vbRed
Else
.cells(begincell + 1, 3).Value = "Completed without Errors"
.cells(begincell + 1, 3).Font.Bold = True
.cells(begincell + 1, 3).Interior.Color = vbGreen
End If
End With
Next cell
End Sub
Sub Wavelength()
Dim wavelength_col As Long, wavelength_value As Double
SectionName = "Wavelength"
On Error GoTo errormessage
wavelength_col = GetColumnIndex(ws, "Wavelength (nm)")
wavelength_value = Getdata(ws, sysrow, wavelength_col)
Exit Sub
errormessage:
ErrorMsg = ErrorMsg
End Sub
Sub Power()
Dim power_col As Long, power_value As Double
SectionName = "Power"
On Error GoTo errormessage
power_col = GetColumnIndex(ws, "Average Power (mW)")
power_value = Getdata(ws, sysrow, power_col)
Exit Sub
errormessage:
ErrorMsg = ErrorMsg
End Sub
Function GetColumnIndex(sht As Worksheet, colname As String) As Double
Dim paramname As Range
Set paramname = sht.Range("A1", sht.cells(2, sht.Columns.Count).End(xlToLeft)).cells.Find(What:=colname, LookAt:=xlWhole, LookIn:=xlFormulas, searchorder:=xlByColumns, searchdirection:=xlPrevious, MatchCase:=True)
If Not paramname Is Nothing Then
GetColumnIndex = paramname.column
ElseIf paramname Is Nothing Then '
ErrorMsg = ErrorMsg & IIf(ErrorMsg = "", "", ", ") & colname & " column index could not be found"
End If
End Function
Function Getdata(sht As Worksheet, WDrow As Long, parametercol As Long) As Variant
Getdata = sht.cells(WDrow, parametercol)
If Getdata = -999 Then
ElseIf Getdata = Empty Then
ErrorMsg = ErrorMsg & IIf(ErrorMsg = "", "", ", ") & "data could not be found"
End If
End Function

first of all, there are several things wrong in your code.
Just to name a few;
your Power() sub uses ws as a WorkSheet object yet they are not declared as Global under the Main method nor are they used as parameters for the sub and hence will not be available? Same applies for Wavelength.
Power() and Wavelenght() both produce a variable yet you do not seem to do anything with those values?
But alas, for the solution;
What you can do is add a ClassModule to your project and give it below fields and name it 'ErrorState'
Option Explicit
Public ErrMsg As String
Public ErrNumber As Long
Public SectionName As String 'suggest to use 'MethodName' but your pick
Then in your CodeModule declare a new Collection as a a Global collection
Global Errors As New Collection
Then add a Method (a Sub if you wish) that adds the error to the collection.
Private Sub AddError(message As String, number As Long, method As String)
Dim error As New ErrorState
error.ErrMsg = message
error.SectionName = method
error.ErrNumber = number
Errors.Add error
End Sub
Add the above correctly to your ErrorHandling as per below example
Sub Power(sht As Worksheet, sysrow As Long)
On Error GoTo ErrorExit
Err.Raise (13) 'you can remove this, this is just to trigger an Error
Dim power_col As Long, power_value As Double
power_col = GetColumnIndex(sht, "Average Power (mW)")
power_value = Getdata(sht, sysrow, power_col)
Exit Sub
ErrorExit:
AddError Err.Description, Err.number, "Power"
End Sub
So your complete code would look like the below (I have simplified the Main method, but I'm sure you get the picture)
Global Errors As New Collection
Private Sub AddError(message As String, number As Long, method As String)
Dim error As New ErrorState
error.ErrMsg = message
error.SectionName = method
error.ErrNumber = number
Errors.Add error
End Sub
Sub Main()
Set Errors = New Collection
Dim wb As Workbook
Dim ws As Worksheet
Set wb = ThisWorkbook
Set ws = ActiveSheet
Wavelength ws, 1
Power ws, 1
Index = GetColumnIndex(ws, "SomeColum")
Data = Getdata(ws, 1, 1)
For Each e In Errors
Debug.Print e.SectionName, e.ErrMsg, e.ErrNumber
Next
End Sub
Sub Wavelength(sht As Worksheet, sysrow As Long)
On Error GoTo ErrorExit
Dim wavelength_col As Long, wavelength_value As Double
wavelength_col = GetColumnIndex(sht, "Wavelength (nm)")
wavelength_value = Getdata(sht, sysrow, wavelength_col) 'do you need to do anything with this value?
Exit Sub
ErrorExit:
AddError Err.Description, Err.number, "Wavelength"
End Sub
Sub Power(sht As Worksheet, sysrow As Long)
On Error GoTo ErrorExit
Dim power_col As Long, power_value As Double
power_col = GetColumnIndex(sht, "Average Power (mW)")
power_value = Getdata(sht, sysrow, power_col) 'do you need to do anything with this value?
Exit Sub
ErrorExit:
AddError Err.Description, Err.number, "Power"
End Sub
Function GetColumnIndex(sht As Worksheet, colname As String) As Double
Dim paramname As Range
Set paramname = sht.Range("A1", sht.Cells(2, sht.Columns.Count).End(xlToLeft)).Cells.Find(What:=colname, LookAt:=xlWhole, LookIn:=xlFormulas, searchorder:=xlByColumns, searchdirection:=xlPrevious, MatchCase:=True)
If Not paramname Is Nothing Then
GetColumnIndex = paramname.Column
Else
AddError Trim(colname & " column index could not be found"), 0, "GetColumnIndex"
End If
End Function
Function Getdata(sht As Worksheet, WDrow As Long, parametercol As Long) As Variant
Getdata = sht.Cells(WDrow, parametercol)
If Getdata = -999 Then
'do something
ElseIf IsEmpty(Getdata) Then
AddError "data could not be found", 0, "Getdata"
End If
End Function

Related

Once variable is assigned a value do not let it be overwritten

I have a global variable ErrorMsg that can be set based on multiple different functions. The problem I am running into is if a certain condition is met, then the macro will assign ErrorMsg a text string that will be displayed in the worksheet Log Sheet. The way I have my code set up, even if an error occurs and ErrorMsg has a set text string assigned to it, the macro will continue running all the way through all the data until it is at the end. The issue with this that a lot of the conditions stem off each other. So if the function GetColumnIndex throws an error, assigns ErrorMsg a text string and continues on, then the next function like GetData will also throw an error and overwrite the value of ErrorMsg to be whatever it is set within its function. This occurs because the value that comes from GetColumnIndex is an input in GetData function. So my question is, how do I tell the macro to recognize once a value has been set to ErrorMsg to continue running through the code, but to not overwrite the variable with an updated text string. As you can see in the script, each function has a line that defines what ErrorMsg is. I am looking to find a way for if GetColumnIndex set ErrorMsg to be the text string to not have GetData overwrite ErrorMsg to a different text string.
Here are the functions I mentioned above.
Global ErrorMsg As String
Sub Main
Dim cell As Range, ws As Worksheet, sysnum As String, sysrow As Integer, wb As Workbook, logsht As Worksheet
Dim power_col As Long, power_value As Double
Set wb = ActiveWorkbook
Set ws = ActiveWorksheet
Set logsht = wb.Worksheets("Log Sheet")
For Each cell In ws.Range("E2", ws.cells(ws.Rows.Count, "E").End(xlUp)).cells
sysnum = cell.Value
sysrow = cell.row
power_col = GetColumnIndex(ws, "Power (mW)")
power_value = GetJiraData(ws, sysrow, power_col)
Dim begincell As Long
With logsht
begincell = .cells(Rows.Count, 1).End(xlUp).row
.cells(begincell + 1, 2).Value = sysnum
.cells(begincell + 1, 2).Font.Bold = True
If Not ErrorMsg = "" Then
.cells(begincell + 1, 3).Value = "Complete with Erorr - " & ErrorMsg
.cells(begincell + 1, 3).Font.Bold = True
.cells(begincell + 1, 3).Interior.Color = vbRed
Else
.cells(begincell + 1, 3).Value = "Completed without Errors"
.cells(begincell + 1, 3).Font.Bold = True
.cells(begincell + 1, 3).Interior.Color = vbGreen
End If
End With
Next cell
End Sub
Function GetColumnIndex(sht As Worksheet, colname As String) As Double
Dim paramname As Range
Set paramname = sht.Range("A1", sht.cells(2, sht.Columns.Count).End(xlToLeft)).cells.Find(What:=colname, Lookat:=xlWhole, LookIn:=xlFormulas, searchorder:=xlByColumns, searchdirection:=xlPrevious, MatchCase:=True)
If Not paramname Is Nothing Then
GetColumnIndex = paramname.Column
ElseIf paramname Is Nothing Then
ErrorMsg = colname & " column index could not be found. Check before running again."
End If
End Function
Function GetData(sht As Worksheet, WDrow As Integer, parametercol As Long)
GetData = sht.cells(WDrow, parametercol)
If GetData = -999 Then
ElseIf GetData < 0 Then
ErrorMsg = "Data cannot be a negative number. Check before running again."
End If
End Function
You can use
If lenb(ErrorMsg) > 0 then ErrorMsg = ErrorMsg & vbCrLf`
ErrorMsg = ErrorMsg & "your text"
And when you have written the error message to the sheet, clear ErrorMsg:`
ErrorMsg = vbNullString

Error handling for Match inside a class - Excel VBA

I have a VBA class that I call to fetch column numbers for the required columns in a worksheet (15 of them). Users are allowed to move columns around and the match functionality works well. However if a user deletes a column, I get a runtime error. How do I trap an error and let the user know that 'X' column name has been deleted but still continue checking the rest of the columns.
Option Explicit
Public EmpName As Long, EmpID As Long, EmpDepartment As Long, EmpAddress As Long
Private Sub Class_Initialize()
Dim ws As Worksheet, r As Range, tStr As String, wsname As String
Set ws = ActiveSheet: Set r = ws.Range("1:1")
EmpName = Application.WorksheetFunction.Match("EmpName", r.value, 0)
EmpID = Application.WorksheetFunction.Match("EmpID", r.value, 0)
EmpDepartment = Application.WorksheetFunction.Match("EmpDepartment", r.value, 0)
EmpAddress = Application.WorksheetFunction.Match("EmpAddress", r.value, 0)
Set r = Nothing: Set ws = Nothing
End Sub
Original code updated
To avoid the run-time error you could use Application.Match instead of Application.WorksheetFunction.Match.
Option Explicit
Public EmpName As Long, EmpID As Long, EmpDepartment As Long, EmpAddress As Long
Private Sub Class_Initialize()
Dim ws As Worksheet, r As Range, tStr As String, wsname As String
Dim Res As Variant
Set ws = ActiveSheet
Set r = ws.Range("1:1")
Res = Application.Match("EmpName", r.Value, 0)
If Not IsError(Res) Then
EmpName = Res
Else
MsgBox "EmpName column not found!", vbInformation, "Missing Column"
End If
Res = Application.Match("EmpID", r.Value, 0)
If Not IsError(Res) Then
EmpID = Res
Else
MsgBox "EmpID column not found!", vbInformation, "Missing Column"
End If
Res = Application.Match("EmpDepartment", r.Value, 0)
If Not IsError(Res) Then
EmpName = Res
Else
MsgBox "EmpDepartment column not found!", vbInformation, "Missing Column"
End If
Res = Application.Match("EmpAddress", r.Value, 0)
If Not IsError(Res) Then
EmpAddress = Res
Else
MsgBox "EmpAddress column not found!", vbInformation, "Missing Column"
End If
End Sub
Using a dictionary
If you don't want all the repetition in the code you might want to look at using a dictionary to store the column names/numbers.
Option Explicit
Private Sub Class_Initialize()
Dim ws As Worksheet, r As Range, tStr As String, wsname As String
Dim dicCols As Object
Dim arrCols As Variant
Dim Res As Variant
Dim idx As Long
arrCols = Array("EmpName", "EmpID", "EmpDepartmen", "EmpAddress")
Set dicCols = CreateObject("Scripting.Dictionary")
Set ws = ActiveSheet
Set r = ws.Range("1:1")
For idx = LBound(arrCols) To UBound(arrCols)
Res = Application.Match(arrCols(idx), r.Value, 0)
If Not IsError(Res) Then
dicCols(arrCols(idx)) = Res
Else
dicCols(arrCols(idx)) = "Not Found"
MsgBox arrCols(idx) & " column not found!", vbInformation, "Missing Column"
End If
Next idx
End Sub
Once this code is executed you can use dicCols(ColumnName) to get the column number.
For example, wherever you refer to the variable EmpName in the rest of the code you can use dicCols("EmpName").
Using a dictionary populated from a function
Another refinement might be to use a function to create the dictionary.
This would allow you to pass different sets of column names when required.
Option Explicit
Public dicCols As Object
Private Sub Class_Initialize()
Dim ws As Worksheet, r As Range, tStr As String, wsname As String
Dim arrColNames As Variant
Dim arrNotFound() As Variant
Dim ky As Variant
Dim cnt As Long
arrColNames = Array("EmpName", "EmpID", "EmpDepartment", "EmpAddress")
Set ws = ActiveSheet
Set r = ws.Range("1:1")
Set dicCols = GetColNos(arrColNames, r)
For Each ky In dicCols.keys
If dicCols(ky) = "Not Found" Then
cnt = cnt + 1
ReDim Preserve arrNotFound(1 To cnt)
arrNotFound(cnt) = ky
End If
Next ky
If cnt > 0 Then
MsgBox "The following columns were not found:" & vbCrLf & vbCrLf & Join(arrNotFound, vbCrLf), vbInformation, "Missing Columna"
End If
End Sub
Function GetColNos(arrColNames, rngHdr As Range) As Object
Dim dic As Object
Dim idx As Long
Dim Res As Variant
Set dic = CreateObject("Scripting.Dictionary")
For idx = LBound(arrColNames) To UBound(arrColNames)
Res = Application.Match(arrColNames(idx), rngHdr.Value, 0)
If Not IsError(Res) Then
dic(arrColNames(idx)) = Res
Else
dic(arrColNames(idx)) = "Not Found"
End If
Next idx
Set GetColNos = dic
End Function

Resolving errors trying to assign values to range variables in Excel VBA code

I am writing a procedure to generate a number of named ranges from values in a worksheet, with the cell to be named in column C and the name to be assigned in the adjacent cell in column D. All variations I've tried to reference these two cells as ranges returned errors. While this procedure will be used only once to generate these names, I'd like to determine the correct syntax so I'll be able to reference ranges for other purposes in the future.
I have a temporary ActiveX CommandButton that calls the following code (just referencing two rows for now for testing purposes):
Private Sub CommandButton1_Click()
Call SetRangeNames(38, 39)
End Sub
I placed the SetRangeNames procedure in a module based on recommendations in posts I've seen elsewhere. The two lines of code returning errors are indicated by a comment within the procedure code, and all of the variations I've tried (with the errors they return) follow that. I placed a MsgBox call in the proc to see how it was rendering the cell references generated. It displays:
rangeNameValueCellAddress = "C38"; namedRangeCellAddress = "D38"
or
rangeNameValueCellAddress = "C39"; namedRangeCellAddress = "D39"
Here's one varation of the proc:
Public Sub SetRangeNames(startRow As Integer, endRow As Integer)
Dim theRange As Range
Dim currentRow As Integer
Dim currentName As String
Dim rangeNameValueCellAddress As String
Dim namedRangeCellAddress As String
For currentRow = startRow To endRow
rangeNameValueCellAddress = """D" & Trim(Str(currentRow) & """")
namedRangeCellAddress = """C" & Trim(Str(currentRow) & """")
MsgBox ("rangeNameValueCellAddress = " & rangeNameValueCellAddress & _
"; namedRangeCellAddress = " & namedRangeCellAddress)
'MsgBox displays: rangeNameValueCellAddress = "C38"; namedRangeCellAddress = "D38"
' or: rangeNameValueCellAddress = "C39"; namedRangeCellAddress = "D39"
'*** The following two statements return errors:
Set theRange = ThisWorkbook.Worksheets(ActiveSheet).Range(namedRangeCellAddress)
currentName = ThisWorkbook.Worksheets(ActiveSheet).Range(rangeNameValueCellAddress).Value2
ActiveWorkbook.Names.Add Name:=currentName, RefersTo:=theRange
Next currentRow
End Sub
Here are the variations I tried ("SYSProjectData" is both the name and CodeName and Name of the worksheet I am working with):
Set theRange = SYSProjectData.Range(namedRangeCellAddress)
currentName = SYSProjectData.Range(rangeNameValueCellAddress).Value2
Returns: "Application-defined or object-defined error"
Set theRange = ThisWorkbook.SYSProjectData.Range(namedRangeCellAddress)
currentName = ThisWorkbook.SYSProjectData.Range(rangeNameValueCellAddress).Value2
Returns: "Object doesn't support this property or method"
Set theRange = ThisWorkbook.ActiveSheet.Range(namedRangeCellAddress)
currentName = ThisWorkbook.ActiveSheet.Range(rangeNameValueCellAddress).Value2
Returns: "Application-defined or object-defined error"
Set theRange = ActiveSheet.Range(namedRangeCellAddress)
currentName = ActiveSheet.Range(rangeNameValueCellAddress).Value2
Returns: "Application-defined or object-defined error"
Set theRange = ThisWorkbook.Worksheets(SYSProjectData).Range(namedRangeCellAddress)
currentName = ThisWorkbook.Worksheets(SYSProjectData).Range(rangeNameValueCellAddress).Value2
Returns: "Type mismatch"
Set theRange = ThisWorkbook.Worksheets("SYSProjectData").Range(namedRangeCellAddress)
currentName = ThisWorkbook.Worksheets("SYSProjectData").Range(rangeNameValueCellAddress).Value2
Returns: "Application-defined or object-defined error"
Set theRange = ThisWorkbook.Sheets(SYSProjectData).Range(namedRangeCellAddress)
currentName = ThisWorkbook.Sheets(SYSProjectData).Range(rangeNameValueCellAddress).Value2
Returns: "Type mismatch"
Set theRange = ThisWorkbook.Sheets("SYSProjectData").Range(namedRangeCellAddress)
currentName = ThisWorkbook.Sheets("SYSProjectData").Range(rangeNameValueCellAddress).Value2
Returns: "Application-defined or object-defined error"
Set theRange = ThisWorkbook.Sheets(ActiveSheet).Range(namedRangeCellAddress)
currentName = ThisWorkbook.Sheets(ActiveSheet).Range(rangeNameValueCellAddress).Value2
Returns: "Type mismatch"
Set theRange = ThisWorkbook.Worksheets(ActiveSheet).Range(namedRangeCellAddress)
currentName = ThisWorkbook.Worksheets(ActiveSheet).Range(rangeNameValueCellAddress).Value2
Returns: "Type mismatch"
Can anyone tell me what I'm doing wrong?
Thanks!
You should be able to do something like this:
Public Sub SetRangeNames(startRow As Integer, endRow As Integer)
Dim currentRow As Long 'Long not Integer (always safer)
For currentRow = startRow To endRow
With ThisWorkbook.Sheets("SYSProjectData")
'worksheets Parent is the containing workbook
.Parent.Names.Add Name:=.Cells(currentRow, "D"), _
RefersTo:=.Cells(currentRow, "C")
End With
Next currentRow
End Sub
Heres your code, commented where there are issues
'Public Sub SetRangeNames(startRow As Integer, endRow As Integer)
' Better to use Long
Public Sub SetRangeNames(startRow As Long, endRow As Long)
Dim theRange As Range
Dim currentRow As Long ' Integer
Dim currentName As String
Dim rangeNameValueCellAddress As String
Dim namedRangeCellAddress As String
For currentRow = startRow To endRow
'rangeNameValueCellAddress = """D" & Trim(Str(currentRow) & """")
' Don't include " in the string value.
' No need for Trim(Str(
rangeNameValueCellAddress = "D" & currentRow
'namedRangeCellAddress = """C" & Trim(Str(currentRow) & """")
namedRangeCellAddress = "C" & currentRow
MsgBox ("rangeNameValueCellAddress = " & rangeNameValueCellAddress & _
"; namedRangeCellAddress = " & namedRangeCellAddress)
'*** The following two statements return errors:
'Set theRange = ThisWorkbook.Worksheets(ActiveSheet).Range(namedRangeCellAddress)
' ActiveSheet is already a worksheetsheet
Set theRange = ActiveSheet.Range(namedRangeCellAddress)
currentName = ActiveSheet.Range(rangeNameValueCellAddress).Value2
ActiveWorkbook.Names.Add Name:=currentName, RefersTo:=theRange
Next currentRow
End Sub
Here's an alternate method, see inline comments
Private Sub CommandButton2_Click()
SetRangeNames2 ActiveSheet.Range("C8")
End Sub
Public Sub SetRangeNames2(startCell As Range)
Dim Nm As Name
Dim Dat As Variant
Dim i As Long
With startCell.Worksheet
' Copy data to Variant array, for speed
Dat = .Range(startCell, .Cells(.Rows.Count, startCell.Column).End(xlUp)).Resize(, 2).Value2
' Loop the array
For i = 1 To UBound(Dat, 1)
' Check if name already exists
Set Nm = Nothing
On Error Resume Next
Set Nm = .Names(Dat(i, 2))
On Error GoTo 0
If Nm Is Nothing Then
' Add name
.Parent.Names.Add Name:=Dat(i, 2), RefersTo:=.Range(Dat(i, 1))
Else
' Name Already exists, update it
Nm.RefersToRange = .Range(Dat(i, 1))
End If
Next
End With
End Sub
Sorry for the delay in posting this. This is what I ended up with...
Public Sub SetRangeNames(strNamedRangeColumn As String, strNameSourceColumn As String, startRow As Long, endRow As Long)
Dim currentRow As Long
Dim rngNameSourceCell As Range
Dim rngNamedRangeCell As Range
Dim strNameSourceCellAddress As String
Dim strNamedRangeCellAddress As String
Dim strNameSourceCellValue As String
Dim strNamedRangeCellValue As String
Dim strRangeValueError As String
strRangeValueError = ""
strNamedRangeColumn = Trim(UCase(strNamedRangeColumn))
strNameSourceColumn = Trim(UCase(strNameSourceColumn))
If Len(strNamedRangeColumn) > 1 Then
MsgBox ("ERROR: The value given for the named range column, """ & strNamedRangeColumn & _
","" was longer than one character.")
Exit Sub
ElseIf Len(strNameSourceColumn) > 1 Then
MsgBox ("ERROR: The value given for the name source column, """ & strNameSourceColumn & _
","" was longer than one character.")
Exit Sub
ElseIf strNamedRangeColumn = "" Then
MsgBox ("ERROR: The value given for the named range column was longer than one character.")
Exit Sub
ElseIf strNameSourceColumn = "" Then
MsgBox ("ERROR: The value given for the name source column was longer than one character.")
Exit Sub
ElseIf Asc(strNamedRangeColumn) < 65 Or Asc(strNamedRangeColumn) > 90 Then
MsgBox ("ERROR: The value given for the named range column, """ & strNamedRangeColumn & _
","" was not a letter.")
Exit Sub
ElseIf Asc(strNameSourceColumn) < 65 Or Asc(strNameSourceColumn) > 90 Then
MsgBox ("ERROR: The value given for the name source column, """ & strNameSourceColumn & _
","" was not a letter.")
Exit Sub
End If
For currentRow = startRow To endRow
strNameSourceCellAddress = strNameSourceColumn & Trim(str(currentRow))
strNamedRangeCellAddress = strNamedRangeColumn & Trim(str(currentRow))
Set rngNameSourceCell = Range(strNameSourceCellAddress)
Set rngNamedRangeCell = Range(strNamedRangeCellAddress)
strNameSourceCellValue = Trim(rngNameSourceCell.Value)
If IsEmpty(rngNameSourceCell) Or Len(strNameSourceCellValue) > 0 Then
strRangeValueError = "Source cell " & strNameSourceCellAddress & " was empty."
End If
If Not (Application.WorksheetFunction.IsText(rngNameSourceCell.Value)) Then
If Len(strRangeValueError) > 0 Then
strRangeValueError = vbCrLf & strRangeValueError
End If
strRangeValueError = strRangeValueError & "Source cell " & strNameSourceCellAddress & _
" contained the not-text value """ & strNameSourceCellValue & """."
End If
If Len(strRangeValueError) > 0 Then
MsgBox (strRangeValueError)
Exit Sub
End If
ThisWorkbook.sheets("mySheetName").Parent.Names.Add Name:=.Cells(currentRow, strNameSourceColumn), _
RefersTo:=.Cells(currentRow, strNamedRangeColumn)
Next currentRow
End Sub
Called as follows...
Private Sub btnGenerateRangeNames_Click()
Call SetRangeNames("C", "E", 8, 11)
' etc.
End Sub
Thanks for your help!

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