Receiving Error when trying to run macro on multiple sheets using VBA - excel

I have the below macro that should be running on each sheet in my workbook. When I run this code, I am getting the following error: 'A table cannot overlap another table' and it is highlighting this line:
ActiveSheet.ListObjects.Add(xlSrcRange, Range("$A:$I"), , xlYes).Name = _
"Table1"
Is this because I applied the macro to table one and now it cannot be applied to the other tables?
All sheets have the same column headers but different number of rows (not sure if that matters). Essentially all I am trying to do is get rid of the index, format the data into a table, extend the column lengths to fit all the column names, and rename the columns.
Another thing to note, there are about 170 sheets that this macro needs to run through.
Sub forEachWs()
Dim ws As Worksheet
For Each ws In ActiveWorkbook.Worksheets
Call CreateTables(ws)
Next
End Sub
Sub CreateTables(ws As Worksheet)
'
' CreateTables Macro
'
' Keyboard Shortcut: Ctrl+Shift+S
'
With ws
Columns("A:A").Select
Selection.Delete Shift:=xlToLeft
Columns("A:I").Select
Application.CutCopyMode = False
ActiveSheet.ListObjects.Add(xlSrcRange, Range("$A:$I"), , xlYes).Name = _
"Table1"
Columns("A:I").Select
ActiveSheet.ListObjects("Table1").TableStyle = "TableStyleLight1"
Columns("A:I").EntireColumn.AutoFit
Range("Table1[[#Headers],[Tier2_ID]]").Select
ActiveCell.FormulaR1C1 = "Community ID"
Range("Table1[[#Headers],[Tier2_Name]]").Select
ActiveCell.FormulaR1C1 = "Community Name"
Range("Table1[[#Headers],[Current_MBI]]").Select
ActiveCell.FormulaR1C1 = "Current MBI"
Range("Table1[[#Headers],[countMBI]]").Select
ActiveCell.FormulaR1C1 = "Cout"
Range("Table1[[#Headers],[Cout]]").Select
ActiveCell.FormulaR1C1 = "Count"
Range("Table1[[#Headers],[TotalEDVisits]]").Select
ActiveCell.FormulaR1C1 = "Total ED Visits"
Range("Table1[[#Headers],[EDtoIPTotal]]").Select
ActiveCell.FormulaR1C1 = "Total ED to Inpatient"
Range("Table1[[#Headers],[totalSev1to3]]").Select
ActiveCell.FormulaR1C1 = "Severity 1 to 3"
Range("Table1[[#Headers],[totalSev4to6]]").Select
ActiveCell.FormulaR1C1 = "Severity 4 to 6"
Range("Table1[[#Headers],[totalPaid]]").Select
ActiveCell.FormulaR1C1 = "Total Paid"
Range("L22").Select
End With
End Sub

Convert Ranges to Tables
The table names in a workbook have to be unique.
This code (re)names each table sequentially i.e. Table1, Table2, Table3....
This is a one-time operation code, so test it first on a copy of your workbook.
If (when) you're satisfied with the outcome, run it in your original workbook.
Now the code is no longer needed (useless).
If you really need to select the cell L22 on each worksheet, you have to make sure the workbook is active (in the first code use If Not wb Is ActiveWorkbook Then wb.Activate).
In the second code, you can then use Application.Goto ws.Range("L22") right before (above) the last 'Else.
Sub ConvertToTables()
' Reference the workbook ('wb').
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim ws As Worksheet
Dim n As Long
For Each ws In wb.Worksheets
n = n + 1 ' to create Table1, Table2, Table3...
ConvertToTable ws, "Table", n
Next
End Sub
Sub ConvertToTable( _
ByVal ws As Worksheet, _
ByVal TableBaseName As String, _
ByVal TableIndex As Long)
'
' CreateTables Macro
'
' Keyboard Shortcut: Ctrl+Shift+S
'
' Note that all column names have to be unique i.e. you cannot
' rename the 'countMBI' column to 'Cout' before the existing 'Cout' column
' has been renamed.
Const OldColsList As String _
= "Tier2_ID,Tier2_Name,Current_MBI,Cout," _
& "countMBI,TotalEDVisits,EDtoIPTotal,totalSev1to3," _
& "totalSev4to6,totalPaid"
Const NewColsList As String _
= "Community ID,Community Name,Current MBI,Count," _
& "Cout,Total ED Visits,Total ED to Inpatient,Severity 1 to 3," _
& "Severity 4 to 6,Total Paid"
Const FirstCellAddress As String = "A1"
' Reference the first cell ('fCell').
Dim fCell As Range: Set fCell = ws.Range(FirstCellAddress)
' Check if the first cell is part of a table ('tbl').
' A weak check whether the table has already been created.
Dim tbl As ListObject: Set tbl = fCell.ListObject
If tbl Is Nothing Then ' the first cell is not part of a table
' Reference the range ('rg').
Dim rg As Range: Set rg = fCell.CurrentRegion
' Delete the first column. Note that the range has shrinked by a column.
rg.Columns(1).Delete xlShiftToLeft
' Convert the range to a table ('tbl').
Set tbl = ws.ListObjects.Add(xlSrcRange, rg, , xlYes)
With tbl
.Name = TableBaseName & CStr(TableIndex)
.TableStyle = "TableStyleLight1"
' Write the lists to string arrays ('OldCols', 'NewCols')
Dim OldCols() As String: OldCols = Split(OldColsList, ",")
Dim NewCols() As String: NewCols = Split(NewColsList, ",")
Dim lc As ListColumn
Dim n As Long
' Loop through the elements of the arrays...
For n = 0 To UBound(OldCols)
' Attempt to reference a table column by its old name.
On Error Resume Next
Set lc = .ListColumns(OldCols(n))
On Error GoTo 0
' Check if the column reference has been created.
If Not lc Is Nothing Then ' the column exists
lc.Name = NewCols(n) ' rename the column
Set lc = Nothing ' reset to reuse in the next iteration
'Else ' the column doesn't exist; do nothing
End If
Next n
' The columns should be autofitted after their renaming.
.Range.EntireColumn.AutoFit
End With
'Else ' the first cell is part of a table; do nothing
End If
End Sub

Related

VLOOKUP Macro for AutoFiltered data

I'm still learning VBA and am wondering if there's a way to run a VLOOKUP in a filtered range.
For example, in the code below, after I filter the data, the first row with data is A4.
However, I have to manually specify that the first row of data is in A4.
My question is whether it's possible so the macro detects the first row of data itself instead of me having to specify.
I've read about potentially using SpecialCells.
I am trying to do this as the datasets I receive change daily, so the first filtered row being A4 today might be A15 or whatever tomorrow.
Thanks
Range("A4").Select '/have to specify range here
Dim formul As String
formul = "=VLOOKUP(C2,Sheet2!A:B,2,0)"
Range("A4:A" & Cells(Rows.Count, 1).End(xlUp).Row) = [formul] '/also specify range here
'''
edit: code with SpecialCells:
''' vba
Range("A1").Select '/have to specify range here
Dim formul As String
formul = "=VLOOKUP(C1,Sheet2!A:B,2,0)"
Range("A1:A" & Cells(Rows.Count, 1).End(xlUp).Row).SpecialCells(xlCellTypeVisible) = [formul] '/also specify range here
'''
Formula To Filtered Cells
This will filter column C and write formulas to the filtered cells in column A.
Option Explicit
Sub FormulaToFilteredCells()
Const sName As String = "Sheet2"
Const dName As String = "Sheet1"
Const dLookupColumn As Long = 1
Const dCriteriaColumn As Long = 3
Const dCriteria As String = "Yes"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
If dws.FilterMode Then dws.ShowAllData ' remove previous filter
Dim drg As Range ' Destination Table Range (has headers)
Set drg = dws.Range("A1").CurrentRegion.Columns(dCriteriaColumn)
Dim ddrg As Range ' Destination Data Range (no headers)
Set ddrg = drg.Resize(drg.Rows.Count - 1).Offset(1)
Dim dcOffset As Long: dcOffset = dLookupColumn - dCriteriaColumn
drg.AutoFilter 1, dCriteria
Dim dvdrg As Range ' Destination Visible Data Range
On Error Resume Next
Set dvdrg = ddrg.SpecialCells(xlCellTypeVisible).Offset(, dcOffset)
On Error GoTo 0
dws.AutoFilterMode = False
If dvdrg Is Nothing Then Exit Sub ' no filtered cells
dvdrg.Formula = "=VLOOKUP(" & dvdrg.Cells(1).Offset(, -dcOffset) _
.Address(0, 0) & ",'" & dName & "'!A:B,2,0)"
End Sub
Working with filtered data is possible with array formulas as shown here, here and here.
Why don't you copy the filtered data to a new Worksheet?
And work with vlookup on the filtered data in the 2nd Worksheet?
Sample Data: Wikipedia => List_of_countries_by_population
Sub FilterTable_and_Copy()
'Prepare Sheet2
If Sheets(2).Name <> "Filtered Data" Then
Sheets.Add After:=Sheets(1)
Sheets(2).Name = "Filtered Data"
End If
Sheets(2).Columns("A:G").ClearContents
'The Data is prepared in the Table "myTable"
'ActiveSheet.ListObjects.Add(xlSrcRange, Range("$A$1:$F$100"), , _
' xlYes).Name = "myTable"
'Filter Data
Sheets(1).Select
ActiveSheet.Range("myTable").AutoFilter Field:=2, Criteria1:="Asia"
'Copy Filtered Data to Sheet2
Range("myTable").Copy
Sheets(2).Select
Range("A2").Select
ActiveSheet.Paste
Application.CutCopyMode = False
'Copy Header
Sheets(1).Select
Rows("1:1").Copy
Sheets(2).Select
Range("A1").Select
ActiveSheet.Paste
Application.CutCopyMode = False
'Format Columns Width
Columns("A:F").ColumnWidth = 30
Columns("A:F").EntireColumn.AutoFit
Range("G1").Select
'Create Table "Table_FilteredData"
Sheets(2).ListObjects.Add(xlSrcRange, Range("A1").CurrentRegion, , _
xlYes).Name = "Table_FilteredData"
'Correct Formatting Issue
Dim myRange As Range
With Sheets(2).ListObjects("Table_FilteredData")
Set myRange = .Range
.Unlist
End With
With myRange
.Interior.ColorIndex = xlColorIndexNone
.Font.ColorIndex = xlColorIndexAutomatic
.Borders.LineStyle = xlLineStyleNone
End With
Sheets(2).ListObjects.Add(xlSrcRange, Range("A1").CurrentRegion, , _
xlYes).Name = "Table_FilteredData"
Sheets(2).ListObjects(1).TableStyle = "TableStyleMedium3"
End Sub
Data filtered for "region = Asia":

VBA Create table for each filter data in another sheet

I need to make a table for each unique value of a column. I used autofilter to select each filter to then copy and paste to another sheet. Due to the amount of data (large) i would like to automate and maybe do a for each cycle where each filter is select individually and copied to a differente sheet. It´s this even possible? Does anyone knows how to maybe simplify this problem ?
Option Explicit
Sub CreateTables()
Const COL_FILTER = 1 ' A
Const SHT_NAME = "Sheet1" ' data sheet
Dim wb As Workbook, ws As Worksheet
Dim rng As Range, iLastRow As Long, i As Long
Set wb = ThisWorkbook
Set ws = wb.Sheets(SHT_NAME)
' get list as unique values
Dim dict, key, ar
Set dict = CreateObject("Scripting.Dictionary")
iLastRow = ws.Cells(Rows.Count, COL_FILTER).End(xlUp).Row
ar = ws.Cells(1, COL_FILTER).Resize(iLastRow, 1)
For i = 2 To iLastRow
dict(ar(i, 1)) = 1
Next
' confirm
If MsgBox(dict.Count & " sheets will be created," & _
" continue ? ", vbYesNo) = vbNo Then
Exit Sub
End If
' apply autofilter in turn
' copy to new sheet
Set rng = ws.UsedRange
ws.AutoFilterMode = False
For Each key In dict
With wb.Sheets.Add(After:=wb.Sheets(wb.Sheets.Count))
.Name = CStr(key)
rng.AutoFilter COL_FILTER, CStr(key)
rng.SpecialCells(xlCellTypeVisible).Copy .Range("A1")
.ListObjects.Add(xlSrcRange, .UsedRange, , xlYes) _
.Name = "Table " & key
End With
MsgBox "Created sheet " & key
Next
MsgBox dict.Count & " sheets created"
End Sub

Create a table with a macro

I have this simple range:
I want to create a macro which creates a table from a range. The macro I get is this:
Sub Macro1()
'
' Macro1 Macro
'
'
Range(Selection, Selection.End(xlToRight)).Select
Range(Selection, Selection.End(xlDown)).Select
Application.CutCopyMode = False
ActiveSheet.ListObjects.Add(xlSrcRange, Range("$A$1:$B$3"), , xlYes).Name = _
"Table12"
ActiveCell.Range("Table12[#All]").Select
End Sub
The problem is that I want the macro to not be contingent on a specific size. I for instance want it to be able to work with this table as range aswell:
The problem in the code seems that it uses "$A$1:$B$3" but it should be independent of that. Is there a simple way to fix this? How can I record the macro so that it works for all tables?
The next piece of code will create a table ("MyTable") starting from the active cell to the adjacent most right column and to the down filled row of active cell column:
Sub TableRightDownOfSelection()
Dim rnG As Range
UnlistIt
Set rnG = Range(ActiveCell.Address & ":" & Cells(Cells(Rows.count, _
ActiveCell.Column).End(xlUp).Row, ActiveCell.End(xlToRight).Column).Address)
ActiveSheet.ListObjects.Add(xlSrcRange, rnG, , xlYes).Name = "MyTable"
End Sub
Sub UnlistIt()
On Error Resume Next
ActiveSheet.ListObjects("Table1").Unlist
If Err.Number > 0 Then Err.Clear
On Error GoTo 0
End Sub
In order to avoid an error message, in case you try the code again, the UnlistIt Sub will be called before creating the table...
You could use the currentregion property
Sub CreateTbl()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim rg As Range
Set rg = ws.Range("A1").CurrentRegion
ws.ListObjects.Add(xlSrcRange, rg, , xlYes).Name = "myTable"
End Sub
But be aware this code will create a table named like myTable_1 in case you already have a table namend myTable somewhere else and it will faill if you run it twice for the same range.
Addition: Based on the comments one could try to use the active cell
Sub CreateTbl()
Dim ws As Worksheet
Dim rg As Range
Set rg = ActiveCell.CurrentRegion
Set ws = rg.Parent
ws.ListObjects.Add(xlSrcRange, rg, , xlYes).Name = "myTable"
End Sub

hiding blank columns on multiple sheets

I want to hide the blank columns on multiple sheets. I can do it on just the active sheet but when I try to make it so it applies to all sheets with a month in the name it doesn't work. This is what I have so far:
Sub CommandButton1_Click()
Dim col As Range
Dim sheetsArray As Sheets
Set sheetsArray = ActiveWorkbook.Sheets(Array("*Jan*", "*Feb*", "*Mar*", "*Apr*", "*May*", "*Jun*", "*Jul*", "*Aug*", "*Sep*", "*Oct*", "*Nov*", "*Dec*"))
Dim sheet As Worksheet
Application.ScreenUpdating = False
For Each sheet In sheetsArray
sheet.Columns.Hidden = False
For Each col In sheet.UsedRange.Columns
col.Hidden = sheet.col.Cells(Rows.Count, 1).End(xlUp).Row = 1
Next col
Next sheet
Application.ScreenUpdating = True
End Sub
It's also now giving me a "Method or Data member not found error"
The Worksheet class does not have a method or data member named col. You can remove sheet. in front of col. Also, at the top of your module, add Option Explicit; then, before running your code, click the Debug menu and then Compile in order to catch such issues early on.
Other than that, you will have to check each sheet name against your name filters; the ActiveWorkbook.Sheets collection unfortunately won't magically interpret the filters in your array. In the end, you can go along those lines:
Option Explicit
Sub CommandButton1_Click()
Dim sheet As Worksheet
Dim col As Range
Dim sheetNameFilters As Variant
Dim filter As Variant
sheetNameFilters = Array("*Jan*", "*Feb*", "*Mar*", "*Apr*", "*May*", "*Jun*", "*Jul*", "*Aug*", "*Sep*", "*Oct*", "*Nov*", "*Dec*")
Application.ScreenUpdating = False
For Each sheet In ThisWorkbook.Worksheets
For Each filter In sheetNameFilters
If sheet.Name Like filter Then
sheet.Columns.Hidden = False
For Each col In sheet.UsedRange.Columns
col.Hidden = (col.Cells(Rows.Count, 1).End(xlUp).Row = 1)
Next
Exit For
End If
Next
Next
Application.ScreenUpdating = True
End Sub
I'm not sure if Array can perform the wild card search as you intended to. Like is a function that can be used as shown in the code below. Hope it meets your needs
Sub HideColumns()
Dim col As Range
Dim sheet As Worksheet
Application.ScreenUpdating = False
For Each sheet In ThisWorkbook.Worksheets
'check if worksheet name as month in it
If sheet.Name Like "*Jan*" Or sheet.Name Like "*Feb*" Or sheet.Name Like "*Mar*" Then 'add for rest of the months
sheet.Columns.Hidden = False 'make all columns visible
DoEvents
'reset the user range
sheet.UsedRange.Calculate 'if you are using usedrange recommend using this as sometimes usedrange behaves erratically
For Each col In sheet.UsedRange.Columns
'check if there are no entries and first row is also blank - make blank if both conditions are met
col.Hidden = IIf(col.Cells(1048576, 1).End(xlUp).Row = 1 And col.Cells(1, 1).Value = "", True, False)
DoEvents
Next col
End If
Next sheet
Application.ScreenUpdating = True
End Sub
Hide or Delete Blank Columns in Real Used Range
(Usually) Standard Module (Often 'Module1')
Option Explicit
'*******************************************************************************
' Purpose: Hides or deletes all blank columns in the Real Used Range
' of worksheets specified by a name pattern list.
' Remarks: The Real Used Range is calculated by using the Find method which
' avoids any possible 'errors' occuring when using the UsedRange
' property.
'*******************************************************************************
Sub HideDeleteColumnsOfRUR(Optional HideFalse_DeleteTrue As Boolean = False)
' Worksheet Name Pattern List
Const cSheets As String = "*Jan*,*Feb*,*Mar*,*Apr*,*May*,*Jun*,*Jul*," _
& "*Aug*,*Sep*,*Oct*,*Nov*,*Dec*"
' If a cell contains a formula that evaluates to "" and if cLookIn is
' equal to xlValues (-4163), it will not be found (Not blank).
Const cLookIn As Variant = -4123 ' -4163 Value, -4123 Formula, -4144 Comment
Dim ws As Worksheet ' (Current) Worksheet
Dim RUR As Range ' (Current) Real Used Range
Dim rngU As Range ' (Current) Union Range
Dim vntSheets As Variant ' Sheet Array
Dim i As Long ' Sheet Array Row Counter
Dim j As Long ' Used Range Column Counter
Application.ScreenUpdating = False
On Error GoTo ProcedureExit ' Enable ScreenUpdating if error occurs.
' Write Worksheet Name Pattern List to Sheet Array.
vntSheets = Split(cSheets, ",")
' Remove possible occurrences of leading and trailing spaces in
' Sheet Array.
'For i = 1 To UBound(vntSheets): vntSheets(i) = Trim(vntSheets(i)): Next
For Each ws In ThisWorkbook.Worksheets ' Loop through worksheets.
For i = 0 To UBound(vntSheets) ' Loop through Worksheet Name Patterns.
If ws.Name Like vntSheets(i) Then ' Worksheet Name Pattern found.
' Unhide all columns, calculate Real Used Range and Union Range.
GoSub RangeAccumulator
Exit For ' Stop checking for (Current) Worksheet Name Patterns.
End If
Next
Next
ProcedureExit:
Application.ScreenUpdating = True
Exit Sub
RangeAccumulator:
With ws
' Unhide all columns in (Current) Worksheet.
.Columns.Hidden = False
' Calculate Real Used Range.
If Not .Cells.Find("*", .Cells(.Rows.Count, .Columns _
.Count), -4123, , 1) Is Nothing Then ' Is not empty sheet.
Set RUR = .Range(.Cells(.Cells.Find("*", .Cells(.Rows.Count, _
.Columns.Count)).Row, .Cells.Find("*", .Cells(.Rows.Count, _
.Columns.Count), , , 2).Column), .Cells(.Cells _
.Find("*", , , , 1, 2).Row, .Cells.Find("*", , , , 2, 2) _
.Column))
Else ' Is empty sheet.
'MsgBox "Worksheet '" & ws.Name & "' is an empty sheet."
Return
End If
End With
' Accumulate Union Range using only Real Used Range's first-row cells (1).
With RUR
For j = 1 To .Columns.Count
If .Columns(j).Find("*", , cLookIn, , 2, 2) Is Nothing Then
If Not rngU Is Nothing Then
Set rngU = Union(rngU, .Cells(1, j))
Else
Set rngU = .Cells(1, j)
End If
End If
Next
End With
' Hide or Delete Union Range's columns.
If Not rngU Is Nothing Then
With rngU.EntireColumn
If Not HideFalse_DeleteTrue Then
.Hidden = True
Else
.Delete
End If
End With
Set rngU = Nothing
End If
Return
End Sub
'*******************************************************************************
'*******************************************************************************
' Purpose: Shows (unhides) all blank columns in worksheets specified by
' a name pattern list.
'*******************************************************************************
Sub ShowAllColumns()
' Worksheet Name Pattern List
Const cSheets As String = "*Jan*,*Feb*,*Mar*,*Apr*,*May*,*Jun*,*Jul*," _
& "*Aug*,*Sep*,*Oct*,*Nov*,*Dec*"
Dim ws As Worksheet ' (Current) Worksheet
Dim vntSheets As Variant ' Sheet Array
Dim i As Long ' Sheet Array Row Counter
Application.ScreenUpdating = False
On Error GoTo ProcedureExit ' Enable ScreenUpdating if error occurs.
' Write Worksheet Name Pattern List to Sheet Array.
vntSheets = Split(cSheets, ",")
' Remove possible occurrences of leading and trailing spaces in
' Sheet Array.
'For i = 1 To UBound(vntSheets): vntSheets(i) = Trim(vntSheets(i)): Next
For Each ws In ThisWorkbook.Worksheets ' Loop through worksheets.
For i = 0 To UBound(vntSheets) ' Loop through Worksheet Name Patterns.
If ws.Name Like vntSheets(i) Then ' Worksheet Name Pattern found.
' Unhide all columns in (Current) Worksheet.
ws.Columns.Hidden = False
Exit For ' Stop checking for (Current) Worksheet Name Patterns.
End If
Next
Next
ProcedureExit:
Application.ScreenUpdating = True
End Sub
'*******************************************************************************
(Usually) Sheet Module (Often 'Sheet1', 'Sheet2' or...)
Option Explicit
'*******************************************************************************
Sub CommandButton1_Click()
' HIDES columns in Real Used Range.
HideDeleteColumnsOfRUR
End Sub
'*******************************************************************************
Sub CommandButton2_Click()
' Shows (unhides) columns.
ShowAllColumns
End Sub
'*******************************************************************************
'Sub CommandButton3_Click()
' ' DELETES columns in Real Used Range.
' HideDeleteColumnsOfRUR True ' (or probably any number different than 0.)
'End Sub
'*******************************************************************************

If Condition to create sheets only when Auto filter has data

I have written a code which does the below steps.
1) Loops through a list of products
2) Auto filters the data with each product.
3) Copies and pastes data on to separate worksheets and names it with that product name.
4) Inserts a line at every change in schedule
The only thing I couldn't do it here is to limit separate worksheet creation only for the products available in the source data when auto filtered.
I tried to do this by adding an if condition to add worksheets by product name only if auto filter shows any data but for some reason it is not working.
I would appreciate any help in fixing this problem and clean my code to make it look better and work faster.
Sub runreport()
Dim rRange As Range
Dim Rng As Range
' Open the Source File
Filename = Application.GetOpenFilename()
Workbooks.Open Filename
'Loops through each product type range from the macro spreadsheet.
For Each producttype In ThisWorkbook.Sheets("Schedule").Range("Product")
' Filters the sheet with a product code that matches and copy's the active sheet selection
Workbooks("Source.xlsx").Sheets("Sheet1").Range("A1:G1").AutoFilter Field:=4, Criteria1:=producttype
Sheets("Sheet1").Select
Sheets("Sheet1").Select
Range("A2").Select
Range(Selection, Selection.End(xlDown)).Select
Range(Selection, Selection.End(xlToRight)).Select
Selection.Copy
'Adds a new workbook
ActiveWorkbook.Sheets.Add After:=ActiveWorkbook.Sheets(Sheets.Count)
'Names the worksheet by Prod type descreption doing a vlookup from the spreadsheet
ActiveSheet.Name = Application.VLookup(producttype, ThisWorkbook.Sheets("Sheet2").Range("A:B"), 2, False)
'This will paste the filtered data from Source Data to the new sheet that is added
Range("a2").Select
ActiveSheet.Paste
ns = ActiveSheet.Name
'Copeis the headers to all the new sheets
Sheets("Sheet1").Select
Range("A1:BC1").Select
Selection.Copy
Sheets(ns).Activate
Range("a1").Select
ActiveSheet.Paste
Columns.AutoFit
' Inserts a blank row for everychange in ID
myRow = 3
Do Until Cells(myRow, 3) = ""
If Cells(myRow, 3) = Cells(myRow - 1, 3) Then
myRow = myRow + 1
Else
Cells(myRow, 1).EntireRow.Insert
myRow = myRow + 2
End If
Loop
Next producttype
End Sub
Try this...
Sub runreport()
Dim rRange As Range
Dim Rng As Range
Dim FiltRows As Integer
' Open the Source File
Filename = Application.GetOpenFilename()
Workbooks.Open Filename
'Loops through each product type range from the macro spreadsheet.
For Each producttype In ThisWorkbook.Sheets("Schedule").Range("Product")
' Filters the sheet with a product code that matches and copy's the active sheet selection
Workbooks("Source.xlsx").Sheets("Sheet1").Range("A1:G1").AutoFilter Field:=4, Criteria1:=producttype
With Workbooks("Source.xlsx").Sheets("Sheet1")
FiltRows = .AutoFilter.Range.Rows.SpecialCells(xlCellTypeVisible).Count / .AutoFilter.Range.Columns.Count
End With
If FiltRows > 1 Then 'There will always be a header row which is why it needs to be greater than one.
Sheets("Sheet1").Select
Sheets("Sheet1").Select
Range("A2").Select
Range(Selection, Selection.End(xlDown)).Select
Range(Selection, Selection.End(xlToRight)).Select
Selection.Copy
'Adds a new workbook
ActiveWorkbook.Sheets.Add After:=ActiveWorkbook.Sheets(Sheets.Count)
'Names the worksheet by Prod type descreption doing a vlookup from the spreadsheet
ActiveSheet.Name = Application.VLookup(producttype, ThisWorkbook.Sheets("Sheet2").Range("A:B"), 2, False)
'This will paste the filtered data from Source Data to the new sheet that is added
Range("a2").Select
ActiveSheet.Paste
ns = ActiveSheet.Name
'Copeis the headers to all the new sheets
Sheets("Sheet1").Select
Range("A1:BC1").Select
Selection.Copy
Sheets(ns).Activate
Range("a1").Select
ActiveSheet.Paste
Columns.AutoFit
' Inserts a blank row for everychange in ID
myRow = 3
Do Until Cells(myRow, 3) = ""
If Cells(myRow, 3) = Cells(myRow - 1, 3) Then
myRow = myRow + 1
Else
Cells(myRow, 1).EntireRow.Insert
myRow = myRow + 2
End If
Loop
End If
Next producttype
End Sub
I would recommend you define more variables than you have it keeps the code cleaner and easier to read as well as eliminates easy errors.
I also recommend always to utilize "option explicit" at the top of every code. It forces defining all variables (when you don't define a variable the program will do it for you (assuming you haven't used option explicit), but excel doesn't always get it correct. Also option explicit helps you avoid typos in variables.
Also as a general rule you rarely if ever have to .select anything to do what you need to with vba.
Below is an example of a cleaned up and shortened code which utilized variable definition and instantiation.
Sub runreport()
Dim wb As Workbook
Dim wsSched As Worksheet
Dim wsNew As Worksheet
Dim wbSource As Workbook
Dim wsSource As Worksheet
Dim rRange As Range
Dim producttype As Range
Dim Filename As String
Dim FiltRows As Integer
Dim myRow As Integer
'instantiate Variables
Set wb = ThisWorkbook
Set wsSched = wb.Worksheets("Schedule")
' Open the Source File
Filename = Application.GetOpenFilename()
Set wbSource = Workbooks.Open(Filename)
Set wsSource = wbSource.Worksheets("Sheet1")
'Loops through each product type range from the macro spreadsheet.
For Each producttype In wsSched.Range("Product")
' Filters the sheet with a product code that matches and copy's the active sheet selection
With wsSource
.AutoFilterMode = False
.Range("A1:G1").AutoFilter Field:=4, Criteria1:=producttype
FiltRows = .AutoFilter.Range.Rows.SpecialCells(xlCellTypeVisible).Count / .AutoFilter.Range.Columns.Count
If FiltRows > 1 Then 'There will always be a header row which is why it needs to be greater than one.
'Add new workbook
Set wsNew = wb.Sheets.Add(After:=ActiveWorkbook.Sheets(Sheets.Count))
'Copy filtered data including header
.AutoFilter.Range.SpecialCells(xlCellTypeVisible).Copy
'Paste filterd data and header
wsNew.Range("A1").PasteSpecial
Application.CutCopyMode = False
wsNew.Columns.AutoFit
'Rename new worksheet
wsNew.Name = WorksheetFunction.VLookup(producttype, wb.Worksheets("Sheet2").Range("A:B"), 2, False)
' Inserts a blank row for everychange in ID
myRow = 3
Do Until Cells(myRow, 3) = ""
If Cells(myRow, 3) = Cells(myRow - 1, 3) Then
myRow = myRow + 1
Else
Cells(myRow, 1).EntireRow.Insert
myRow = myRow + 2
End If
Loop
End If
End With
Next producttype
End Sub
First, you can check this answer for ways to optimize your vba code
As for your code in its current form, it would be easiest if you select the entire range of your product code data first. Then you can check this range after your filter and determine if all the rows are hidden. See a sample of the code below
Dim productData as Range
Set productData = Range(Range("A2"), Range("A2").End(xlDown).End(xlToRight))
' Filters the sheet with a product code that matches and copy's the active sheet selection
Workbooks("Source.xlsx").Sheets("Sheet1").Range("A1:G1").AutoFilter _
Field:=4, Criteria1:=producttype
' The error check will skip the creation of a new sheet if the copy failed (i.e. returns a non-zero error number)
On Error Resume Next
' Copies only the visible cells
productData.SpecialCells(xlCellTypeVisible).Copy
If Err.number = 0 then
'Adds a new workbook
ActiveWorkbook.Sheets.Add After:=ActiveWorkbook.Sheets(Sheets.Count)
ActiveSheet.Name = Application.VLookup(producttype, _
ThisWorkbook.Sheets("Sheet2").Range("A:B"), 2, False)
Range("a2").Select
ActiveSheet.Paste
End If
While you can Range.Offset one row and check if the Range.SpecialCells method with xlCellTypeVisible is Not Nothing, I prefer to use the worksheet's SUBTOTAL function. The SUBTOTAL function discards hidden or filtered rows from its operations so a simple COUNTA (SUBTOTAL subfunction 103) of the cells below the header will tell you if there is anything available.
Sub runreport()
Dim rRange As Range, rHDR As Range, rVAL As Range, wsn As String
Dim fn As String, owb As Workbook, twb As Workbook
Dim i As Long, p As Long, pTYPEs As Variant
pTYPEs = ThisWorkbook.Sheets("Schedule").Range("Product").Value2
Set twb = ThisWorkbook
' Open the Source File
fn = Application.GetOpenFilename()
Set owb = Workbooks.Open(fn)
With owb
'is this Workbooks("Source.xlsx")?
End With
With Workbooks("Source.xlsx").Worksheets("Sheet1")
With .Cells(1, 1).CurrentRegion
'store the header in case it is needed for a new worksheet
Set rHDR = .Rows(1).Cells
'reset the the filtered cells
Set rVAL = Nothing
For p = LBound(pTYPEs) To UBound(pTYPEs)
.AutoFilter Field:=4, Criteria1:=pTYPEs(p)
With .Resize(.Rows.Count - 1, 7).Offset(1, 0) '<~~resize to A:G and move one down off the header row
If CBool(Application.Subtotal(103, .Cells)) Then
'there are visible cells; do stuff here
Set rVAL = .Cells
wsn = Application.VLookup(pTYPEs(p), twb.Worksheets("Sheet2").Range("A:B"), 2, False)
'if the wsn worksheet doesn't exist, go make one and come back
On Error GoTo bm_New_Worksheet
With Worksheets(wsn)
On Error GoTo bm_Safe_Exit
rVAL.Copy Destination:=.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0)
'when inserting rows, always work from the bottom to the top
For i = .Cells(Rows.Count, 3).End(xlUp).Row To 3 Step -1
If .Cells(i, 3).Value2 <> .Cells(i - 1, 3).Value2 Then
.Rows(i).Insert
End If
Next i
'autofit the columns
For i = .Columns.Count To 1 Step -1
.Columns(i).AutoFit
Next i
End With
End If
End With
Next p
End With
End With
GoTo bm_Safe_Exit
bm_New_Worksheet:
On Error GoTo 0
With Worksheets.Add(after:=Sheets(Sheets.Count))
.Name = wsn
rHDR.Copy Destination:=.Cells(1, 1)
End With
Resume
bm_Safe_Exit:
End Sub
When a worksheet that is referenced by the wsn string does not exist, the On Error GoTo bm_New_Worksheet runs off and creates one. The Resume brings the code processing right back to the place it errored out.
One caution when using this method is to ensure that you have unique, legal worksheet names returned by your VLOOKUP function.

Resources