I want to select a array of sheets using the Sheets(Array()) method.
The sheets I want to select are named in the cells of my workheet Printlist.
The sheetnames are listed form column D to K.
Not all cells are filled so if I use the folowing function it errors on the rows with blank cells. How can I avoid this error:
This is what the sheet looks like:
And this is the code
Sub PDF_maken()
Dim ws As Worksheet
Dim LR As Long
Dim r As Range
Dim Mypath As String
Dim strarray As String
Set ws = ActiveWorkbook.Worksheets("Printlijst")
LR = ws.Cells(Rows.Count, 1).End(xlUp).Row
For Each r In ws.Range("B20:B20").Cells
If Not IsEmpty("B" & r.Row) Then
Mypath = ws.Range("B" & r.Row).Text
colCheck = 4
Do Until Cells(r.Row, colCheck) = ""
strarray = strarray & IIf(colCheck > 4, ",") & """" & Cells(r.Row, colCheck).Value & """"
colCheck = colCheck + 1
Loop
ActiveWorkbook.Sheets(strarray).Select
ActiveWorkbook.SelectedSheets.ExportAsFixedFormat _
Type:=xlTypePDF, _
Filename:=Mypath & ws.Range("C" & r.Row).Text & ".pdf", _
Quality:=xlQualityStandard, IncludeDocProperties:=True, _
IgnorePrintAreas:=False, OpenAfterPublish:=False
End If
Next r
End Sub
You can use a regular array rather than the Array() function to create the array. Then you can loop through the cells that contains sheet names and only add them if they're not blank. Here's an example.
Sub PDF_maken()
Dim ws As Worksheet
Dim lLastRow As Long
Dim rMap As Range
Dim sPath As String
Dim aSheets() As String
Dim lShCnt As Long
Dim rSh As Range
Set ws = ActiveWorkbook.Worksheets("Printlist")
lLastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
For Each rMap In ws.Range("B2:B" & lLastRow).Cells
'Make sure there's a path
If Not IsEmpty(rMap.Value) Then
sPath = ws.Range("B" & rMap.Row).Text
're-dimension an array to hold all the sheet names
ReDim aSheets(1 To Application.WorksheetFunction.CountA(rMap.Offset(, 2).Resize(1, 8)))
'reset the counter
lShCnt = 0
'loop through all the cells that might have a sheet name
'and add them to the array
For Each rSh In rMap.Offset(, 2).Resize(1, 8).Cells
If Not IsEmpty(rSh.Value) Then
lShCnt = lShCnt + 1
aSheets(lShCnt) = rSh.Text
End If
Next rSh
ActiveWorkbook.Sheets(aSheets).Select
ActiveSheet.ExportAsFixedFormat xlTypePDF, sPath & rMap.Offset(0, 1).Text & ".pdf"
End If
Next rMap
ws.Select
End Sub
If you get Error 9: Subscript Out of Range there are three things to check:
The first one is that you spelled a sheet name wrong. Make sure there are no spaces or other funny business that makes it look like you have a good sheet name and you don't.
Next, make sure you qualify all of your references back to the workbook level. Depending on where your code is, unqualified references can point to different places. Don't ever use Sheets(). Always use ThisWorkbook.Sheets() or some other workbook reference. That will make sure you're not trying to access a sheet in a workbook that you didn't intend to.
Finally, you can get that error if you pass numbers to Sheets because your sheet names are numbers. Or rather they look like numbers, but they're really text. sheets(array(1234,4567)).select is different than sheets(array("1234","4567")).select. You have to pass strings to Sheets or you'll get that error. Kind of. You can pass numbers, but it will Select the sheets based on their index numbers rather than their names. That's why you have to be extra careful when your sheet names look like numbers.
Do a similar loop,
something like
colCheck=4
do until cells(r.row,colCheck)=""
strArray=strarray & iif(colCheck>4,",","") & cells(r.row,colCheck).value
colCheck=colCheck+1
loop
then you'll get something like a,b,c I've not tested this, so may need some tweaking. I'll revisit in a moment.
Related
I'm trying to create a single PDF file containing a sheet for each tab which I have listed from cell J2 in my Control sheet but I keep getting a Subscript Out Of Range error.
When I record the action I see that it creates an array of sheet names which it then selects to export, so I have a For loop which goes through the list and creates an array which adds to itself until it reaches the end of the list - the aim being to create one long string which I then select as an array.
All appears to be good (the variable PDFArray displays a string of the tab names in what appears to be the correct format) but when I get to the line 'Worksheets(Array(PDFarray)).Select' then I get the error. I've made sure the sheet names contain no undesirable characters or spaces but still no joy. Any help would be very much appreciated. Thank you
Sub B_PDFs()
Dim PDFarray As String, PDFName as String, sht As String
Sheets("Control").Select
PLFile = ActiveWorkbook.Name
PDFLoc = Application.ActiveWorkbook.Path & "\"
PDFName = Range("A20")
PDFSheetCount = Range("J1").Offset(Rows.Count - 1, 0).End(xlUp).Row
'Loop through column J and create a string with each tab name to be exported
For x = 2 To PDFSheetCount Step 1
If x = PDFSheetCount Then
sht = """ " & "" & Cells(x, 10) & """ "
Else
sht = """" & "" & Cells(x, 10) & """" & ", "
End If
PDFarray = PDFarray & sht
Next x
'Create PDF from the array above
Worksheets(Array(PDFarray)).Select - this is where I get the error Subscript Out Of Range
Selection.ExportAsFixedFormat Type:=xlTypePDF, Filename:=PFDLoc & PDFName, Quality:= _
xlQualityStandard, IncludeDocProperties:=True, IgnorePrintAreas:=False,
OpenAfterPublish:=False
Workbooks(PLFile).Activate
End Sub
I don't understand why MS makes NOT requiring variable declaration the default. Select Tools/Options/Editor and check Require Variable Declaration. This will place Option Explicit at the start of any new module. To correct this module, enter it manually at the beginning.
Doing so would have enabled you to find and correct a typo in your code.
You should also be avoiding Select, Selection and Activate. They rarely serve any purpose at all, and can cause multiple problems because they lull into avoiding explicit declarations of which workbook, worksheet, etc. you need. See How to avoid using Select in Excel VBA
However in using the ExportAsFixedFormat method to export selected worksheets, it seems Selection and ActiveSheet are required for it to work.
Array(str_variable) returns an array with a single entry that contains the entire string variable. It does not interpret the string variable so as to split it into separate elements.
So, rewriting your code somewhat (I will leave it to you to clean up the PDF document):
Option Explicit
Sub B_PDFs()
Dim PDFarray As Variant, PDFName As String, PLFile As String, PDFLoc As String
Dim wsControl As Worksheet
Dim WB As Workbook
'Consider wheter you want to use ThisWorkbook or a specific workbook
Set WB = ThisWorkbook
With WB
Set wsControl = .Worksheets("Control")
PLFile = .Name
PDFLoc = .Path & "\"
End With
With wsControl
PDFName = .Range("A20")
'create PDFarray
'This will be a 1-based 2D array starting at J1
'If you need to start at J2, alter the initial cell
PDFarray = .Range(.Cells(1, 10), .Cells(.Rows.Count, 10).End(xlUp))
End With
'convert to a 1D array
PDFarray = WorksheetFunction.Transpose(PDFarray)
'Note the use of `Select` and `ActiveSheet` when using this `ExportAsFixedFormat` method
Worksheets(PDFarray).Select
'Create PDF from the array above
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, Filename:=PDFLoc & PDFName, Quality:= _
xlQualityStandard, IncludeDocProperties:=True, IgnorePrintAreas:=False, OpenAfterPublish:=False
End Sub
What #RonRosenfeld has suggested is correct about select and selection. The expression you are building is string whereas, Excel expects it to be real array.
So in principle an approach like below shall work for you which will create an array for processing and can be used as you want to utilise.
Dim shtNames As Variant
Dim pdfArray
shtNames = Range("J2:J" & Range("J1").Offset(Rows.Count - 1, 0).End(xlUp).Row).Value
pdfArray = Application.Transpose(shtNames)
Disclaimer - I used to write macros a lot, but it's a perishable skill. 5 years takes it toll.
The basic concept is this:
I have a template workbook, with up to 30 tabs, that all have indeterminate rows and columns (i.e. It's not always A7:J30 - one tab might have 3 columns, the next 34 columns. Rows are also indeterminate.).
Currently, someone is copy/pasting 30 separate CSVs into this one templated workbook.
This templated workbook is read by another program to populate data. Row 6 of each template sheet is where the other program looks for headers (i.e. I might copy a CSV's data from A2:G1000, but it would need to paste in A7:G1005 of the template target workbook).
All of the CSVs are stored in the same directory. We can copy/paste a Template workbook into that directory, run a macro, and be done.
What I've done so far:
Sub V1BruteForceCopy()
'
'This code lives in ImportTemplate.XLSM, and is run from the same
'
Workbooks.Open (ThisWorkbook.Path & "\deposits.csv") 'Open deposits.CSV in same directory
Range("A2:G1000000").Copy 'Very inflexible copy job - ugly.
Windows("ImportTemplate.xlsm").Activate 'hate to Activate, but can't get it to work without it.
Sheets("depositbatches").Range("A7").Select 'must call each Sheet in the code, instead of declare variable
ActiveSheet.Paste 'don't like Activate, but Sheets("depositbatches").Range("A7").Paste throws an error.
End Sub 'to add a new CSV and a new Sheet to copy to, I have to copy a whole new block of code and then overwrite Sheets("name") and Workbooks.Open(ThisWorkook.Path & "\name.csv") every time.
Other things I've tried:
Sub rangecopy_001()
Dim ImpTemp As Workbook 'Reserved for ImportTemplate
Dim CSVdeposits As Workbook 'Reserved for deposits.CSV
Dim shDeposits As Worksheet 'Deposits worksheet inside ImportTemplate
Dim lRow As Long 'variable for last row
Dim lCol As Long 'variable for last column
Dim test As Range 'variable for copy/paste range
Set ImpTemp = Workbooks.Open(ThisWorkbook.Path & "\ImportTemplate_CSV.xlsm") 'Open DWImportTemplate
Set CSVdeposits = Workbooks.Open(ThisWorkbook.Path & "\deposits.csv") 'Open deposits.CSV
Set shDeposits = ImpTemp.Sheets("depositbatches") 'Declare that shDeposits is a ImportTemplate sheet
With CSVdeposits 'copy out of deposits.CSV and paste into ImportTemplate deposits sheet
'find last row - makes this dynamic
lRow = Cells.Find(What:="*", _
After:=Range("A1"), _
LookAt:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
'find last column - makes this dynamic
lCol = Cells(1, Columns.Count).End(xlToLeft).Column
test = CSVdeposits.Sheet(1).Range("A2:" & Cells(lRow, lCol)) 'error code 438 - Object doesn't support method
DW.shDeposits.Range("A7") = test
End With
End Sub
This makes the copy range dynamic, but I'm still getting an object error when I try to select the range. I got this method from (Copy from one workbook and paste into another) but it's too simple. Plus, if I want to add another 20 tabs, I have to copy/paste this code block another 20 times and change the variables each time.
I found this (Copy multiple rows from multiple workbooks to one master workbook), but Ron DeBruin's thing won't work because we have to move everything down to Row 6, plus we can't count on the headers of the CSVs working properly.
I like the last answer here (Dynamic range of data to paste into another sheet?) but I can't seem to make it work for a single workbook target from other workbooks.
I want to use an array, or set of arrays to declare my worksheets, but I don't know how to iterate over two arrays at one time that are string-based. I'm thinking something like this, but I'm not done:
Sub ArrayCopyV1()
'
'This code lives in Template.XLSM and is run from the same. Copy this book to the directory you wish to copy from.
'
'
Dim ArraySheets As Variant 'an array with all Sheet names. Should have the same number of args as ArrayCSVs array.
Dim ArrayCSVs As Variant 'an array with all CSV names Should have the same number of args as ArraySheets array.
Dim template As Worksheet 'variable for template worksheet inside
Template workbook
Dim CSV As Workbook 'variable for CSV workbook
Dim i As Integer 'variable i to be used in FOR loop counter
Dim lcol as Integer
Dim lrow as Integer
ArraySheets = Array("depositbatches", "otherSheet1", "OtherSheet2")
ArrayCSVs = Array("\deposits.csv", "other1.csv", "Other2.csv")
For i = LBound(ArraySheets) To UBound(ArraySheets)
Set CSV = Workbooks.Open(ThisWorkbook.Path & ArrayCSVs(i))
Set template = Workbooks.Open(ThisWorkbook.Path & ArraySheets(i))
With CSV 'copy out of deposits.CSV and paste into DWImportTemplate deposits sheet
'find last row - makes this dynamic
lRow = Cells.Find(What:="*", _
After:=Range("A1"), _
LookAt:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
'find last column - makes this dynamic
lCol = Cells(1, Columns.Count).End(xlToLeft).Column
test = CSV.Sheet(1).Range("A2:" & Cells(lRow, lCol))
template.Range("A7") = test
End With
Next i
End Sub
For example:
Sub CopyAll()
Dim rw As Range, wb As Workbook
'read over your file<>sheet table
For Each rw In ThisWorkbook.Sheets("Files").Range("A2:B30").Rows
Set wb = Workbooks.Open(ThisWorkbook.Path & "\" & rw.Cells(1).Value) '<< csv file name
With wb.Sheets(1).Range("A1").CurrentRegion
'skip headers (relies on contiguous data)
.Offset(1, 0).Resize(.Rows.Count - 1, .Columns.Count).Copy _
ThisWorkbook.Sheets(rw.Cells(2).Value).Range("A7") '<< sheet name to paste into
End With
wb.Close False
Next rw
End Sub
I have the below loop in VBA:
For i = 1 To Range("B" & "65536").End(xlUp).Row Step 1
Companies = Range("A" & i).Value
Next i
MsgBox Companies 'Output Company Name (One time)
So above loop iterates through rows, that all have a company name in Column "A". I want to add all these company names to an array, so I can print them all out later on (after the loop)
How can I dynamically add the Companies value to an array, and use it later on?
you don't need Loop
Just try this :
Dim DirArray As Variant
DirArray = Range("A1:A5000").Value
I think something like this is what you're looking for.
Sub tgr()
'Declare variables
Dim ws As Worksheet
Dim Companies As Variant
Dim i As Long
'Always fully qualify which workbook and worksheet you're looking at
Set ws = ActiveWorkbook.ActiveSheet
'You can assing a Variant variable to the value of a range
' and it will populate the variable as an array if there
' is more than one cell in the range
'Note that I am going off of column B as shown in your original code,
' and then using Offset(, -1) to get the values of column A
Companies = ws.Range("B1", ws.Cells(ws.Rows.Count, "B").End(xlUp)).Offset(, -1).Value
If IsArray(Companies) Then
'More than one company found, loop through them
For i = LBound(Companies, 1) To UBound(Companies, 1)
MsgBox "Company " & i & ":" & Chr(10) & _
Companies(i, 1)
Next i
Else
'Only one company found
MsgBox Companies
End If
End Sub
If you need an array, which is increased every time and still saves its contents, something like this should work:
Option Explicit
Public Sub TestMe()
Dim i As Long
Dim companies() As Variant
ReDim companies(0)
For i = 1 To 20
ReDim Preserve companies(UBound(companies) + 1)
companies(UBound(companies)) = Range("A" & i)
Next i
End Sub
If you need simply need to take the values to array, then the answer of #Leo R. is probably the easiest way to achieve it.
I need to get the Account Numbers into an array which is indicated as 1 on the column select. Results expected is - {FD_002_17,FD_004_17}. I am planning to use this in a Name Range.
Table of interest to subset
I tried using
=INDEX(B2:B6,MATCH(1,A2:A6),1)
But this fails as Match does not return an array.
Using the post (https://stackoverflow.com/a/6755513/4050510) in the SO question that Hugs referred to i came up with the following formula for your need.
Its a array formula that you enter into your first cell, and then fill it downwards using the little handle in the corner of the selected cell.
=IFERROR(INDEX($B$2:$B$6;SMALL(IF($A$2:$A$6=1;ROW($B$2:$B$6)-MIN(ROW($B$2:$B$6))+1;"");ROW(A1)));"")
It is possible to do this. You can assign the named range to a formula such as :
=INDEX(Sheet1!$B:$B, N(IF({1}, MODE.MULT(IF(Sheet1!$A$2:$A$6=1, ROW(Sheet1!$A$2:$A$6)*{1,1})))))
Then you can reference your Named Range like: =INDEX(MyNamedRange, 2)
EDIT:
You can either set a hidden sheet to have a filtered list of the values in a range of cells, or else use VBA:
VBA:
Put this in the worksheet codemodule of the relevant work sheet:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Me.Range("A:A")) Is Nothing Then Exit Sub
Dim ARange As Range, BRange As Range
Dim i As Long, lastRow As Long, strCount As Long
lastRow = Me.Range("A" & Me.Rows.Count).End(xlUp).Row
Set ARange = Me.Range("A1:A" & lastRow)
Set BRange = Me.Range("B1:B" & lastRow)
Dim stringArr() As String
For i = 1 To lastRow
If ARange.Cells(i, 1).Value = 1 Then
ReDim Preserve stringArr(0 To strCount)
stringArr(strCount) = BRange.Cells(i, 1).Value
strCount = strCount + 1
End If
Next i
Dim str As String
str = Join(stringArr, ",")
Dim dv As Validation
Set dv = Me.Range("DVCell").Validation
If Not dv Is Nothing Then
dv.Modify _
Type:=xlValidateList, _
AlertStyle:=xlValidAlertStop, _
Operator:=xlBetween, _
Formula1:=str
Else
dv.Add _
Type:=xlValidateList, _
AlertStyle:=xlValidAlertStop, _
Operator:=xlBetween, _
Formula1:=str
End If
End Sub
To use formulas and a hidden sheet, use the techniques to fill a range of cells, and then assign that dynamic range to the data validation....
VBA arrays are new to me and it seems like there are multiple ways to create string arrays.
I believe I need to create a dynamic array
But I can't find any examples how to pass dynamic arrays to a subroutine
I know how many items there need to be in the array by the count of the User range (so maybe I don't need a dynamic array??). I'm having trouble passing the array through to another Subroutine.
The thought process is as follows:
Iterate through a list of user names
Create a sheet for each
Save each user name in an array as I iterate through
In another Subroutine, select all the sheets I created and save as a PDF
Below is my code. I'm getting Run-time error 9 - Subscript out of range (Referring to the array object)
I appreciate any help! Thank you!
Sub CreateAllDashboards(StartDate As Date, EndDate As Date)
'Used to iterate through the list of users and call the Sub to create Dashboards
Dim UserNameRangeStart As Range
Set UserNameRangeStart = Range("UserName")
Dim SheetNames() As String
'Cyle through users
For i = 1 To GetUserNameRange().CounT
'Some code
ReDim Preserve SheetNames(i)
SheetNames(i) = UserNameRangeStart.Offset(i, 0).Value
Next i
Call CreatePDF(EndDate, SheetNames) 'Also tried SheetNames()
End Sub
Sub CreatePDF(FileDate As Date, ByRef SheetNames As Variant)
Dim FilePath As String, FileName As String
FilePath = Application.ActiveWorkbook.Path
FileName = "Production Dashboards - " & Format(FileDate, "mmddyy") & ".pdf"
ThisWorkbook.Sheets(SheetNames).Select
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, FileName:= _
FileName, Quality:=xlQualityStandard, IncludeDocProperties:=True, _
IgnorePrintAreas:=False, OpenAfterPublish:=True
End Sub
The array parameter is not a problem it is passed correctly to method CreatePDF(...). The parameter type can be changed to SheetNames() As String but SheetNames As Variant works as well.
Then the Run-time error 9 - Subscript out of range is raised here ThisWorkbook.Sheets(SheetNames).Select because the array SheetNames contains invalid sheet name, which is the very first item. This item is an empty string and empty string is not valid as a sheet name.
In the For Next loop index starts with value 1 but the array starts with 0. So the very first item of the array SheetNames remains untouched and is finally an empty string. To solve it set the lower bound in ReDim explicitly to 1. HTH
(Note: if you omit lower bound then Option Base is used and if no Option Base is specified then 0 is used.)
'Cyle through users
For i = 1 To GetUserNameRange().Count
'Some code
ReDim Preserve SheetNames(1 To i)
SheetNames(i) = UserNameRangeStart.Offset(i, 0).value
Next i
I would change this:
Sub CreatePDF(FileDate As Date, ByRef SheetNames As Variant)
To this:
Sub CreatePDF(FileDate As Date, SheetNames() As String)
But your problem is at this line:
ThisWorkbook.Sheets(SheetNames).Select
Edited from dee's comment:
You can put an array of sheet names in .Sheets() but without empty rows. So in your sub "CreateAllDashboards" do this:
ReDim Preserve SheetNames(i - 1)
SheetNames(i - 1) = UserNameRangeStart.Offset(i, 0).Value
and you could read that about arrays in VBA.
I've tested the following using a single sheet workbook with a range named Users and another named FileDate. It does what you asked.
The reason for the Run-time error 9 - Subscript out of range error is that you have to reference the array element. ThisWorkbook.Sheets(SheetNames).Select will throw an error but ThisWorkbook.Sheets(SheetNames(x)).Select won't (as long as x is initialised and within the bounds of the array)
Sub PopulateArray()
Dim user As Range
Dim SheetNames As Variant
ReDim SheetNames(1 To 1) 'Initialise the array
For Each user In [Users]
ThisWorkbook.Sheets.Add After:=Worksheets(Worksheets.Count)
With Worksheets(Worksheets.Count)
.Name = user.Value2
.[A1] = user.Value2 'you can't print a blank sheet!
End With
SheetNames(UBound(SheetNames)) = user.Value2
ReDim Preserve SheetNames(1 To UBound(SheetNames) + 1)
Next user
ReDim Preserve SheetNames(1 To UBound(SheetNames) - 1) 'Delete the last element
Call CreatePDF([FileDate], SheetNames)
End Sub
Sub CreatePDF(FileDate As Date, ByRef SheetNames As Variant)
Dim FilePath As String, FileName As String
Dim x As Long
FilePath = Application.ActiveWorkbook.Path & "\" 'Note backslash added to path.
FileName = "Amtec Production Dashboards - " & Format(FileDate, "mmddyy")
For x = 1 To UBound(SheetNames)
ThisWorkbook.Sheets(SheetNames(x)).ExportAsFixedFormat Type:=xlTypePDF, FileName:= _
FileName & SheetNames(x) & ".pdf", Quality:=xlQualityStandard, IncludeDocProperties:=True, _
IgnorePrintAreas:=False, OpenAfterPublish:=True
Next x
End Sub
The above demonstrates how to parse an array to another sub as requested but you could integrate the CreatePDF code into the calling sub fairly easily too.