I'm in a module function, and I have a value that needs to get updated across multiple worksheets. I would like to take a data driven approach to this, since it may change a bit in the future.
In essence, I want to create an array of strings, each entry is an absolute reference to a cell, something like so:
Array("'Sheet1'!$A$1","'Sheet2'!$C$5")
I'd like to be able to do something like so
for each item in arr
Range(item).value = some_value
next item
The issue is that I'm in a module, The Range property is only available on a worksheet, and if I try to reference worksheet B from worksheet A via the Range property, it gives me an error.
How would you go about doing this?
Create an array of range objects like so:
arr = Array(WorkSheets("Sheet1").Range("A1"), WorkSheets("Sheet2").Range("C5"))
Dim rng as Range
For i = LBound(arr) To UBound(arr)
arr(i).Value = some_value
Next i
You could also use the Collection class
Dim coll As New Collection
Dim rng As Range
coll.Add WorkSheets("Sheet1").Range("A1")
coll.Add WorkSheets("Sheet2").Range("C5")
For Each rng In coll
rng.Value = some_value
Next rng
Given an array of string addresses, you can process it like
Sub Demo()
Dim arr As Variant
Dim sh As String, addr As String
Dim item As Variant
arr = Array("'Sheet 1'!$A$1", "'Sheet2'!$C$5")
For Each item In arr
sh = Replace(Left(item, InStr(item, "!") - 1), "'", "")
addr = Mid(item, InStr(item, "!") + 1)
Worksheets(sh).Range(addr) = some_value
Next
End Sub
If you can switch to an array (or collection) of Range then justnS' answer is better. But if you need to stick with an array of strings, this will do it.
You ask about multiple worksheets but say your program may be extended later. If it possible that you will need to update multiple workbooks, the following may be helpful.
I have set the array elements to workbook name, worksheet name, cell address and value. I have assumed the destination workbooks are open although it would not be difficult for the macro to open them if necessary. I test the workbook and worksheet names but not the cell address.
Sub Test1()
'
Dim Dest() As Variant
Dim DestPart() As String
Dim Found As Boolean
Dim InxBook As Integer
Dim InxDest As Integer
Dim InxSheet As Integer
Dest = Array("Test1.xls|Sheet3|B1|abc", "Test2.xls|Sheet2|F5|def", _
"Test3.xls|Sheet1|D3|ghi")
' Each element of Dest contains: workbook name, sheet name, cell address,
' and value separated by pipes.
' This code assumes the destination workbooks are already open.
For InxDest = LBound(Dest) To UBound(Dest)
DestPart = Split(Dest(InxDest), "|")
Found = False
For InxBook = 1 To Workbooks.Count
If DestPart(0) = Workbooks(InxBook).Name Then
Found = True
Exit For
End If
Next
If Found Then
With Workbooks(InxBook)
Found = False
For InxSheet = 1 To .Sheets.Count
If DestPart(1) = .Sheets(InxSheet).Name Then
Found = True
Exit For
End If
Next
If Found Then
.Sheets(InxSheet).Range(DestPart(2)).Value = DestPart(3)
End If
End With
End If
Next
End Sub
Related
Little background, I am very new to VBA and just cant seem to find a solution to my problem. I am using this project as a means of learning basic VBA principles. Please bare with me.
I am currently attempting to write a macro that pulls values from non-contiguous cells (IE: F9, E15, G17, etc..) from a specific workbook and then pastes them into a table in a primary workbook. Each cell has data that needs to be added to a specific column in said table. I have hundreds of different files with the exact same layout (same important cell locations) that I want to eventually cycle through and add to a master table on the primary workbook. I would like to automate it.
My problem lies in not knowing the best method do go about this. I only need information from 12 cells per file so it is not an intense transfer. I have attempted going about it through arrays, creating variables, and messing with ranges. I was able to get to the point where I create a different variable for each cell I want data from and then, one-by-one, insert them into a specific cell in the primary workbook. This is far from automatic and doesn't include inserting each value under a specific column in my table.
Here is the most functional macro I've been able to create. It seems clunky and inefficient and does not prove to be a solution for my primary problems: automation, efficiency.
Sub data_pull()
Dim x As Workbook
Dim y As Workbook
Application.ScreenUpdating = False
Set x = Workbooks.Open("C:\Users\ - workbook that data is pulled from")
Set y = Workbooks.Open("C:\Users\ - workbook that data is put to")
'Pulling data through variables
RSS = x.Sheets(1).Range("F9").Value
RSE1_F = x.Sheets(1).Range("E13").Value
RSE1_B = x.Sheets(1).Range("F13").Value
RSE2_F = x.Sheets(1).Range("E14").Value
RSE2_B = x.Sheets(1).Range("F14").Value
TI = x.Sheets(1).Range("F20").Value
SI = x.Sheets(1).Range("F30").Value
FIBI = Split(x.Sheets(1).Range("F36").Value, "/") 'Cell has two values separated by a "/"
PEN = x.Sheets(1).Range("E40").Value
'Putting data through predefined variables
y.Sheets(1).Range("A1").Value = RSS
y.Sheets(1).Range("B1").Value = RSE1_F
y.Sheets(1).Range("C1").Value = RSE1_B
y.Sheets(1).Range("D1").Value = RSE2_F
y.Sheets(1).Range("E1").Value = RSE2_B
y.Sheets(1).Range("F1").Value = TI
y.Sheets(1).Range("G1").Value = SI
y.Sheets(1).Range("H1").Value = FIBI(0)
y.Sheets(1).Range("I1").Value = FIBI(1)
y.Sheets(1).Range("J1").Value = PEN
x.Close
Application.ScreenUpdating = True
End Sub
As you can see it is completely handled by calling for specific cell locations and does not append any data to a table specifically. I have a hunch that I could define a range with each cell location and then loop through that range, appending each cell to the desired table location.
Any and all feedback is greatly appreciated. If any more info is needed I am more than happy to elaborate!
Thanks!
One option for collecting cell values from a non-contiguous range is by defining the whole range, copying into an array and pasting in your uniform output region:
Option Explicit
Sub General_Testing()
' > Var
Dim wsInput As Worksheet
Dim wsOutput As Worksheet
Dim RG As Range
Dim CL As Range
Dim RGarr
Dim I As Long
' > Change to your workbooks/Sheets
Set wsInput = ThisWorkbook.Worksheets(1)
Set wsOutput = ThisWorkbook.Worksheets(2)
' > Source Data range
Set RG = wsInput.Range("$F$6,$E$13:$F$14,$F$20:$F$21")
ReDim RGarr(1 To RG.Cells.Count)
' > Move into array
I = 1
For Each CL In RG.Cells
RGarr(I) = CL.Value
I = I + 1
Next CL
With wsOutput
' > Array to output range
.Range("A1").Resize(1, UBound(RGarr)) = RGarr
' > last couple oddball values
.Range("H1:I1").Value = Split(wsInput.Range("F36"), "/")
.Range("J1").Value = wsInput.Range("F40").Value
End With
End Sub
If you want, you could easily do the whole thing including your split cell in the one array, just check for delimiter and increment I twice.
This is what is looks like:
Input:
Output:
Method 2:
Option Explicit
Sub General_Testing()
' > Var
Dim wsInput As Worksheet
Dim wsOutput As Worksheet
Dim RG As Range
Dim CL As Range
Dim RGarr
Dim I As Long
' > Change to your workbooks/Sheets
Set wsInput = ThisWorkbook.Worksheets(1)
Set wsOutput = ThisWorkbook.Worksheets(2)
' > Source Data range
Set RG = wsInput.Range("$F$6,$E$13:$F$14,$F$20:$F$21,$F$36,$E$40")
ReDim RGarr(1 To RG.Cells.Count)
' > Move into array
I = 1
For Each CL In RG.Cells
If InStr(1, CL.Value, "/") > 0 Then
' > String must be split
ReDim Preserve RGarr(1 To UBound(RGarr) + 1)
RGarr(I) = Split(CL.Value, "/")(0)
I = I + 1
RGarr(I) = Split(CL.Value, "/")(1)
I = I + 1
Else
' > String must not be split
RGarr(I) = CL.Value
I = I + 1
End If
Next CL
With wsOutput
' > Array to output range
.Range("A1").Resize(1, UBound(RGarr)) = RGarr
End With
End Sub
This question already has an answer here:
Can we put dictionary items(array) into a Range with a single statement?
(1 answer)
Closed 6 months ago.
At the moment I have a range of names, and I need to create a new column which only contains the unique names.
Sub Unique_Values()
mySheet = Sheets("Sheet1").Range("E9:I20")
With CreateObject("scripting.dictionary")
For Each cell In mySheet
a = .Item(cell)
Next
Range("D2").Value = Join(.keys, vbLf)
End With
End Sub
This code creates a dictionary and returns the list of unique names, but it's one long list (i've just inserted it into D2) but I need it to populate column D with the unique names, one name per cell. I can't quite figure out how to loop through the keys and put them into an individual cell
Please, try the next updated code:
Sub Unique_Values()
Dim MySheet As Worksheet, rng As Range, cell As Range
Set MySheet = Sheets("Sheet1")
Set rng = MySheet.Range("E9:I20")
With CreateObject("scripting.dictionary")
For Each cell In rng.cells
.item(cell.Value) = 1
Next
MySheet.Range("D2").Resize(.count, 1).Value2 = Application.Transpose(.Keys)
End With
End Sub
It is good to declare all necessary variable, naming them in a relevant way.
Then, dict.keys is a 1D array (not having rows) and to place it in a column, it needs to be transposed.
I only tried adapting your code as it is. To make it faster, the iterated range should be placed in an array and then all the array processing will be done in memory, resulting a faster result. Anyhow, for the range you show us (if this is the real one), processing should take less than a second...
In fact, the faster version is easy to be designed, so here it is:
Sub Unique_Values_Array()
Dim MySheet As Worksheet, arr, i As Long, j As Long
Set MySheet = Sheets("Sheet1")
arr = MySheet.Range("E9:I20").Value2
With CreateObject("scripting.dictionary")
For i = 1 To UBound(arr)
For j = 1 To UBound(arr, 2)
.item(arr(i, j)) = 1
Next j
Next i
MySheet.Range("D2").Resize(.count, 1).Value2 = Application.Transpose(.Keys)
End With
End Sub
It makes sense and speed difference only in case of larger ranges...
If you use a collection you can create a unique list and write to the range. A collection will not let you add the same index key twice, therefore we ignore the error and then resume error checking when done writing.
Sub test()
Dim myNames As New Collection
Dim mySheet As Range
Dim i As Long
Set mySheet = Sheets("Sheet1").Range("E9:I20")
On Error Resume Next
For Each cell In mySheet
myNames.Add cell, cell.Value
Next
On Error GoTo 0
For i = 1 To myNames.Count
Worksheets("Sheet1").Cells(i + 2, 4) = myNames(i)
Next
End Sub
So i have an excel file where i can enter the different projects that i want to analyse and the location of the files. Then a code to go and get the files and generate 2 sheets (from a template that i created) for each project entered and populate the data. These projects can vary the name and quantity.
My problem appears when i try to do a total table. Where i would go and get from the same cell in the different sheets the value and sum them. The number of sheets can change so i didnt manage to use a sheets.count, but the name of the sheets that relevant for this operation all start by "Total_".
So the beginning of the code that i have so far is:
`Sub refresh()
Parametre
Dim nbOnglet As Integer
Dim nbProjet As Integer
Dim name As String
Dim nametot As String
Dim A As String
Dim B As String
Dim idx As Integer
Dim iDebut As Integer
Dim values As Variant
Dim rng As Range
Dim xRng As Range
Dim x As Long
Dim vArray As Variant
Dim dSum As Double
Initialisation
iDebut = 9
Déterminer le nombre d'onglets du Classeur
nbOnglet = Sheets.Count
Déterminer le nombre de projet à traiter
folderpath = Range("C3").Value
Sheets("Sommaire").Select
nbLigne = Cells(10, "A").Value
x = 0
For idx = 1 To nbLigne
activate Récapitulatif
Sheets("Récapitulatif").Select
Define the variable name - tab name
A = "Total_"
B = Sheets("Sommaire").Cells(iDebut + idx, "D").Value
name = B
nametot = A & B`
Then for the sun i have tried different options but none appears to work for the entire table. I managed to get the good result for one cell by using the following:
x = x + sheets(nametot).range("F7").Value2
But couldn't do it for all the range (F7:CI31).
Other formula that i have tried was:
Set xRng = ThisWorkbook.Sheets(nbLigne).Range("K7:CI31")
xRng.FormulaR1C1 = "=SUM('" & ThisWorkbook.Sheets(nametot).name & "'!RC+'" & ThisWorkbook.Sheets(nametot).name & "'!RC)"
Although this gives the equation that i want since it is running in a loop, it calculates the same for each sheet and stops at the last one identified... So not doing what i would like to: sum the same cell across the different sheets named 'Total_XXXX' and present the value in the 'Récapitulatif' sheet.
I have been looking around internet and i can't really figure out a way to do it.
DO you have any ideas?
Thank you so much in advance
example of the table
Consolidate Worksheets
Links (Microsoft Docs)
Range.Address
Range.Consolidate
XlConsolidationFunction Enumeration
Description
In the workbook containing this code (ThisWorkbook), the following will consolidate (in this case sum up (xlSum)) all the same ranges (srcRange) in all worksheets with names starting with a specified string (srcLead) into another worksheet (tgtName) starting from a specified cell (tgtFirst).
The Code
Option Explicit
Sub consolidateWorksheets()
' Define constants.
Const srcRange As String = "K7:CI31"
Const srcLead As String = "Total_"
Const tgtName As String = "Récapitulatif"
Const tgtFirst As String = "A1"
' Define workbook.
Dim wb As Workbook
Set wb = ThisWorkbook
' Define Target Worksheet.
Dim tgt As Worksheet
Set tgt = wb.Worksheets(tgtName)
' Define R1C1-Style Source Ranges Address.
Dim rcRng As String
rcRng = tgt.Range(srcRange).Address(ReferenceStyle:=xlR1C1)
' Define Consolidation Array.
Dim Data As Variant
ReDim Data(1 To wb.Worksheets.Count)
' Declare variables.
Dim ws As Worksheet ' Current Source Worksheet
Dim CurrentName As String ' Current Source Worksheet Name
Dim n As Long ' Current Element in Consolidation Array
' Write full paths of Source Worksheets to Consolidation Array.
For Each ws In wb.Worksheets
CurrentName = ws.Name
If InStr(1, CurrentName, srcLead, vbTextCompare) = 1 Then
n = n + 1
Data(n) = "'" & ws.Name & "'!" & rcRng
End If
Next ws
' Validate and resize Consolidation Array.
If n = 0 Then
MsgBox "No worksheets to consolidate.", vbCritical, "Fail"
Exit Sub
End If
ReDim Preserve Data(1 To n)
' Consolidate.
tgt.Range(tgtFirst).Consolidate Sources:=Data, _
Function:=xlSum
' Inform user.
MsgBox "Data consolidated.", vbInformation, "Success"
End Sub
I am having 2 issues that I've been trying to solve all day. First off whenever I try to watch any variable no matter what it says in the watches bar. I tried even just setting a variable to equal a number and watching it and it still gave me .
Second I am trying to put all of the values in column B that have a value into an array (TagName) and it is driving me up a wall. This is the point of the for loop. The out of context thing is not helping the case.
Just for reference "ist" was i as a string but then I added the B just to shorten the code.
Don't worry about the extra dims those are for code that is already working
Thank you for your help!
Sub GenTags()
Dim FolderPath As String
Dim OutputFileNum As Integer
Dim TagName(100) As String
Dim i As Long
Dim ist As String
Sheets("Parameters").Activate
For i = 1 To ActiveWorkbook.ActiveSheet.Columns("B").End(xlDown).Row
ist = "B" & CStr(i)
TagName(i) = ActiveWorkbook.Sheets("Parameters").Range(ist)
Next
End Sub
If you only want cells with values, you should probably have that as part of your loop. I think this should work. I also changed the array to be a variant in case you have a mix of string and numbers.
Sub GenTags()
Dim FolderPath As String
Dim OutputFileNum As Integer
Dim TagName(100) As Variant
Dim i As Long, c As Long
Dim ist As String
Sheets("Parameters").Activate
For i = 1 To ActiveWorkbook.ActiveSheet.Columns("B").End(xlDown).Row
If Not IsEmpty(Range("B" & i)) Then
TagName(c) = Range("B" & i).Value
c = c + 1
End If
Next
End Sub
This approach is a little more granular and takes care of empty cells in the column.
It's easy just yo customize the ">>>>" section
Sub GenTags()
Dim FolderPath As String
Dim OutputFileNum As Integer
Dim ist As String
' Define object variables
Dim sourceSheet As Worksheet
Dim paramSheet As Worksheet
Dim sourceRange As Range
Dim cellEval As Range
' Define other variables
Dim sourceSheetName As String
Dim paramSheetName As String
Dim sourceColumn As String
Dim tagName() As Variant
Dim counter As Long ' before i
Dim nonBlankCounter As Long
Dim totalCells As Long
' >>> Customize to fit your needs
sourceSheetName = "Sheet1"
paramSheetName = "Parameters"
sourceColumn = "B"
' Initialize objects - Change sheets names
Set sourceSheet = ThisWorkbook.Worksheets(sourceSheetName)
Set paramSheet = ThisWorkbook.Worksheets(paramSheetName)
Set sourceRange = Application.Union(sourceSheet.Columns(sourceColumn).SpecialCells(xlCellTypeConstants), sourceSheet.Columns(sourceColumn).SpecialCells(xlCellTypeFormulas))
' Get how many items in column b are
totalCells = sourceRange.Cells.Count
' Redimension the array to include all the items
ReDim tagName(totalCells)
' Initilize the counter (for documentation sake)
counter = 0
For Each cellEval In sourceRange
' Add non empty values
If Trim(cellEval.Value) <> vbNullString Then
' Store it in the array
tagName(counter) = cellEval.Value
counter = counter + 1
End If
Next cellEval
' Redim to leave only used items
ReDim Preserve tagName(counter - 1)
End Sub
Let me know if it helps!
Thank you for your responses. Unfortunatley I had to put this project on the back burner until yesterday but I did try both answers and they didn't work. I decided to go a different direction with the entire code and got it working. Thank you for your help and sorry for the late response.
In Excel I have a column of words. I believe you call words "strings" in the programming world.
Row by row, I need to take each word in the column and put single inverted commas around it.
For example, if the word in the cell is dog, I need to change it to 'dog'.
I am trying to write a macro to do this, but I am already running into problems with the very first part of the vba code, which is just to import the column of words into vba from the excel spreadsheet.
My code is below. The Error message says "subscript out of range", but as you can see I have dimmed the array. What am I doing wrong? Thanks.
Sub putquotes()
Dim sym(1 To 162) As String
For i = 1 To 162
sym(i) = Worksheets("sheet1").Cells(i + 1, 1)
Next i
End Sub
I think your issue is your sheet1 name which should probably be Sheet1
I would use something like this which will run on the first worksheet (see Set ws = Sheets(1))
Note that the third sheet would be Set ws = Sheets(3), or you could use Set ws = Sheets("Sheet1") if you did have such a sheet
This code:
will run independent of the sheet that is selected
looks from the first to last used cell in column A (rather than hard-coding 162 rows)
uses variant arrays rather than ranges for speed
adds a double '' to ensure the first is visible :)
Sub PutQuotes()
Dim ws As Worksheet
Dim varList
Dim rng1 As Range
Dim lngCnt As Long
Set ws = Sheets(1)
Set rng1 = ws.Range(ws.[a1], ws.Cells(Rows.Count, "A").End(xlUp))
varList = rng1.Value2
For lngCnt = 1 To UBound(varList)
If Len(varList(lngCnt, 1)) > 0 Then _
varList(lngCnt, 1) = "''" & varList(lngCnt, 1) & "'"
Next
'dump updated array back over range
rng1.Value2 = varList
End Sub
You don't have a sheet named "Sheet1". Either:
This code lives in a standard module in the workbook with the data and you've renamed the
sheet, or
The code lives in another workbook and you haven't properly qualified your Worksheets property
I'm going to assume the latter. When you use collection properties like Worksheets or Cells, Excel makes assumptions on who the parent is. An unqualified Worksheets call in a standard module will assume
ActiveWorkbook.Worksheets()
An unqualified Worksheets call in the ThisWorkbook module will assume
ThisWorkbook.Worksheets()
To check where the problem is, add this line to your code
Debug.Print Worksheets("Sheet1").Parent.Name
That will tell you which workbook Excel is using and may be different than you want.
To avoid bad guessing, it's best to fully qualify your references. For instance, if you're opening the workbook with the data, it might look like this
Sub putquotes()
Dim wb As Workbook
Dim sym(1 To 162) As String
Dim i As Long
Set wb = Workbooks.Open("Path\Name")
For i = 1 To 162
sym(i) = wb.Sheets("Sheet1").Cells(i + 1, 1)
Next i
End Sub
Holding that wb reference is an easy way to qualify the reference. If you're not opening a separate file in code, you can just qualify explicitly like
ThisWorkbook.Worksheets("Sheet1")
ActiveWorkbook.Worksheets("Sheet1")
Workbooks("Mybook.xlsx").Worksheets("Sheet1")
A better way to read cell values into an array is like this
Sub putquotes()
Dim wb As Workbook
Dim sym As Variant
Dim i As Long
Set wb = Workbooks.Open("Path\Name")
sym = wb.Sheets("Sheet1").Range("A2").Resize(162, 1).Value
For i = LBound(sym, 1) To UBound(sym, 1)
Debug.Print "'" & sym(i, 1) & "'"
Next i
End Sub
That will give you a two-dimensional-base-1 array, which you may not like, but it's faster than reading them in one at a time.
I believe you want something like this...
Public Sub DoQuotes()
Dim iRow As Integer
Dim Result() As String
iRow = 1
Do While Not IsEmpty(Sheet1.Cells(iRow, 1))
ReDim Preserve Result(iRow - 1)
Result(iRow - 1) = "'" & Sheet1.Cells(iRow, 1) & "'"
iRow = iRow + 1
Loop
For Each x In Result
MsgBox (x)
Next x
End Sub
However, bear in mind that Excel will treat the first quote as a text delimiter so it whilst the value in the array is 'something' it will look like something' in Excel.
Just a general aside point, try to avoid calls to Worksheets() instead use the strongly typed Sheet1 object - saves all sorts of future pain if the worksheets get renamed. You can see what the sheets are "really" called in the vba editor. It will say something like Sheet1(MyWorksheet)