How to combine Workbook, Worksheet and Range variables into one Range, in Excel VBA? - excel

Here is an example setting:
Dim wb As Workbook
Dim ws As Worksheet
Dim rn As Range
More specifically in my scenario:
The call:
Select Case CheckInDb(Range("Username"), "\..\_DataBase_\", "UserBase.xlsm", Sheets("UD_Base"), Range("UD_Base[U_ID]"))
The function header:
Function CheckInDb(What As Variant, Folder As String, FileName As String, Ws As Worksheet, Rn As Range)
I wanted to do something like this within the function (after opening the file, etc.):
(note: Wb is generated within the function)
CheckInDb = IsError(Application.Match(What,Wb.Ws.Rn, 0))
I have tried in several ways to precombine them step by step instead, but did not succeed with any
for example
set ws = wb.Sheets("RangeAdding")
would work manually, but Ws=Wb.Ws NOT. How to work around this, to achieve all 3 parts coming from variables?
Adding more information
I will post my full function to show what the real issue is. I have modded it a bit according to your suggestions, and the problem is:
The Workbook only gets opened inside the function, however if it is not open already when I write the full range in the calling it will give me an error, that it does not exist. Therefore I wanted to first ever use the range "live" inside the function, when the reference is already open.
Sub checkUser()
Select Case CheckInDb(Range("Username"), "\..\_DataBase_\", "UserBase.xlsm", Workbooks("Userbase.xlsm").Sheets("UD_Base").Range("UD_Base[U_ID]"))
Case True: Range("Status_U").Value = ("Szabad")
Case False: Range("Status_U").Value = ("Foglalt")
End Select
End Sub
Function CheckInDb(What As Variant, Folder As String, FileName As String, Rng As Range)
Dim Wb As Workbook
Dim wasOpen As Boolean
Dim File As String, Path As String
' Relative path:
Path = ThisWorkbook.Path & Folder
File = Path & FileName
On Error Resume Next
Set Wb = Workbooks(FileName)
wasOpen = True
On Error GoTo 0
If Wb Is Nothing Then
Set Wb = Workbooks.Open(File, , True) 'with settings: (File, true,true, , PW)
wasOpen = False
End If
'...
CheckInDb = IsError(Application.Match(What, Rng, 0))
'...
Select Case wasOpen
Case True
'Wb.Save
Case False
'Wb.Save
Wb.Close (False)
End Select
Set Wb = Nothing
End Function

Your rn object already knows which Worksheet object it belongs to. (You can use rn.Parent to refer to that worksheet.) And the worksheet knows which Workbook object it belongs to. (You can use rn.Parent.Parent to refer to that workbook.)
So your code should be:
CheckInDb = IsError(Application.Match(What, Rn, 0))
This means there is no need to pass the workbook and worksheet information to your function (because it is inherent in the Range you are passing), i.e.:
Select Case CheckInDb(Range("Username"), Range("UD_Base[U_ID]"))
and
Function CheckInDb(What As Variant, Rn As Range)
Based on your edit to the question, which now makes it clear that all the objects in the calling procedure don't exist when you execute the call (and therefore the call fails), you should pass the names of your workbook (as you already do), worksheet and range, i.e.:
Sub checkUser()
Select Case CheckInDb(Range("Username"), _
"\..\_DataBase_\", "UserBase.xlsm", _
"UD_Base", _
"U_ID")
Case True: Range("Status_U").Value = ("Szabad")
Case False: Range("Status_U").Value = ("Foglalt")
End Select
End Sub
Function CheckInDb(What As Variant, _
Folder As String, FileName As String, _
wsName As String,
rngName As String) As Boolean
'...
'... existing code to create wb object
'...
Dim rn As Range
Set rn = wb.Worksheets(wsName).Range(rngName)
CheckInDb = IsError(Application.Match(What, Rn, 0))

Related

Using a collection to check if Names exist

I am trying to create a subroutine that will take a collection of a bunch of strings, step through it, and check for the existence of a named range or formula that has that string as it's name. Trying it with just one item first:
Dim colCritNames As New Collection
colCritNames.Add "Version" 'the name of a named formula
For i = 1 To colCritNames.Count
nm = CStr(colCritNames(i).Name)
nmchk = Check_UW_For_Name(nm)
If Not nmchk Then Call Fail("Critical Name") 'prints a msgbox with the error type so I know what happened
Next i
'...code for if all the names are there...
Function Check_UW_For_Name(find_name As String) As Boolean
Dim wb As Workbook
Set wb = UserFileBook 'global ref to the workbook to check
On Error Goto Fail
Check_UW_For_Name = CBool(Len(wb.Names(find_name).Name) <> 0)
On Error GoTo 0
End Function
Thats edited from the full thing. Check_UW_For_Name was working fine when I just called it with "Version" as the argument Check_UW_For_Name("Version"); it found it in USerFIleBook, and when I called it with "Nope", since there is no Nope name it went to my error handler. But when I try to use a collection to store the names I want to look for I keep getting 'ByRef argument mismatch'. I tried just nm = colCritNames(i) and nm=colCritNames(i).Name, I tried having find_name be Variant and adding a ByVal, and I originally tried having nm be a Name, having Check_UW_For_Name(find_name as Name) and using a for each (for each nm in colCritNames...) and none of it has worked.
How could I set a collection of names and step through it to see if there's a named range/formula that matches in the relevant workbook? Or is there a better way to do this? (I need the collection in other places too)
I don't quite understand what your plan is with a collection, but this will add any cell with the specified string in, as well as any ranges. What you're doing once they've been identified (added to collection) is not clear to me, but hopefully this makes sense and gets you going.
Sub RunForEachString()
Const yourStrings = "foo,bar,hope,this,works"
Dim stringsAsArray() As String
stringsAsArray = Split(yourStrings, ",")
Dim i As Long
For i = LBound(stringsAsArray) To UBound(stringsAsArray)
Call findAllNamesFormulas(stringsAsArray(i), ThisWorkbook)
Next i
End Sub
Private Sub findAllNamesFormulas(theText As String, theWorkbook As Workbook)
Dim ws As Worksheet, n As Name, aCell As Range
Dim aCollection As New Collection
For Each ws In ThisWorkbook.Worksheets
For Each aCell In ws.UsedRange.Cells
If InStr(1, aCell.Formula, theText, vbTextCompare) > 0 Then
aCollection.Add (aCell)
End If
Next aCell
Next ws
For Each n In ThisWorkbook.Names
If InStr(1, n.Name, theText, vbTextCompare) > 0 Then
aCollection.Add (n)
End If
Next n
'not sure what you plan to do after collection?
Debug.Print aCollection.Count
End Sub
This works for me:
Sub Tester()
Dim colCritNames As New Collection, nm, wb As Workbook, msg As String
colCritNames.Add "Version"
colCritNames.Add "NotThere"
colCritNames.Add "AlsoNotThere"
Set wb = ThisWorkbook 'for example
For Each nm In colCritNames
If Not Check_UW_For_Name(wb, CStr(nm)) Then
msg = msg & vbLf & " - " & nm
End If
Next nm
If Len(msg) > 0 Then
MsgBox "One or more required names are missing:" & msg, _
vbExclamation, "Oops"
Exit Sub
End If
'proceed if OK...
End Sub
'check for a defined Name `find_name` in workbook `wb`
' prefer wb as parameter over using a Global....
Function Check_UW_For_Name(wb As Workbook, find_name As String) As Boolean
On Error Resume Next
Check_UW_For_Name = (wb.Names(find_name).Name = find_name)
End Function
You could create a collection of all named ranges in the workbook like this:
Private Sub NamedRangesDemo()
Dim NamedRanges As New Collection, NamedRange As Variant
For Each NamedRange In ThisWorkbook.Names
NamedRanges.Add NamedRange.Name
Next NamedRange
End Sub
And then compare the whatever strings you want to the NamedRanges collection.
By the way, this question is somewhat similar to yours.

Excel loses data when second workbook is closed

EDIT at the bottom of the question
I have a function in my code, that gets a dictionary of samples, fills in data from another workbook and returns filled dictionary. Everything worked fine, but once I changed the opening of the file to be ReadOnly, I experience problems.
Here is the code (which has been simplified to remove redundant parts):
Function get_samples_data(ByVal instructions As Scripting.Dictionary, ByRef patients_data As Scripting.Dictionary) As Scripting.Dictionary
'takes a dictionary of samples and fills their data from the file <0 GL all RL>
Dim wb As Workbook
Dim ws As Worksheet
Dim data_start As Long
Dim data_end As Long
Dim rw As Range
Dim rw_nr As String
'open <GP all> in ReadOnly mode based on path and filename in specific cells
Application.ScreenUpdating = False
Set wb = Workbooks.Open(ThisWorkbook.Sheets(1).Cells(13, 2).Value2 & ThisWorkbook.Sheets(1).Cells(13, 1).Value2, False, True)
Set ws = wb.Worksheets("ALL")
'get row nr. of the first and the last sample to export
data_start = ws.Columns("A:A").Find(what:=instructions("from_sample"), LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows).Row
data_end = ws.Columns("A:A").Find(what:=instructions("to_sample"), LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows).Row
'main loop
For i = data_start To data_end
Set rw = ws.Rows(i)
rw_nr = rw.Cells(1, 1).Value
If rw.Cells(1, 11).Value = instructions("group") Then
If patients_data.Exists(rw_nr) Then
Set patients_data(rw_nr) = fetch_sample_data(rw, patients_data(rw_nr))
End If
End If
Next
'close <GP all> without saving
wb.Close (False)
Set get_samples_data = patients_data
End Function
When I debugged, I noticed, that the data is lost on the call of wb.Close(False). Until that point data is intact, but once the source workbook is closed, the data (which is a range object) is turned blank. Not set to nothing, which happens when the data is not found in the source workbook, but all properties of the range object can be seen in debugger, but all have a value of .
Before I changed the openmode to ReadOnly, everything worked and data stayed there.
What did I miss? Why are data, stored in a different variable, lost?
EDIT:
Fetch sample data indeed returns a range object.
Private Function fetch_sample_data(ByVal rw As Range, ByRef sm As sample) As sample
Dim data As Range
Set data = Range(rw.Cells(1, 19), rw.Cells(1, 63))
Set sm.data = data
Set fetch_sample_data = sm
End Function
I tried changing the order of closing and setting the return value, but the error prevails.
Is it so then, that a Range object is always only a reference to a range in a worksheet? If I want the data to stay, do I need to change all Range objects in question to arrays? Or is there a way to create a Range object independent of a workbook (I do not want to copy the range into any sheet in the main workbook carrying the macro)?
Below is the main sub, as #Pᴇʜ asked for it. I will not add the remaining functions, because the whole code is scattered over 1 form, 2 modules and 14 classes (many carrying long methods).
The two commented open commands are those that caused everything to work properly. The closing commands were at the end of main sub, so in regards to the comment of #Pᴇʜ, if range object is always only a reference to an actual range of cells, they were available for the whole duration of the program.
Sub RL_creator_GP_main()
Dim instructions As New Scripting.Dictionary
Dim samples As Scripting.Dictionary
Dim result_list As Variant
Dim rep As cReport
Dim scribe As New descriptor
Application.ScreenUpdating = False
'get instructions from inputboxes (group, from sample, to sample)
Set instructions = procedures.input_instructions()
If instructions.Exists("terminated") Then
Exit Sub
End If
'get <GP all> and <RL headers> ready
'Call procedures.prepare_file("GP all.xlsx", pth:=ThisWorkbook.Sheets(1).Cells(12, 2).Value)
'Call procedures.prepare_file("RL headers.xlsx", pth:=ThisWorkbook.Sheets(1).Cells(13, 2).Value)
'get patients data from <RL headers>, closes the file afterwards
Set samples = procedures.get_patients_data(instructions)
'get patients data from <GP all>, closes the file afterwards
Set samples = procedures.get_samples_data(instructions, samples)
because samples is submitted ByRef to get_samples_data you don't need to return it:
Sub RL_creator_GP_main()
'your code here …
'get patients data from <RL headers>, closes the file afterwards
Set samples = procedures.get_patients_data(instructions)
'get patients data from <GP all>, closes the file afterwards
procedures.get_samples_data instructions, samples 'this call will change the original samples because it is ByRef!
In fetch_sample_data you add a range to your dictionary. But a Range object is only a reference to the worksheet and does not contain data itself. So instead of that turn the range into an array to add the actual data instead of only a reference:
Private Sub fetch_sample_data(ByVal rw As Range, ByRef sm As sample)
Dim data() As Variant
data = Range(rw.Cells(1, 19), rw.Cells(1, 63)).Value
Set sm.data = data
'again you don't need a function to return the sample as it is ByRef
End Sub
Finally get_samples_data should be a sub not a function. And call fetch_sample_data as a sub like fetch_sample_data rw, patients_data(rw_nr)
Sub get_samples_data(ByVal instructions As Scripting.Dictionary, ByRef patients_data As Scripting.Dictionary)
'takes a dictionary of samples and fills their data from the file <0 GL all RL>
Dim wb As Workbook
Dim ws As Worksheet
Dim data_start As Long
Dim data_end As Long
Dim rw As Range
Dim rw_nr As String
'open <GP all> in ReadOnly mode based on path and filename in specific cells
Application.ScreenUpdating = False
Set wb = Workbooks.Open(ThisWorkbook.Sheets(1).Cells(13, 2).Value2 & ThisWorkbook.Sheets(1).Cells(13, 1).Value2, False, True)
Set ws = wb.Worksheets("ALL")
'get row nr. of the first and the last sample to export
data_start = ws.Columns("A:A").Find(what:=instructions("from_sample"), LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows).Row
data_end = ws.Columns("A:A").Find(what:=instructions("to_sample"), LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows).Row
'main loop
For i = data_start To data_end
Set rw = ws.Rows(i)
rw_nr = rw.Cells(1, 1).Value
If rw.Cells(1, 11).Value = instructions("group") Then
If patients_data.Exists(rw_nr) Then
fetch_sample_data rw, patients_data(rw_nr)
End If
End If
Next
'close <GP all> without saving
wb.Close (False)
End Sub
Background explanation
Calling functions and subs:
First of all the Call statement is not needed. Parameters in functions are always in parenhesis, the function is used to return a value.
Result = MyFunction(Param1, Param2) ' functions return a result and parameters are in parentesis
MySub Param1, Param2 ' subs don't return a result and don't use parentesis
Call MySub(Param1, Param2) ' But with the Call statement they need parentesis
What does ByRef do:
If you declare a parameter ByRef that means you don't submit data to the sub but only a reference (By Reference) to that data in the memory. So if you have the following sub:
Sub MySub(ByVal Param1, ByRef Param2)
Param1 = 1
Param2 = 2
End Sub
And use it like
Sub Example()
Dim Var1 As Long: Var1 = 10
Dim Var2 As Long: Var2 = 20
MySub Var1, Var2 'note Var2 is submitted ByRef!
Debug.Print Var1, Var2 'returns 10, 2 the value in Var2 got changed by MySub without returning anything
End Sub
So when you submit the variabe by reference that means MySub changes the value in Var2 when performing Param2 = 2 because Param2 and Var2 reference the same space in memory. While if you submit ByVal (by value) you actually make a copy of the data in the memory and Param1 and Var1 reference different places in the memory.
That is why you don't need a function to return something if you submit it ByRef you already changed the data in the memory.
So in your code if you declare Sub get_samples_data(ByVal instructions As Scripting.Dictionary, ByRef patients_data As Scripting.Dictionary) then calling it like procedures.get_samples_data instructions, samples makes patients_data and samples point to the same space in memory. So because the data is only once in the memory and there is only 2 links pointing to them any changes made in one of the links actually edits the exact same data in memory. Therefore you don't need to return data.

Object doesn't support this property or method: Workbooks.Worksheets.Range

I am trying to duplicate color from a different workbook but getting a run-time error 438: Object doesn't support this property or method on the last line. I believe that before setting the workbooks and worksheet to certain names, it worked. What am I doing wrong?
Sub TextColor()
Dim wb1 As Workbook
Dim wb2 As Workbook
Dim ws1 As Worksheet
Application.ScreenUpdating = False
Set wb1 = Workbooks("File 1.xlsm")
Set wb2 = Workbooks("File 2.xlsm")
Set ws1 = Worksheets("Sheet 1")
wb2.Worksheets("Destination Sheet").Range("G4").Interior.Color = wb1.ws1.Range("D19").Interior.Color
The object 'ws1' is created separately and, while a child object of 'wb1', the variable itself is not a child object of it, only the reference to it. Valid referencing styles would be:
ws1
wb1.Worksheets("Sheet1")
Also, when you set 'ws1' you should reference the parent workbook in that line. This would change your 'ws1' line to now be:
Set ws1 = wb1.Worksheets("Sheet1")
From then on you only need to reference 'ws1' as the object.
As a side note, when working with other workbooks, I recommend testing if the worksheet exists or not. If you hard-code the name in and it doesn't exist, gets changed, or you made a typing error, this can throw an error. Below is a function you can use to test if a worksheet exists or not.
Function WorksheetExists( _
ByVal SheetName As String, _
Optional TargetBook As Workbook _
) As Boolean
If TargetBook Is Nothing Then
If ActiveWorkbook Is Nothing Then Exit Function
Set TargetBook = ActiveWorkbook
End If
On Error Resume Next
WorksheetExists = CBool(Len(TargetBook.Worksheets(SheetName).Name) <> 0)
On Error GoTo 0
End Function
The same logic applies if you're looking for a workbook and need to know if it exists, although a function for it would be slightly different, shown in the below code.
Public Function ExistingFile( _
ByVal FilePath As String _
) As Boolean
Dim Attributes As Integer
On Error Resume Next
Attributes = GetAttr(FilePath)
ExistingFile = (Err.Number = 0) And (Attributes And vbDirectory) = 0
Err.Clear
End Function
Note the above function could potentially throw an error if the file name is too long.

Loop a function that runs on files in a folder

I have a macro that is to be used inside a macro I found on internet.
The second macro runs through all Excel files inside a folder:
Sub RunOnAllFilesInFolder()
Dim folderName As String, eApp As Excel.Application, fileName As String
Dim wb As Workbook, ws As Worksheet, currWs As Worksheet, currWb As Workbook
Dim fDialog As Object: Set fDialog = Application.FileDialog(msoFileDialogFolderPicker)
Set currWb = ActiveWorkbook: Set currWs = ActiveSheet
'Select folder in which all files are stored
fDialog.Title = "Select a folder"
fDialog.InitialFileName = currWb.Path
If fDialog.Show = -1 Then
folderName = fDialog.SelectedItems(1)
End If
'Create a separate Excel process that is invisibile
Set eApp = New Excel.Application: eApp.Visible = False
'Search for all files in folder [replace *.* with your pattern e.g. *.xlsx]
fileName = Dir(folderName & "\*.*")
Do While fileName <> ""
'Update status bar to indicate progress
Application.StatusBar = "Processing " & folderName & "\" & fileName
Set wb = eApp.Workbooks.Open(folderName & "\" & fileName)
'...
'YOUR CODE HERE
'...
wb.Close SaveChanges:=False 'Close opened worbook w/o saving, change as needed
Debug.Print "Processed " & folderName & "\" & fileName
fileName = Dir()
Loop
eApp.Quit
Set eApp = Nothing
'Clear statusbar and notify of macro completion
Application.StatusBar = ""
MsgBox "Completed executing macro on all workbooks"
End Sub
I made a macro that, based on three named cells in a file, finds the ranges and change the style of some other ranges.
Not all Excel files have all three named cells, so I need the code to work when the range is not valid.
I tried to use error handlers but I received the following error:
"Loop without Do"
I tried IF and else for when the range does not exist and also found errors.
My code:
Dim test As Worksheet
Dim rOutstandingR As Range
Dim rAdditionalDueR As Range
Dim rFollowingR As Range
Dim rOutstandingBorderR As Range
Dim rAdditionalDueBorderR As Range
Dim rFollowingBorderR As Range
Dim ORow As Long
Dim OCol As Long
Dim ARow As Long
Dim ACol As Long
Dim FRow As Long
Dim FCol As Long
Dim OutstandingTopBorderRange As Range
Dim OutstandingBottomBorderRange As Range
Dim OutstandingRightBorderRange As Range
Dim AdditionalDueTopBorderRange As Range
Dim AdditionalDueBottomRange As Range
Dim AdditinalDueRightBorderRange As Range
Dim FollowingTopBorderRange As Range
Dim FollowingBottomBorderRange As Range
Dim FollowingRightBorderRange As Range
Dim OutstandingTextRange As Range
Dim AdditionalDueTextRange As Range
Dim FollowingTextRange
With Range("A1:Z999")
'Setting up another range that may not exists within excel file and give an error
Set rOutstandingR = ActiveSheet.Range("Outstanding")
rOutstandingBorderR = rOutstandingR.Address
rOutstandingR.Select
‘more code in which I change format of cells based on range
'Setting up another range that may not exists within excel file and give an error
Set rAdditionalDueR = ActiveSheet.Range("AdditionalDue")
rAdditionalDueBorderR = rAdditionalDueR.Address
rAdditionalDueR.Select
‘more code in which I change format of cells based on range
'Setting up another range that may not exists within excel file and give an error
'Setting Up rFollowingR as Range for Following Variable
Set rFollowingR = ActiveSheet.Range("Following")
rFollowingBorderR = rFollowingR.Address
rFollowingR.Select
‘more code in which I change format of cells based on range
As you can imagine by the amount of ranges, there is a lot of code in between but it's only based on the three major ranges for the named cells "Outstanding", "AdditionalDue" and "Following".
I need that all the codes between ranges work and if the first range doesn't exist goes to validate then next and do the changes of format, etcetera.
I tried to put some error handlers (resume labels) but I wasn't able to fix it when I used the code above within the first macro due to the loop through all the files.
How can I put the error handlers so I could use this macro inside the one that runs over a folder of files.
There are two ways to handle this, however with the snippets provided it's not straightforward to test what you're working on. You may want to consider separating your code into multiple subs/functions.
This solution should be clean assuming that you want some type of handling code to run:
With range("A1:Z999")
'Setting up another range that may not exists within excel file and give an error
On Error GoTo OutstandingError
Set rOutstandingR = ActiveSheet.range("Outstanding")
rOutstandingBorderR = rOutstandingR.Address
rOutstandingR.Select
OutstandingResume:
'more code in which I change format of cells based on range
'Setting up another range that may not exists within excel file and give an error
On Error GoTo AdditionalDueError
Set rAdditionalDueR = ActiveSheet.range("AdditionalDue")
rAdditionalDueBorderR = rAdditionalDueR.Address
rAdditionalDueR.Select
AdditionalDueResume:
'more code in which I change format of cells based on range
'Setting up another range that may not exists within excel file and give an error
'Setting Up rFollowingR as Range for Following Variable
On Error GoTo FollowingError
Set rFollowingR = ActiveSheet.range("Following")
rFollowingBorderR = rFollowingR.Address
rFollowingR.Select
FollowingResume:
'more code in which I change format of cells based on range
GoTo Complete
OutstandingError:
'Error handling code here
Resume OutstandingResume
AdditionalDueError:
'Error handling code here
Resume AdditionalDueResume
FollowingError:
'Error handling code here
Resume FollowingResume
Complete:
This solution just bypasses the block entirely without any handling code:
With range("A1:Z999")
'Setting up another range that may not exists within excel file and give an error
On Error GoTo OutstandingResume
Set rOutstandingR = ActiveSheet.range("Outstanding")
rOutstandingBorderR = rOutstandingR.Address
rOutstandingR.Select
OutstandingResume:
'more code in which I change format of cells based on range
'Setting up another range that may not exists within excel file and give an error
On Error GoTo AdditionalDueResume
Set rAdditionalDueR = ActiveSheet.range("AdditionalDue")
rAdditionalDueBorderR = rAdditionalDueR.Address
rAdditionalDueR.Select
AdditionalDueResume:
'more code in which I change format of cells based on range
'Setting up another range that may not exists within excel file and give an error
'Setting Up rFollowingR as Range for Following Variable
On Error GoTo FollowingResume
Set rFollowingR = ActiveSheet.range("Following")
rFollowingBorderR = rFollowingR.Address
rFollowingR.Select
FollowingResume:
'more code in which I change format of cells based on range
If you'd like to go in a different direction, here is a function that returns a boolean for whether or not a named range exists. Using this you could refactor this to use conditionals instead of relying on error checking and line jumps.
Private Function BET_RangeNameExists(nname) As Boolean
Dim n As Name
BET_RangeNameExists = False
For Each n In ActiveWorkbook.Names
If UCase(n.Name) = UCase(nname) Then
BET_RangeNameExists = True
Exit Function
End If
Next n
End Function
Taken from https://bettersolutions.com/excel/named-ranges/vba-named-range-exists.htm

Tracing precedents in external spreadsheets using Excel VBA

I'm currently trying to trace the dependencies of a complex set of Excel spreadsheets. My ideal end goal would be a tree structure, starting with my first spreadsheet. However, I don't want to include all of the dependencies of the child spreadsheets, just the ones of the cells referenced by the original spreadsheet. For example:
In cell A1 of my first workbook:
somebook.xls!Sheet1!C2
I want to look at cell C2 in sheet 1 of somebook.xls for its (external) dependencies, and then recurse.
At the moment I'm using LinkInfo to get a list of external dependencies, searching using Find, and I'm struggling with vbscript's primitive regex capabilities to try and extract the address out of the cells I find. This is not a brilliant way of doing things.
Does anyone know if Excel will tell you which cells in an external spreadsheet are being referenced? If not, any other tools that might help?
Thanks.
This answer is based off Bill Manville's macro from many years back. The macro still works, but I broke it out into functions allowing for more flexibility and reusability. The main addition by me is the ability to find external dependencies only, and the extension to both precedents and dependents. I also added a call to a custom macro called unhideAll; this was necessary for me as dependencies were not being found in hidden worksheets.
'Module for examining depedencies to/from a sheet from/to other sheets
Option Explicit
Sub showExternalDependents()
Dim deps As Collection
Set deps = findExternalDependents(ActiveCell)
Call showDents(deps, True, "External Dependents: ")
End Sub
Sub showExternalPrecedents()
Dim precs As Collection
Set precs = findExternalPrecedents(ActiveCell)
Call showDents(precs, True, "External Precedents: ")
End Sub
'external determines whether or not to print out the absolute address including workbook & worksheet
Sub showDents(dents As Collection, external As Boolean, header As String)
Dim dent As Variant
Dim stMsg As String
stMsg = ""
For Each dent In dents
stMsg = stMsg & vbNewLine & dent.Address(external:=external)
Next dent
MsgBox header & stMsg
End Sub
Function findPrecedents(rng As Range) As Collection
Set findPrecedents = findDents(rng, True)
End Function
Function findDependents(rng As Range) As Collection
Set findDependents = findDents(rng, False)
End Function
Function findExternalPrecedents(rng As Range) As Collection
Set findExternalPrecedents = findExternalDents(rng, True)
End Function
Function findExternalDependents(rng As Range) As Collection
Set findExternalDependents = findExternalDents(rng, False)
End Function
'Gives back only the dependencies that are not on the same sheet as rng
Function findExternalDents(rng As Range, precDir As Boolean) As Collection
Dim dents As New Collection
Dim dent As Range
Dim d As Variant
Dim ws As Worksheet
Set ws = rng.Worksheet
For Each d In findDents(rng, precDir)
Set dent = d
With dent
If Not (.Worksheet.Name = ws.Name And .Worksheet.Parent.Name = ws.Parent.Name) Then _
dents.Add Item:=dent
End With
Next d
Set findExternalDents = dents
End Function
'this procedure finds the cells which are the direct precedents/dependents of the active cell
'If precDir is true, then we look for precedents, else we look for dependents
Function findDents(rng As Range, precDir As Boolean) As Collection
'Need to unhide sheets for external dependencies or the navigate arrow won't work
Call mUnhideAll
Dim rLast As Range, iLinkNum As Integer, iArrowNum As Integer
Dim dents As New Collection
Dim bNewArrow As Boolean
'Appliciation.ScreenUpdating = False
If precDir Then
ActiveCell.showPrecedents
Else
ActiveCell.ShowDependents
End If
Set rLast = rng
iArrowNum = 1
iLinkNum = 1
bNewArrow = True
Do
Do
Application.Goto rLast
On Error Resume Next
ActiveCell.NavigateArrow TowardPrecedent:=precDir, ArrowNumber:=iArrowNum, LinkNumber:=iLinkNum
If Err.Number > 0 Then Exit Do
On Error GoTo 0
If rLast.Address(external:=True) = ActiveCell.Address(external:=True) Then Exit Do
bNewArrow = False
dents.Add Item:=Selection
iLinkNum = iLinkNum + 1 ' try another link
Loop
If bNewArrow Then Exit Do
iLinkNum = 1
bNewArrow = True
iArrowNum = iArrowNum + 1 'try another arrow
Loop
rLast.Parent.ClearArrows
Application.Goto rLast
Set findDents = dents
End Function
Sub mUnhideAll()
'
' mUnhideAll Macro
'
' Unhide All
Dim ws As Worksheet
For Each ws In Worksheets
ws.Visible = True
Next
'Sheets("Sprint Schedule Worksheet").Visible = False
End Sub
Excel's built in support, as you're finding, is limited and can be extremely frustrating.
In my experience, I've found a couple of tools from http://www.aivosto.com/ to be useful; Visustin v6 is especially useful for code related auditting/processing.
Here's a simpler version of Colm Bhandal's findDents and findExternalDents. It assumes all worksheets were made visible and arrows were cleared before use.
Function findDents(rCell As Range, bPrec As Boolean) As Collection
'Return all direct precedents (bPrec=True) or dependents (bPrec=False) of rCell
Dim sAddr As String, nLink As Integer, nArrow As Integer
Const bAbs As Boolean = False, bExt As Boolean = True
Set findDents = New Collection
If bPrec Then
rCell.showPrecedents ' even if rCell has no formula
Else
rCell.showDependents
End If
On Error Resume Next ' ignore errors
sAddr = rCell.Address(bAbs, bAbs, xlA1, bExt)
nArrow = 1
Do
nLink = 1
Do
rCell.NavigateArrow bPrec, nArrow, nLink
If ActiveCell.Address(bAbs, bAbs, xlA1, bExt) = sAddr Then Exit Do
findDents.Add Selection ' possibly more than one cell
nLink = nLink + 1
Loop
If nLink = 1 Then Exit Do
nArrow = nArrow + 1
Loop
On Error GoTo 0
If bPrec Then
rCell.showPrecedents Remove:=True
Else
rCell.showDependents Remove:=True
End If
End Function
Function findExternalDents(rCell As Range, bPrec As Boolean) As Collection
'Return ...Dents that are NOT in the same workbook and worksheet as rCell
Dim rDent As Range, wsName As String, wbName As String
With rCell.Worksheet: wsName = .Name: wbName = .Parent.Name: End With
Set findExternalDents = New Collection
For Each rDent In findDents(rCell, bPrec)
If rDent.Worksheet.Name <> wsName Or rDent.Worksheet.Parent.Name <> wbName Then findExternalDents.Add Item:=rDent
Next rDent
End Function
You might want to modify this to use a SortedList instead of a Collection. In that case, change
findDents.Add Selection
to
findDents.Add Selection.Address(bAbs, bAbs, xlA1, bExt), Null

Resources