Can't call VBA Dictionary item with string key - vba

I'm trying to create a mapping for data cleaning using vba dictionary. I store a range of values of country codes like FR, BE, NL as keys and their offset values as items: France, Belgium, Netherlands... When I run a test and try to retrieve the values using the strings as key, it throws run time error 451 'did not return object' Can anyone tell me what could be the problem?
Sub getthisdone()
'Dim dict As scripting.dictionary
Dim ws As Worksheet
Dim lastRow As Long
Dim key As String, dictItem As String
Dim i As Long
Set ws = ThisWorkbook.Worksheets("Country mapping")
Set dict = CreateObject("Scripting.Dictionary")
lastRow = ws.Range("A1").SpecialCells(xlCellTypeLastCell).Row
For i = 2 To 8 'lastRow
key = ws.Cells(i, 1).Text
dictItem = ws.Cells(i, 2).Text
With dict
.Add key, dictItem
End With
Next i
MsgBox dict.items("FR") '<---- Error happens here, why?
End Sub

.Item not .Items
MsgBox dict.Item("FR")
If you click Project → References, tick "Microsoft Scripting Runtime" and change the CreateObject line to:
Dim dict As Scripting.Dictionary: Set dict = New Scripting.Dictionary
You will get early binding auto-completion and avoid typos like this.

Related

dictionary.exists(key) ADDS the key

I am going crazy with VBA dictionaries, as the Exists() method makes no sense.
I thought you can use the dict.Exists(key) method to check if a key is in the dictionary without further actions. The problem is that when checking it, the key is automatically added into the dictionary. It really makes no sense!
Here's my code. Am I doing something wrong?
Function getContracts(wb As Workbook) As Dictionary
Dim cData As Variant, fromTo(1 To 2) As Variant
Dim contracts As New Dictionary, ctrDates As New Collection
Dim positions As New Dictionary, p As Long, r As Long
Dim dataSh As String, i As Long
dataSh = "Export"
cData = wb.Worksheets(dataSh).UsedRange
For i = LBound(cData) To UBound(cData)
fromTo(1) = cData(i, 1)
fromTo(2) = cData(i, 2)
Set ctrDates = Nothing
If IsDate(fromTo(1)) And IsDate(fromTo(2)) Then
If Not contracts.Exists(cData(i, 3)) Then ' Here it detects correctly that the key doesn't exist
ctrDates.Add fromTo
contracts.Add cData(i, 3), ctrDates ' And here it fails because the key just got added by .Exists()
Else
Set ctrDates = contracts(cData(i, 3))
ctrDates.Add fromTo
contracts(cData(i, 3)) = ctrDates
End If
Else
Debug.Print "Not a valid date in line " & i
End If
Next i
End Function
You can shorten your code to
For i = LBound(cData) To UBound(cData)
fromTo(1) = cData(i, 1)
fromTo(2) = cData(i, 2)
Set ctrDates = Nothing
If IsDate(fromTo(1)) And IsDate(fromTo(2)) Then
If Not IsEmpty(contracts(cData(i, 3))) Then Set ctrDates = contracts(cData(i, 3))
ctrDates.Add fromTo
Set contracts(cData(i, 3)) = ctrDates
Else
Debug.Print "Not a valid date in line " & i
End If
Next i
If one changes a value at a key it will automatically add the key if it does not exist.
Further reading on dictionaries
PS: This might also circumvent the strange behaviour described in the comments as you do not use the exist method. But on the other hand I have never experienced such a strange behaviour when using dictionaries
Collections of Date Pairs in a Dictionary
A reference to the Microsoft Scripting Runtime library is necessary for this to work.
Option Explicit
Sub GetContractsTEST()
Const dName As String = "Export"
Dim wb As Workbook: Set wb = ThisWorkbook
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim Contracts As Scripting.Dictionary: Set Contracts = GetContracts(dws)
If Contracts Is Nothing Then Exit Sub
Dim Key As Variant, Item As Variant
For Each Key In Contracts.Keys
Debug.Print Key
For Each Item In Contracts(Key)
Debug.Print Item(1), Item(2)
Next Item
Next Key
End Sub
Function GetContracts(ByVal ws As Worksheet) As Scripting.Dictionary
Const ProcName As String = "GetContracts"
On Error GoTo ClearError
Dim cData As Variant: cData = ws.UsedRange.Value
Dim fromTo(1 To 2) As Variant
Dim Contracts As New Scripting.Dictionary
Contracts.CompareMode = TextCompare
Dim r As Long
For r = LBound(cData) To UBound(cData)
fromTo(1) = cData(r, 1)
fromTo(2) = cData(r, 2)
If IsDate(fromTo(1)) And IsDate(fromTo(2)) Then
If Not Contracts.Exists(cData(r, 3)) Then
Set Contracts(cData(r, 3)) = New Collection
End If
Contracts(cData(r, 3)).Add fromTo
Else
Debug.Print "Not a valid date in line " & r
End If
Next r
Set GetContracts = Contracts
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
Possible Solution:
I had the same issue, this tends to happen when the compare more has not been set. I have not dug any deeper into this as the issue cannot always be replicated and the documentation around .Exists() and .CompareMode isn't that thorough source.
(as everyone has said you should enable the Microsoft Scripting Runtime reference for early binding)
When creating a new dictionary set its .CompareMode to vbBinaryCompare this will set a more strict compare mode and also in my case fixes the .Exists() bug. Do note that you can only set .CompareMode on an empty dictionary
Dim NewDictionary As New Scripting.Dictionary
NewDictionary.CompareMode = vbBinaryCompare
If NewDictionary.Exists(key) Then
'do things
End If

VBA: Add Item to Existing Key in Dictionary

I want to do something extremely simple and I still can't find the solution via Google.
I want to add an item to an existing key in a VBA dictionary.
My Code:
Sub mymacro()
Set DICT = CreateObject("scripting.dictionary")
For i = 1 To 10
key = Worksheets("Sheet1").Cells(i, "D").Value
item = Worksheets("Sheet1").Cells(i, "L").Value
DICT.Add key, item
If DICT.Exists(key) Then
DICT(key).Add item '(<-- causes runtime error 424, object required)
End If
Next i
For Each i In DICT.Items: Debug.Print i: Next
End Sub
This code however is giving me a Runtime Error 424, 'Object required'
Any help is appreciated
You can store Collection objects in the Dictionary's values. This allows the storage of multiple values per key. Something like:
Option Explicit
Sub mymacro()
Dim dict As Object
Set dict = CreateObject("scripting.dictionary")
Dim i As Long
Dim ColumnDKey As Variant
Dim ColumnLValue As Variant
Dim ColumnLValues As Collection
For i = 1 To 10
ColumnDKey = Worksheets("Sheet1").Cells(i, "D").Value
ColumnLValue = Worksheets("Sheet1").Cells(i, "L").Value
If Not dict.Exists(ColumnDKey) Then
dict.Add ColumnDKey, New Collection
End If
Set ColumnLValues = dict.Item(ColumnDKey)
ColumnLValues.Add ColumnLValue
Next i
For Each ColumnDKey In dict.Keys
Set ColumnLValues = dict.Item(ColumnDKey)
For Each ColumnLValue In ColumnLValues
Debug.Print ColumnLValue
Next
Next
End Sub

Object or With Variable Not Set

Option Explicit
Public Sub consolidateList()
DeleteTableRows (ThisWorkbook.Worksheets("Master").ListObjects("MasterSheet"))
FillTableRows
End Sub
Private Sub FillTableRows()
'set up worksheet objects
Dim wkSheet As Worksheet
Dim wkBook As Workbook
Dim wkBookPath As String
Set wkBook = ThisWorkbook
wkBookPath = wkBook.Path
Set wkSheet = wkBook.Worksheets("Master")
'set up file system objects
Dim oFile As Object
Dim oFSO As Object
Dim oFolder As Object
Dim oFiles As Object
Set oFSO = CreateObject("Scripting.FileSystemObject")
Set oFolder = oFSO.GetFolder(wkBookPath)
Set oFiles = oFolder.Files
'set up loop
Dim checkBook As Excel.Workbook
Dim reportDict As Dictionary
Application.ScreenUpdating = False
'initial coordinates
Dim startRow As Long
Dim startColumn As Long
startColumn = 3
Dim i As Long 'tracks within the row of the sheet where information is being pulled from
Dim k As Long 'tracks the row where data is output on
Dim j As Long 'tracks within the row of the sheet where the data is output on
Dim Key As Variant
j = 1
k = wkSheet.Range("a65536").End(xlUp).Row + 1
Dim l As Long
'look t Set checkBook = Workbooks.Open(oFile.Path)hrough folder and then save it to temp memory
On Error GoTo debuger
For Each oFile In oFiles
startRow = 8
'is it not the master sheet? check for duplicate entries
'oFile.name is the name of the file being scanned
'is it an excel file?
If Mid(oFile.Name, Len(oFile.Name) - 3, 4) = ".xls" Or Mid(oFile.Name, Len(oFile.Name) - 3, 4) = ".xlsx" Then
Set checkBook = Workbooks.Open(oFile.Path)
For l = startRow To 600
If Not (IsEmpty(Cells(startRow, startColumn))) Then
'if it is, time do some calculations
Set reportDict = New Dictionary
'add items of the payment
For i = 0 To 33
If Not IsEmpty(Cells(startRow, startColumn + i)) Then
reportDict.Add Cells(4, startColumn + i), Cells(startRow, startColumn + i)
End If
Next i
For i = startRow To 0 Step -1
If Not IsEmpty(Cells(i, startColumn - 1)) Then
reportDict.Add "Consumer Name", Cells(i, startColumn - 1)
Exit For
End If
Next i
'key is added
For Each Key In reportDict
'wkSheet.Cells(k, j) = reportDict.Item(Key)
Dim myInsert As Variant
Set myInsert = reportDict.Item(Key)
MsgBox (myInsert)
wkSheet.ListObjects(1).DataBodyRange(2, 1) = reportDict.Item(Key)
j = j + 1
Next Key
wkSheet.Cells(k, j) = wkSheet.Cells(k, 9) / 4
wkSheet.Cells(k, j + 1) = oFile.Name
'
k = k + 1
' Set reportDict = Nothing
j = 1
Else
l = l + 1
End If
startRow = startRow + 1
Next l
checkBook.Close
End If
' Exit For
Next oFile
Exit Sub
debuger:
MsgBox ("Error on: " & Err.Source & " in file " & oFile.Name & ", error is " & Err.Description)
End Sub
Sub DeleteTableRows(ByRef Table As ListObject)
On Error Resume Next
'~~> Clear Header Row `IF` it exists
Table.DataBodyRange.ClearContents
'~~> Delete all the other rows `IF `they exist
Table.DataBodyRange.Offset(1, 0).Resize(Table.DataBodyRange.Rows.count - 1, _
Table.DataBodyRange.Columns.count).Rows.Delete
On Error GoTo 0
End Sub
Greetings. The above code consolidates a folder of data that's held on excel spreadsheets into one master excel spreadsheet. The goal is to run a macro on Excel Spreadsheet named master on the worksheet named master which opens up other excel workbooks in the folder, takes the information, and puts it into a table in the worksheet "master". After which point, it becomes easy to see the information; so instead of it being held on hundreds of worksheets, the records are held on one worksheet.
The code uses a dictionary (reportDict) to temporarily store the information that is needed from the individual workbooks. The goal then is to take that information and place it in the master table at the bottom row, and then obviously add a new row either after a successful placement or before an attempted placement of data.
The code fails at the following line:
wkSheet.ListObjects(1).DataBodyRange(2, 1) = reportDict.Item(Key)
The failure description is "object or with variable not set" and so the issue is with the reportDict.Item(Key). My guess is that somehow VBA is not recognizing the dictionary item as stable, but I don't know how to correct this. Eventually the goal is to have code which does:
for each key in reportDict
- place the item which is mapped to the key at a unique row,column in the master table
- expand the table to accomodate necessary data
next key
Implicit default member calls are plaguing your code all over.
reportDict.Add Cells(4, startColumn + i), Cells(startRow, startColumn + i)
That's implicitly accessing Range.[_Default] off whatever worksheet is currently the ActiveSheet (did you mean that to be wkSheet.Cells?), to get the Key - since the Key parameter is a String, Range.[_Default] is implicitly coerced into one, and you have a string key. The actual dictionary item at that key though, isn't as lucky.
Here's a MCVE:
Public Sub Test()
Dim d As Dictionary
Set d = New Dictionary
d.Add "A1", Cells(1, 1)
Debug.Print IsObject(d("A1"))
End Sub
This procedure prints True to the debug pane (Ctrl+G): what you're storing in your dictionary isn't a bunch of string values, but a bunch of Range object references.
So when you do this:
Dim myInsert As Variant
Set myInsert = reportDict.Item(Key)
You might as well have declared myInsert As Range, for it is one.
This is where things get interesting:
MsgBox (myInsert)
Nevermind the superfluous parentheses that force-evaluate the object's default member and pass it ByVal to the MsgBox function - here you're implicitly converting Range.[_Default] into a String. That probably works.
So why is this failing then?
wkSheet.ListObjects(1).DataBodyRange(2, 1) = reportDict.Item(Key)
Normally, it wouldn't. VBA would happily do this:
wkSheet.ListObjects(1).DataBodyRange.Cells(2, 1).[_Default] = reportDict.Item(Key).[_Default]
And write the value in the DataBodyRange of the ListObject at the specified location.
I think that's all just red herring. Write explicit code: if you mean to store the Value of a cell, store the Value of a cell. If you mean to assign the Value of a cell, assign the Value of a cell.
I can't replicate error 91 with this setup.
This, however:
DeleteTableRows (ThisWorkbook.Worksheets("Master").ListObjects("MasterSheet"))
...is also force-evaluating a ListObject's default member - so DeleteTableRows isn't receiving a ListObject, it's getting a String that contains the name of the object you've just dereferenced... but DeleteTableRows takes a ListObject parameter, so there's no way that code can even get to run FillTableRows - it has to blow up with a type mismatch before DeleteTableRows even gets to enter. In fact, it's a compile-time error.
So this is a rather long answer that doesn't get to the reason for error 91 on that specific line (I can't reproduce it), but highlights a metric ton of serious problems with your code that very likely are related to this error you're getting. Hope it helps.
You need to iterate through the dictionary's Keys collection.
dim k as variant, myInsert As Variant
for each k in reportDict.keys
debug.print reportDict.Item(k)
next k

Search if value in cell (i,j) exists in another sheets, else i+1 until same value is found

I have an issue with my VBA script which I'm not able to resolve, despite of all the researches I've made (Indeed, I tried to modify all the vba scripts which were near what I'm looking for, but it doesn't work).
Thank you very much for your help !
I have 2 sheets.
For the first one (ActiveSheet), I have a list.
For example :
Beurre
Creme fraiche
Fromage
Oeufs
Yaourts
In the second one ("Add value"), I have this list :
Chocolat
Carotte
Haricot
Fromage
Endive
I want the script to verify if the first value which is the sheet ("Add Value") exists in the ActiveSheet.
If it doesn't, it takes the second value in "Add Value" to make this verification. And so on with the other lines.
The loop has to stop when the script finds the same value. Then it does an action (MsgBox, for example).
For example, when the script researches "Chocolat" (the first line of the sheet "Add Value") in the ActiveSheet, it won't find it : it will use the second word to make this reasearch until it uses world "Fromage" which also exist in the second sheet.
It does the action (the msgbox), then quit the loop to continue with the other called macro which are in the script.
Moreover, I would like to choose the columns of the cell from "Add Value" each time I call the macro. Indeed, there will be several lists in this sheet.
Here is my macro. The issue is that I get the error 424 on the ligne If Not FindString Is Nothing Then
Public Sub Var()
Dim plage As Variant
Set plage = ActiveSheet.Range("A:A")
Dim col As Integer
Dim Ligne As Integer
Set Ligne = 2
Dim FindString As String
Set FindString = ThisWorkbook.Sheets("Add Value").Cells(Ligne, col).Value
End Sub
Sub Boucle_Ajout(col)
With plage
Do
If Not FindString Is Nothing Then
'do
Else
Ligne = Ligne + 1
End If
Loop While Not FindString Is Nothing
End With
End Sub
Then when I call the Macro, I only have to choose the column.
For example :
Call Boucle_Ajout(1)
Thank you very much for your help, because I am sick of not finding the solution.
PS : sorry for my english, I'm french.
Assuming the lines without numbers are in A1 to A5, this works:
Option Explicit
Const THECOLUMN = "A1"
Sub FindLineInOtherSheet()
Dim activeSheetRange As Range
Dim addValueRange As Range
Dim activeSheetLastRow As Integer
Dim addValueLastRow As Integer
Dim i As Integer
Dim n As Integer
Dim activeSheetCell As String
Dim addValueCell As String
'*
'* Setup
'*
Set activeSheetRange = ThisWorkbook.Sheets("activeSheet").Range(THECOLUMN)
activeSheetLastRow = findLastRow("activeSheet", THECOLUMN)
addValueLastRow = findLastRow("addValue", THECOLUMN)
'*
'* Loop through each cell in addValue for each cell in activeSheet
'*
For i = 1 To activeSheetLastRow
Set addValueRange = ThisWorkbook.Sheets("addValue").Range(THECOLUMN)
activeSheetCell = activeSheetRange.Value
For n = 1 To addValueLastRow
addValueCell = addValueRange.Value
If addValueCell = activeSheetCell Then
MsgBox ("Trouvé " & addValueCell)
End If
Set addValueRange = addValueRange.Offset(1, 0) 'Next row
Next n
Set activeSheetRange = activeSheetRange.Offset(1, 0)
Next i
End Sub
Function findLastRow(Sheetname As String, ColumnName As String) As Integer
Dim lastRow As Integer
Dim r As Range
Dim WS As Worksheet
Set WS = Worksheets(Sheetname)
lastRow = WS.UsedRange.Rows.Count
'*
'* Search backwards till we find a cell that is not empty
'*
Set r = WS.Range(ColumnName).Rows(lastRow)
While IsEmpty(r)
Set r = r.Offset(-1, 0)
Wend
lastRow = r.Row
Set WS = Nothing
findLastRow = lastRow
End Function

Copy named ranges to the active sheet

I'm trying to copy named ranges from the Wk1 worksheet to the active sheet in the workbook.
I keep getting error messages when I run the code. They either say an Object is not set or a variable has not been declared.
Sub ChangeNamedRangesOnNewWKsheet()
Dim RangeName As Name
Dim HighlightRange As Range
Dim RangeName2 As String
Dim NewRangeName As String
Dim Ws As Worksheets
Dim cs As Worksheet
Set cs = Application.ActiveSheet
''''' Delete invalid named ranges
For Each RangeName In ActiveWorkbook.Names
If InStr(1, RangeName.RefersTo, "#REF!") > 0 Then
RangeName.Delete
End If
Next RangeName
For Each RangeName In Ws
If InStr(1, RangeName, "Wk1", 1) > 0 Then
Set HighlightRange = RangeName.RefersToRange
NewRangeName = Replace(RangeName.Name, "Wk1", "cs.Name")
RangeName2 = Replace(RangeName, "='Wk1'", "'cs.Name'")
On Error Resume Next
HighlightRange.Copy Destination:=Worksheets("cs.Name").Range(RangeName2)
Range(RangeName2).Name = NewRangeName
On Error GoTo 0
End If
Next RangeName
MsgBox "Done"
End Sub
Ive changed the code to this. Im not getting error messages but the code is still not working. the named ranges are not copying from the Wk1 sheet to the Active sheet. The only thing that happens is that the Message Box Opens
Sub ChangeNamedRangesOnNewWKsheet()
Dim RangeName As Name
Dim HighlightRange As Range
Dim RangeName2 As String
Dim NewRangeName As String
Dim Cs As Worksheet
Set Cs = Application.ActiveSheet
''''' Delete invalid named ranges
For Each RangeName In ActiveWorkbook.Names
If InStr(1, RangeName.RefersTo, "#REF!") > 0 Then
RangeName.Delete
End If
Next RangeName
For Each RangeName In ActiveWorkbook.Names
If InStr(1, RangeName, "Wk1", 1) > 0 Then
Set HighlightRange = RangeName.RefersToRange
NewRangeName = Replace(RangeName.Name, "Wk1", "cs.Name")
RangeName2 = Replace(RangeName, "='Wk1'", "'cs.Name'")
On Error Resume Next
HighlightRange.Copy Destination:=Worksheets("cs.Name").Range(RangeName2)
Range(RangeName2).Name = NewRangeName
On Error GoTo 0
End If
Next RangeName
MsgBox "Done"
End Sub
Took me some time to figure out whats not working when there is no error, but finally I think I managed to figure out the issue.
Replace the following line in your code
HighlightRange.Copy Destination:=Worksheets("cs.Name").Range(RangeName2)
to:
HighlightRange.Copy Destination:=Worksheets(cs.Name).Range(HighlightRange.Address)
This should give you desired result.
Syntax for Copy to destination is Destination:=Worksheets("sheet_name").Range(range). Here sheet_name should be the name of the sheet. So when you write Worksheets("cs.Name") code looks for the sheet named cs.Name which actually does not exist hence just use Worksheets(cs.Name). Second thing here is range (just to explain things better I am using $A$1:$A$5 as range). When you write .Range(RangeName2) code is looking for 'cs.Name'!$A$1:$A$5. Again this is incorrect because range should be written as .Range($A$1:$A$5). So .Range(HighlightRange.Address) will give you the proper range.
You can also play out in the line RangeName2 = Replace(RangeName, "='Wk1'", "'cs.Name'") to get proper address.
Hope this helps.
EDIT :
__________________________________________________________________________________
example of what i want. copy the named range Wk1Totalhrs from Wk1 sheet to Wk2-Wk7 sheets so that Wk1Totalhrs becomes Wk2Totalhrs,Wk3Totalhrs etc on the corresponding new sheet
Try the following code to achieve what you mentioned as your requirement in comment (or as above).
Sub ChangeNamedRangesOnNewWKsheet()
Dim RangeName As Name
Dim HighlightRange As Range
Dim RangeName2 As String, NewRangeName As String, SearchRange As String
Dim MyWrkSht As Worksheet, cs As Worksheet
Set MyWrkSht = ActiveWorkbook.Worksheets("Wk1")
SearchRange = "Wk1Totalhrs" '---> enter name of the range to be copied
''''' Delete invalid named ranges
For Each RangeName In MyWrkSht.Names
If InStr(1, RangeName.RefersTo, "#REF!") > 0 Then
RangeName.Delete
End If
Next RangeName
'For Each RangeName In MyWrkSht.Names ActiveWorkbook.Names
For Each RangeName In ActiveWorkbook.Names
If RangeName.Name = SearchRange Then '---> search for the named range Wk1Totalhrs
Set HighlightRange = RangeName.RefersToRange
For Each cs In ActiveWorkbook.Sheets
Debug.Print cs.Name
If cs.Name <> "Wk1" Then '---> don't do anything in the sheet Wk1
NewRangeName = Replace(RangeName.Name, "Wk1", cs.Name)
RangeName2 = Replace(RangeName, "='Wk1'", cs.Name)
HighlightRange.Copy Destination:=Worksheets(cs.Name).Range(HighlightRange.Address)
Range(RangeName2).Name = NewRangeName
End If
Next cs
End If
Next RangeName
End Sub
I think it's just as simple as this.
Public Sub ShowNames()
Dim Nm As Name
Dim i As Long
For Each Nm In ActiveWorkbook.Names
i = i + 1
Range("A1").Offset(i, 0).Value = Nm
Next Nm
End Sub
Im not getting error messages but the code is still not working.the named ranges are not copying from the Wk1 sheet to the Active sheet.
The following line will return false positives when the named range starts with or contains WK10, WK11, etc.
If InStr(1, RangeName, "Wk1", 1) > 0 Then
A little further down, you are quoting a variable property; this makes it a literal string, not the value of the variable property.
NewRangeName = Replace(RangeName.Name, "Wk1", "cs.Name")
You need a more concrete way to identify the defined names on WK1. After looking closely at your problem, I believe that you may have one or more dynamic named ranges that are defined by formulas. This would explain some of the 'not working' behavior of your code that should be working with more conventional ReferTo: properties.
There is also the problem of whether you should rewrite the RefersTo: of an existing defined named range or add a new named range. One common practise is to simply attempt to delete the named range un On Error Resume Next and then create a new one. I've never liked this method for a variety of reasons; one being that deleting a named range will make dependent named ranges refer to #REF! and I've never considered on error resume next to be a 'best practise'.
The following builds a dictionary of keys containing named ranges to be created and ones that already exist using multiple criteria. I've tested this repeatedly on a combination of conventional and dynamic named ranges with success.
Option Explicit
Sub ChangeNamedRangesOnNewWKsheet()
Dim nm As Name
Dim rtr As String, nm2 As String
Dim w As Long
Dim k As Variant, dict As Object
Set dict = CreateObject("Scripting.Dictionary")
dict.comparemode = vbTextCompare
With ActiveWorkbook
'Delete invalid named ranges and build dictionary of valid ones from WK1
For Each nm In .Names
If CBool(InStr(1, nm.RefersTo, "#REF!", vbTextCompare)) Or _
CBool(InStr(1, nm.RefersTo, "#NAME?", vbTextCompare)) Then
'Debug.Print nm.Name
On Error Resume Next
nm.Delete
Err.Clear
On Error GoTo 0
ElseIf LCase(Left(nm.Name, 3)) = "wk1" And _
(CBool(InStr(1, nm.RefersTo, "wk1!", vbTextCompare)) Or _
CBool(InStr(1, nm.RefersTo, "'wk1'!", vbTextCompare))) Then
dict.Item(Mid(nm.Name, 4)) = LCase(nm.RefersTo)
ElseIf LCase(Left(nm.Name, 2)) = "wk" Then
dict.Item(nm.Name) = LCase(nm.RefersTo)
End If
Next nm
For w = 1 To Worksheets.Count
With Worksheets(w)
If LCase(.Name) <> "wk1" And Left(LCase(.Name), 2) = "wk" Then
For Each k In dict
If dict.exists(.Name & k) Then
.Parent.Names(.Name & k).RefersTo = _
Replace(LCase(dict.Item(k)), "wk1", .Name, 1, -1, vbTextCompare)
ElseIf Left(LCase(k), 2) <> "wk" Then
.Parent.Names.Add _
Name:=.Name & k, _
RefersTo:=Replace(LCase(dict.Item(k)), "wk1", .Name, 1, -1, vbTextCompare)
End If
Next k
End If
End With
Next w
End With
dict.RemoveAll: Set dict = Nothing
'MsgBox "All worksheets done"
End Sub
Note that this creates/redefines all named ranges on all worksheets (other than WK1). As far as I can determine, the only chance to have false positives would be to have an existing named range with a name something like WK1wkrange (but that would just be silly).
This code works
Public Sub CopyNamedRanges()
Dim namedRange As Name
Dim targetRefersTo As String
Dim targetName As String
On Error Resume Next
For Each namedRange In ActiveWorkbook.Names
If Left$(namedRange.RefersTo, 6) = "='Wk1'" And Left$(namedRange.Name, 3) = "Wk1" Then
targetName = Replace(namedRange.Name, "Wk1", ActiveSheet.Name)
targetRefersTo = Replace(namedRange.RefersTo, "Wk1", ActiveSheet.Name)
ActiveWorkbook.Names.Add targetName, targetRefersTo ' Might error if it already exists
ActiveWorkbook.Names(targetName).RefersTo = targetRefersTo
namedRange.RefersToRange.Copy Range(targetName) ' Remove this line if it's not required
End If
Next
End Sub
How the code works
This part If Left$(namedRange.RefersTo, 6) = "='Wk1'"
makes sure that the range refers to some cells on the sheet called Wk1
The other condition (Left$(namedRange.Name, 3) = "Wk1") would also match named ranges on sheets Wk10 - Wk19.
This part ActiveWorkbook.Names.Add targetName, targetRefersTo will adds a new named range that refers to the cells on the current sheet
This part namedRange.RefersToRange.Copy Range(targetName) copies the contents of the named range on the Wk1 sheet to the current sheet (remove the line if you don't need it)
Dim RangeName As Variant Try changing the variable type