Add or Delete Excel Sheets Based On Array Values - arrays

I am working on a piece of code that creates an array and populates it based on the contents of a column in an Excel Sheet. I would then like to use this array to add or delete Excel Sheets.
Actions I'd like the Macro to do:
If the sheet name matches an array value do nothing
If there is no sheet name for an array value, add a sheet and name it the array value
If there is a sheet that does not exist in the array, delete the sheet.
I can populate the array with the values, but I am having a difficult time adding/deleting sheets based on the array values. I have noted the spot I am stuck in my code.
Sub CheckCities()
'Declare Variable
Dim rngCities As Range
Dim rngCityName As Range
Dim ws As Worksheet
Dim arrCityName() As String
Dim counter As Integer
Dim intWsCount As Integer
'Reset and erase array at start of program. Allows for proper data in array
Erase arrCityName
'initialize counter variable
counter = 0
'Set Range Name for wsData Customers
With wsAllCities1.Range("A2")
Set rngCities = Range(.Offset(0, 0), .End(xlDown))
End With
''''''''''''''''''''''''''''''''''''''''''''
' For Loop through Each City in rngCities
' adds current rngCities cell value to array
''''''''''''''''''''''''''''''''''''''''''''
For Each rngCityName In rngCities.Cells
'Debug.Print rngCityName.Value ' Print the values of each cell
counter = counter + 1 'Step up counter variable by 1
ReDim Preserve arrCityName(0 To rngCities.Count)
arrCityName(counter) = rngCityName.Value 'use the counter variable to create Array(#)
Next rngCityName
'''''''''''''''''''''''''''''''''''''''''''''''''''
'Test to verify Array was populated with City Names
'''''''''''''''''''''''''''''''''''''''''''''''''''
'wsAllCities1.Range("E2").Value = arrCityName(0)
'wsAllCities1.Range("E3").Value = arrCityName(1)
'wsAllCities1.Range("E4").Value = arrCityName(2)
'wsAllCities1.Range("E5").Value = arrCityName(3)
'wsAllCities1.Range("E6").Value = arrCityName(4)
'wsAllCities1.Range("E7").Value = arrCityName(5)
'wsAllCities1.Range("E8").Value = arrCityName(6)
'wsAllCities1.Range("E9").Value = arrCityName(7)
'wsAllCities1.Range("E10").Value = arrCityName(8)
'wsAllCities1.Range("E11").Value = arrCityName(9)
''''''''''''''''''''''''''''''''''''''''''''
' Loop statement to check sheet names
' adds or deletes sheets via arrCityName values
''''''''''''''''''''''''''''''''''''''''''''
''''STUCK ON CODE BELOW''''''''''''''''
''''STUCK ON CODE BELOW''''''''''''''''
''''STUCK ON CODE BELOW''''''''''''''''
''''STUCK ON CODE BELOW''''''''''''''''
''''STUCK ON CODE BELOW''''''''''''''''
''''STUCK ON CODE BELOW''''''''''''''''
intWsCount = ThisWorkbook.Worksheets.Count 'Count Number of Worksheets in this workbook
For Each ws In ThisWorkbook.Worksheets
counter = 0 'set variable
Do
ws.Activate 'activate the next worksheet in the look
If ws.Name <> "AllCities" Then
For Each arrayItem In arrCityName
If arrCityName = ws.Name Then
Debug.Print "City Name Found!"
ElseIf arrCityName <> ws.Name Then
End If
Next
Debug.Print "This city, " & ws.Name & ", does not exist in city list"
End If
Loop Until intWsCount 'Loop (x) number of times. X is determinted by variable intWsCount
Next
End Sub

You can run two separate loops. One loop to add sheets. One loop to delete sheets:
Sub dural()
Dim DesiredSheets(1 To 3) As String
Dim KillIt As Boolean, AddIt As Boolean
DesiredSheets(1) = "Sheet1"
DesiredSheets(2) = "Sheet2"
DesiredSheets(3) = "Whatever"
For Each sh In Sheets
KillIt = True
v = sh.Name
For Each a In DesiredSheets
If v = a Then
KillIt = False
End If
Next a
If KillIt Then sh.Delete
Next sh
For Each a In DesiredSheets
AddIt = True
For Each sh In Sheets
If a = sh.Name Then
AddIt = False
End If
Next sh
If AddIt Then
Sheets.Add
ActiveSheet.Name = a
End If
Next a
End Sub

Untested:
Sub CheckCities()
'Declare Variable
Dim rngCities As Range
Dim rngCityName As Range
Dim ws As Worksheet
Dim arrCityName() As String
Dim counter As long
Dim x as long, nm as string
With wsAllCities1
Set rngCities = .Range(.Range("A2").Offset(0, 0), _
.Range("A2").End(xlDown))
End With
ReDim Preserve arrCityName(1 To rngCities.Cells.Count)
counter=0
For Each rngCityName In rngCities.Cells
counter = counter + 1
arrCityName(counter) = rngCityName.Value
Next rngCityName
for x=1 to counter
nm = arrCityName(x)
set ws = nothing
on error resume next 'ignore error if no sheet found
set ws = thisworkbook.sheets(nm)
on error goto 0 'stop ignoring errors
if ws is nothing then
set ws = thisworkbook.worksheets.add()
ws.Name = nm
debug.print "Added sheet '" & nm & "'"
else
debug.print "Sheet '" & nm & "' already exists"
end if
next x
End Sub

Try this function. It does exactly what you need.
Public Function Test()
Dim wks, xlWSH As Worksheet
Dim myRange, Cell As Range
Dim ProtectIt As Boolean
'Refer to sheet name where you save your sheet names list
Set wks = Worksheets("SheetName")
With wks
'Refer to first cell where your sheet names list starts. Here is "A1"
Set myRange = .Range(.Cells(1, "A"), .Cells(.Rows.Count, "A").End(xlUp))
End With
For Each xlWSH In ActiveWorkbook.Worksheets
For Each Cell In myRange
'If sheet name is in your list then set DoIt to False
If xlWSH.Name = Cell.Value Then
DoIt = False
Exit For
Else
DoIt = True
End If
Next Cell
If DoIt = True Then
With xlWSH
'Do Some Actions With Sheet
End With
End If
Next xlWSH
End Function

Related

Transpose Filtered Column As String to Cell

I have a table which looks like this:
I wrote code which gives output like this:
The goal is a results table which does the following:
Count number of times "old" status appears
Count numer of times "new" status appears
Get all the (unique) old groups in one cell
Get all the (unique) new groups in one cell
The following code worked on one computer but not on another (both Windows, 64bit):
Sub TableSummary()
Dim sht As Worksheet
Dim i As Integer
Dim tbl As ListObject
Dim new_tbl As ListObject, old_tbl As ListObject
Dim new_array As Variant, old_array As Variant
'2. Disable Screen Updating - stop screen flickering and Disable Events to avoid inturupted dialogs / popups
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
Application.DisplayAlerts = False
On Error Resume Next
Application.DisplayAlerts = True
'4. Add a new summary table to summary worksheet
With ActiveWorkbook
sht.ListObjects.Add(xlSrcRange, sht.UsedRange, , xlYes).Name = "Summary"
sht.ListObjects("Summary").TableStyle = "TableStyleMedium5"
End With
i = 1
For Each sht In ActiveWorkbook.Worksheets
If sht.Name = "Summary" Then
'Define Column Headers of Summary
sht.Cells(1, 4).Resize(1, 4).Value = Array("Nbr of old", "Nbr of new", "Groups old", "Groups new")
i = i + 1
For Each tbl In sht.ListObjects
' Blue table
If tbl.TableStyle = "TableStyleMedium2" Then
sht.Range("D" & i).Value = WorksheetFunction.CountIf(tbl.Range, "old")
sht.Range("E" & i).Value = WorksheetFunction.CountIf(tbl.Range, "new")
Set new_tbl = sht.ListObjects("Summary")
Set new_tbl = sht.ListObjects("Summary").Range().AutoFilter(Field:=2, Criteria1:="old")
new_array = Application.Transpose(WorksheetFunction.Unique(sht.ListObjects("Summary").ListColumns("Group").DataBodyRange.SpecialCells(xlCellTypeVisible))) 'This doesn't work on my other machine
sht.Range("F" & i).Value = Join(new_array, ", ") 'works!
'Debug.Print Join(new_array, ", ")
sht.ListObjects("Summary").AutoFilter.ShowAllData
Set new_tbl = sht.ListObjects("Summary")
Set new_tbl = sht.ListObjects("Summary").Range().AutoFilter(Field:=2, Criteria1:="new")
new_array = Application.Transpose(WorksheetFunction.Unique(sht.ListObjects("Summary").ListColumns("Group").DataBodyRange.SpecialCells(xlCellTypeVisible))) 'This doesn't work on my other machine
sht.Range("G" & i).Value = Join(new_array, ", ") 'works!
Debug.Print Join(new_array, ", ")
sht.ListObjects("Summary").AutoFilter.ShowAllData
End If
Next
End If
Next
End Sub
Application.Transpose does not work on my second machine.
Here's a different approach using a function to create the list of unique values:
Sub TableSummary()
Const NEW_OLD_COL As Long = 2
Const GROUP_COL As String = "Group"
Const VAL_OLD As String = "old"
Const VAL_NEW As String = "new"
Dim sht As Worksheet, DstSht As Worksheet
Dim i As Integer
Dim tbl As ListObject
Dim new_tbl As ListObject, old_tbl As ListObject
Dim new_array As Variant, old_array As Variant
Set sht = ActiveSheet 'or whatever...
Set DstSht = sht
i = 2
For Each tbl In sht.ListObjects
' Blue table
If tbl.TableStyle = "TableStyleMedium2" Then
With tbl.ListColumns(NEW_OLD_COL)
DstSht.Range("G" & i).Value = WorksheetFunction.CountIf(.DataBodyRange, VAL_OLD)
DstSht.Range("H" & i).Value = WorksheetFunction.CountIf(.DataBodyRange, VAL_NEW)
End With
tbl.Range.AutoFilter Field:=NEW_OLD_COL, Criteria1:="new"
DstSht.Range("I" & i).Value = VisibleUniques(tbl, GROUP_COL)
tbl.Range.AutoFilter
tbl.Range.AutoFilter Field:=NEW_OLD_COL, Criteria1:="old"
DstSht.Range("J" & i).Value = VisibleUniques(tbl, GROUP_COL)
tbl.Range.AutoFilter
i = i + 1
End If
Next
End Sub
'Return a comma-separated list of all unique values in visible cells in
' column `ColName` of listobject `tbl`
Function VisibleUniques(tbl As ListObject, ColName As String) As String
Dim rngVis As Range, dict As Object, c As Range
On Error Resume Next 'ignore error if no visible cells
Set rngVis = tbl.ListColumns(ColName).DataBodyRange.SpecialCells(xlCellTypeVisible)
On Error GoTo 0 'stop ignoring errors
If rngVis Is Nothing Then Exit Function
Set dict = CreateObject("scripting.dictionary")
For Each c In rngVis.Cells
dict(CStr(c.Value)) = True
Next c
VisibleUniques = Join(dict.keys, ", ")
End Function

Subscript out of range when trying to loop through array to read values

I have a string of predefined worksheets, that I need to run specific code for. I get a compile error.
The code is set up to copy data from one sheet to another.
How do I do the same for multiple sheets?
When I step through the code sht is showing the MHP60,MHP61,MHP62 and not just MHP60.
I get a subscript out of range error.
Sub Prepare_CYTD_Report()
Dim addresses() As String
Dim addresses2() As String
Dim SheetNames() As String
Dim SheetNames2() As String
Dim wb1 As Workbook, wb2 As Workbook
Dim my_Filename
'Declare variables for MHP60, MHP61, MHP62 Trial Balance Values
Dim i, lastcol As Long
Dim tabNames, cell As Range
Dim tabName As String
Dim sht As Variant
addresses = Strings.Split("A9,A12:A26,A32:A38,A42:A58,A62:A70,A73:A76,A83:A90", ",") 'Trial Balance string values
addresses2 = Strings.Split("G9,G12:G26,G32:G38,G42:G58,G62:G70,G73:G76,G83:G90", ",") 'Prior Month string values
SheetNames = Strings.Split("MHP60,MHP61,MHP62")
'SheetNames2 = Strings.Split("MHP60-CYTDprior,MHP61-CYTDprior,MHP62-CYTDprior")
Set wb1 = ActiveWorkbook 'Revenue & Expenditure Summary Workbook
'*****************************Open CYTD files
my_Filename = Application.GetOpenFilename(fileFilter:="Excel Files,*.xl*;*.xm*", Title:="Select File to create CYTD Reports")
If my_Filename = False Then
Exit Sub
End If
Application.ScreenUpdating = False
Set wb2 = Workbooks.Open(my_Filename)
'*****************************Load Column Header Strings & Copy Data
For Each sht In SheetNames
lastcol = wb1.Sheets(sht).Cells(5, Columns.Count).End(xlToLeft).Column
On Error Resume Next
Set tabNames = wb1.Sheets(sht).Cells(4, 3).Resize(1, lastcol - 2).SpecialCells(xlCellTypeConstants)
'actual non-formula text values on row 4 from column C up to column lastCol'
On Error GoTo 0
If Err.Number <> 0 Then
MsgBox "No headers were found on row 4 of MHP60", vbCritical
Exit Sub
End If
For Each cell In tabNames
tabName = Strings.Trim(cell.Value2)
'dedicated variable in case of requirement for further parsing (space/comma elimination?)'
If CStr(wb1.Sheets(sht).Evaluate("ISREF('[" & wb2.Name & "]" & tabName & "'!$A$1)")) = "True" Then
'If wb2 has a tab named for the value in tabName
For i = 0 To UBound(addresses)
wb2.Sheets(tabName).Range(addresses(i)).Value2 = wb1.Sheets(sht).Range(addresses(i)).Offset(0, cell.Column - 1).Value2
'Debug.Print "data for " & wb2.Sheets(tabName).Range(addresses(i)).Address(, , , True) & " copied from " & wb1.Sheets("MHP60").Range(addresses(i)).Offset(0, cell.Column - 1).Address(, , , True)
Next i
Else
Debug.Print "A tab " & tabName & " was not found in " & wb2.Name
End If
Next cell
Next sht
MsgBox "CYTD Report Creation Complete", vbOKOnly
Application.ScreenUpdating = True
End Sub
Split by what?
SheetNames = Strings.Split("MHP60,MHP61,MHP62")
Split by comma? Then use the following instead:
SheetNames = Strings.Split("MHP60,MHP61,MHP62", ",")
Alternative
Dim SheetNames() As Variant ' needs to be Variant to work with Array()
SheetNames = Array("MHP60", "MHP61", "MHP62")
This should be quicker as your macro does not need to split the string and has it as array directly.

Defining terms for an array search

I've been searching for an answer to this, but I haven't been able to find anything specific enough to fill the gap in my VBA knowledge.
I'm putting two lists of data into arrays to be compared using a modified version of the code found here
(I'll post it below).
HOWEVER, I don't want to input the whole cell into the array to be compared with the second array. For instance, if the cell in the first sheet says "Company, LLC", I would like to only search "Company". I have some code that does this:
s = rCell.Value
indexofthey = InStr(1, s, ",")
aftercomma = Right(s, Len(s) - indexofthey + 1)
celld = Left(s, Len(s) - Len(aftercomma))
The code I need to somehow work this into (copied from the answer to the question I linked above) is this:
Option Explicit
Private Sub cmdCompare2to1_Click()
Dim sheet1 As Worksheet, sheet2 As Worksheet, sheet3 As Worksheet
Dim lngLastR As Long, lngCnt As Long
Dim var1 As Variant, var2 As Variant, x
Dim rng1 As Range, rng2 As Range
Set sheet1 = Worksheets(1)
Set sheet2 = Worksheets(2)
Set sheet3 = Worksheets(3) ' assumes sheet3 is a blank sheet in your workbook
Application.ScreenUpdating = False
'let's get everything all set up
'sheet3 column headers
sheet3.Range("A1:B1").Value = Array("in1Not2", "in2Not1")
'sheet1 range and fill array
With sheet1
lngLastR = .Range("A" & .Rows.Count).End(xlUp).Row
Set rng1 = .Range("A1:A" & lngLastR)
var1 = rng1
End With
'sheet2 range and fill array
With sheet2
lngLastR = .Range("A" & .Rows.Count).End(xlUp).Row
Set rng2 = .Range("A1:A" & lngLastR)
var2 = rng2
End With
'first check sheet1 against sheet2
On Error GoTo NoMatch1
For lngCnt = 1 To UBound(var1)
x = Application.WorksheetFunction.Match(var1(lngCnt, 1), rng2, False)
Next
'now check sheet2 against sheet1
On Error GoTo NoMatch2
For lngCnt = 1 To UBound(var2)
x = Application.WorksheetFunction.Match(var2(lngCnt, 1), rng1, False)
Next
On Error GoTo 0
Application.ScreenUpdating = True
Exit Sub
NoMatch1:
sheet3.Range("A" & sheet3.Rows.Count).End(xlUp).Offset(1) = var1(lngCnt, 1)
Resume Next
NoMatch2:
sheet3.Range("B" & sheet3.Rows.Count).End(xlUp).Offset(1) = var2(lngCnt, 1)
Resume Next
End Sub
Assuming you do not want to change the values in your cells you will need to loop through the arrays. You can use a proc like this:
Sub RemoveUnwantedText(ByRef theArray As Variant)
Dim theValue As String
Dim i As Long
Dim indexOfComma As Integer
' array is created from single-column range of cells
' and so has 2 dimensions
For i = LBound(theArray, 1) To UBound(theArray, 1)
theValue = CStr(theArray(i, 1))
indexOfComma = InStr(1, theValue, ",")
If indexOfComma > 0 Then
theValue = Trim(Left(theValue, indexOfComma - 1))
End If
theArray(i, 1) = theValue
Next i
End Sub
Paste this into the same module as your code. In your code, before you do any comparison, add these calls:
RemoveUnwantedText var1
RemoveUnwantedText var2

How to name new worksheets based on values in array and copy associated values from original data set?

I have data in columns P,Q,R. I would like to filter through R, and make a new Worksheet for each unique item in Column R. This new worksheet will also bring along the associated values in P and Q.
Thus far I have learned how to filter the data in R and put the unique values into an array. For each value in the array I made a new sheet named Array1(i) because I am unable to convert the value into a string for some reason. How can I do this in an optimized fashion such that I create a new sheet for each unique value in R and bring along the values in the same rows in P and Q as well? Here is my code:
Also, how do I declare the array dynamically rather than hard coding 50? How can I use a dynamic range for column R?
Note the values in the array will be something like 6X985
Sub testarray()
Dim TestRg As Excel.Range
Dim Array1(50) As Variant
Dim SheetName As String
Dim i, j, k As Integer
i = 1
Set TestRg = Range("R1:R36879")
TestRg.AdvancedFilter Action:=xlFilterInPlace, Unique:=True
For Each c In TestRg.SpecialCells(xlCellTypeVisible)
Array1(i) = c.Value
'SheetName = CStr(c.Value)
Worksheets.Add.Name = i
i = i + 1
Next c
j = i - 1
i = 1
Worksheets("Sheet1").ShowAllData
For Each c In Range("S3:S" & j)
c.Value = Array1(i)
i = i + 1
Next c
k = 1
For Each d In Range("T3:T" & j)
d.Value = k
k = k + 1
Next d
End Sub
The code itself is kind of advanced, I added comments to assist with understanding. I hope it helps:
Sub tgr()
Dim wsData As Worksheet
Dim wsNew As Worksheet
Dim rngData As Range
Dim xlCalc As XlCalculation
Dim arrUnq() As Variant
Dim strSheetName As String
Dim UnqIndex As Long
Dim i As Long
Set wsData = Sheets("Sheet1")
Set rngData = wsData.Range("R1", wsData.Cells(Rows.Count, "R").End(xlUp))
'Disable application items to let code run faster
With Application
xlCalc = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
.EnableEvents = False
.DisplayAlerts = False
End With
'Re-enable all the application items just in case there's an error
On Error GoTo CleanExit
'Get the list of unique values from rngData, sorted alphabetically
'Put that list into the arrUnq array
With Sheets.Add
rngData.AdvancedFilter xlFilterCopy, , .Range("A1"), True
.UsedRange.Sort .UsedRange, xlAscending, Header:=xlYes
arrUnq = Application.Transpose(.Range("A2", .Cells(Rows.Count, "A").End(xlUp)).Value)
.Delete
End With
For UnqIndex = LBound(arrUnq) To UBound(arrUnq)
'Verify a valid worksheet name
strSheetName = arrUnq(UnqIndex)
For i = 1 To 7
strSheetName = Replace(strSheetName, Mid(":\/?*[]", i, 1), " ")
Next i
strSheetName = Trim(Left(WorksheetFunction.Trim(strSheetName), 31))
'Check if worksheet name already exists
If Not Evaluate("ISREF('" & strSheetName & "'!A1)") Then
'Sheet doesn't already exist, create sheet
Sheets.Add(After:=Sheets(Sheets.Count)).Name = strSheetName
End If
Set wsNew = Sheets(strSheetName)
wsNew.UsedRange.Clear
'Filter for the unique data
With rngData
.AutoFilter 1, arrUnq(UnqIndex)
'Copy the data from columns P:R to the new sheet
Intersect(wsData.Range("P:R"), .EntireRow).SpecialCells(xlCellTypeVisible).Copy wsNew.Range("A1")
End With
Next UnqIndex
rngData.AutoFilter 'Remove any remaining filters
CleanExit:
With Application
.Calculation = xlCalc
.ScreenUpdating = True
.EnableEvents = True
.DisplayAlerts = True
End With
If Err.Number <> 0 Then
MsgBox Err.Description, , "Error: " & Err.Number
Err.Clear
End If
End Sub

Pasting non-empty array values into Excel

I'm using the code below to loop through a array(populated from a range) and then add the value of the cell to another array, then I paste the new array into a table in Excel. It's working fine but it also pastes all the empty array values into the table as well, this is a problem as I have a drop down list using that table and it contains lots of empty values at the end.
Is there a way I can either remove the empty values from the array or only paste the values which aren't empty?
Sub newFilterStaff()
Dim sourceData, targetData() As Variant
Dim sourceRange, targetRange, rng As Range
Dim sheet As Worksheet
Dim i, staffCount As Integer
Dim time As Long
Dim name As String
time = GetTickCount
'Set default values
staffCount = 0
Set sheet = Worksheets("wfm_staff")
Set sourceRange = sheet.[C1:C100]
sourceData = sourceRange.Value
'sheet.Range("E2:E50").Clear 'Clear previous list
For i = LBound(sourceData, 1) To UBound(sourceData, 1)
If sourceData(i, 1) <> "XXXX" And i <> 1 Then 'Remove header row
Set rng = sheet.Range("A" & i)
With rng
name = .Value
End With
ReDim Preserve targetData(0 To staffCount) 'Add name to array
targetData(staffCount) = name
staffCount = staffCount + 1
End If
Next
Range("E2:E" & UBound(targetData) + 1) = WorksheetFunction.Transpose(targetData)
Debug.Print GetTickCount - time, , "ms"
End Sub
Then just check for blanks:
Sub newFilterStaff()
Dim sourceData, targetData() As Variant
Dim sourceRange, targetRange, rng As Range
Dim sheet As Worksheet
Dim i, staffCount As Integer
Dim time As Long
Dim name As String
time = GetTickCount
'Set default values
staffCount = 0
Set sheet = Worksheets("wfm_staff")
Set sourceRange = sheet.[C1:C100]
sourceData = sourceRange.Value
'sheet.Range("E2:E50").Clear 'Clear previous list
For i = LBound(sourceData, 1) To UBound(sourceData, 1)
If sourceData(i, 1) <> "XXXX" And i <> 1 Then 'Remove header row
If sourceData(i, 1) <> "" Then
Set rng = sheet.Range("A" & i)
With rng
name = .Value
End With
ReDim Preserve targetData(0 To staffCount) 'Add name to array
targetData(staffCount) = name
staffCount = staffCount + 1
End If
End If
Next
Range("E2:E" & UBound(targetData) + 1) = WorksheetFunction.Transpose(targetData)
Debug.Print GetTickCount - time, , "ms"
End Sub
If name <> "" Then
targetData(staffCount) = name
staffCount = staffCount + 1
End If

Resources