Copy Iteration Issue - arrays

The script below triggers every couple milliseconds due to the Worksheet Calculate event and then copies from my Pivot Table to the Chart Helper. Script works great but when it copies the next iteration of data it pastes it after the original data set it just copied.
I need it to continuously paste over the original data set. Example if the original data set copies to A1:A15 I want it to replace A1:A15 not keep A1:A15 then add the new refreshed data set to A16:A30.
I suspect this line is the culprit Offset(1).Resize(.Rows.Count, .Columns.Count).Value = .Value
Private Sub Worksheet_Calculate()
If Not Worksheets("Dashboard").ToggleButton1.Value Then Exit Sub
Dim chPivot As PivotCache
On Error GoTo SafeExit
Application.EnableEvents = False
Application.ScreenUpdating = False
For Each chPivot In ActiveWorkbook.PivotCaches
chPivot.Refresh
Next chPivot
With ThisWorkbook.Sheets("Data Breakdown").PivotTables("PivotTable1").PivotFields("Price").DataRange
ThisWorkbook.Sheets("Chart Helper").Cells(Rows.Count, 1).End(xlUp). _
Offset(1).Resize(.Rows.Count, .Columns.Count).Value = .Value
With ThisWorkbook.Sheets("Data Breakdown").PivotTables("PivotTable1").PivotFields("Cost").DataRange
ThisWorkbook.Sheets("Chart Helper").Cells(Rows.Count, 2).End(xlUp). _
Offset(1).Resize(.Rows.Count, .Columns.Count).Value = .Value
End With
End With
SafeExit:
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub

Assuming your data either gets larger or stays the same size then you just always need to paste data into the exact same cell to overwrite prior pastes.
i.e. replace .Cells(Rows.Count, 1).End(xlUp).Offset(1) with Range("A1")
You also need to separate your with statements. It can become ambiguous which object is being referenced when many are nested. Lastly, remove the column resize. You only need to resize the row here.
Your code could also be cleaned up a little by creating some Worksheet variables
Private Sub Worksheet_Calculate()
If Not Worksheets("Dashboard").ToggleButton1.Value Then Exit Sub
Dim db As Worksheet: Set db = ThisWorkbook.Sheets("Data Breakdown")
Dim ch As Worksheet: Set ch = ThisWorkbook.Sheets("Chart Helper")
Dim chPivot As PivotCache
On Error GoTo SafeExit
Application.EnableEvents = False
Application.ScreenUpdating = False
For Each chPivot In ActiveWorkbook.PivotCaches
chPivot.Refresh
Next chPivot
'Value transfer 'PRICE' to A1
With db.PivotTables("PivotTable1").PivotFields("Price").DataRange
ch.Range("A1").Resize(.Rows.Count).Value = .Value
End With
'Value transfer 'COST' to B1
With db.PivotTables("PivotTable1").PivotFields("Cost").DataRange
ch.Range("B1").Resize(.Rows.Count).Value = .Value
End With
SafeExit:
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub

Related

Excel VBA needs loop and variable to Send emails due to Outlook max

I have a code I found and it works great. However, due to Outlook's max of 500, I have a need to create a loop and count the instances. Below are the primary data columns, The other columns are not relevant to the macro. I cannot seem to write the code to loop, as I am somewhat new to VBA.
Column B - email address
Column F - "x" (lowercase to indicate an email must be sent.
Option Explicit
Sub Test1()
Dim OutApp As Object
Dim OutMail As Object
Dim cell As Range
' Change to path of OFT Template (AND user name)
Set OutEmail = objOutlook.CreateItemFromTemplate("C:\Change Notification.oft")
Application.ScreenUpdating = False
Set OutApp = CreateObject("Outlook.Application")
On Error GoTo cleanup
For Each cell In Columns("B").Cells.SpecialCells(xlCellTypeConstants)
If cell.Value Like "?*#?*.?*" And _
LCase(Cells(cell.Row, "F").Value) = "x" Then
Set OutMail = OutApp.CreateItem(0)
On Error Resume Next
With OutMail
.SentOnBehalfOfName = "shared#abccorp.com"
.to = cell.Value
.Send 'Or use Display
End With
On Error GoTo 0
Set OutMail = Nothing
End If
Next cell
cleanup:
Set OutApp = Nothing
Application.ScreenUpdating = True
End Sub

Copy/Paste VBA Script Recorder Logs Wrong

A few issues: Script is linked to a form ctrl button that runs Update Data every minute. This runs Copy Data and copies row A39:T39 and pastes that row in the other sheet. That is the intent. But it doesn't paste right. Need to past in a row not a column starting w/ a time stamp on the other sheet in A2. Stop Recording Data is linked to a form ctrl button to cancel Update Data but that doesn't work either.
Sub UpdateData()
Application.OnTime Now + TimeValue("00:01:00"), "UpdateData"
CopyData
End Sub
Sub CopyData()
Dim sht1 As Worksheet, sht2 As Worksheet, cpyRng As Range, logRng As Long
Application.StatusBar = "Recording Dashboard Started"
Set sht1 = ThisWorkbook.Sheets("Dashboard")
Set sht2 = ThisWorkbook.Sheets("Log")
Set cpyRng = sht1.Range("A39:T39")
logRng = sht2.Cells(2, Columns.Count).End(xlToLeft).Column + 1
sht2.Range("A2") = Now
cpyRng.Copy sht2.Cells(2, logRng)
End Sub
Sub StopRecordingData()
Application.StatusBar = "Recording Dashboard Stopped"
Application.OnTime Now + TimeValue("00:01:00"), "UpdateData", False
End Sub
Put this code into an own module.
To start logging, call StartRecordingData()
and for stopping call StopRecordingData()
Option Explicit
Dim boolLoggingActive As Boolean
Public Sub StartRecordingData()
Application.StatusBar = "Recording Dashboard Started"
boolLoggingActive = True
UpdateData
End Sub
Public Sub StopRecordingData()
Application.StatusBar = "Recording Dashboard Stopped"
boolLoggingActive = False
End Sub
Private Sub UpdateData()
If boolLoggingActive = True Then
Application.OnTime Now + TimeValue("00:01:00"), "UpdateData"
CopyData
End If
End Sub
Private Sub CopyData()
Dim sht1 As Worksheet, sht2 As Worksheet, cpyRng As Range, logRng As Long
Application.StatusBar = "Recording Dashboard Started"
Set sht1 = ThisWorkbook.Sheets("Dashboard")
Set sht2 = ThisWorkbook.Sheets("Log")
Set cpyRng = sht1.Range("A39:T39")
Dim rngLogTargetBeginningCell As Range
Set rngLogTargetBeginningCell = sht2.Rows(sht2.Rows.Count).Columns(1).End(xlUp).Offset(1, 0)
rngLogTargetBeginningCell = Now
Dim rngLastCellSelection As Range
Application.ScreenUpdating = False ' Stop Updating Graphic during data copy
Set rngLastCellSelection = Selection ' remember the last selection because pasting will change the active cell
cpyRng.Copy
rngLogTargetBeginningCell.Offset(0, 1).PasteSpecial xlPasteValues
Application.CutCopyMode = False ' Remove the copy area marker
rngLastCellSelection.Select ' reselect the old cell
Application.ScreenUpdating = True ' update graphics again
End Sub

Delete sheets if their names belong to an array

I have a workbook with multiple sheets named like dates e.g 12-01-2015, 12-02-2015, .... etc. I would like to create a code that deletes specific sheets if their names are listed in an array.
I created a code but it does not work. My guess is it is related to incorrect statement listed in line starting with "If".
I would really appreciate any tip where I am going wrong
Sub DeleteSelectedSheets()
Dim i As Long
Dim Holidays() As Variant
Holidays = Array("12-3-2015", "12-4-2015")
For Each Worksheet In ActiveWorkbook
If Worksheet(i).Name = Holidays(i) Then
Application.DisplayAlerts = False
Sheets(i).Delete
Application.DisplayAlerts = True
End If
Next
End Sub
Sub DeleteSelectedSheets()
Dim Holidays As Variant
Holidays = Array("Sheet1", "Sheet3", "Sheet5")
For Each Sheet In ThisWorkbook.Sheets
If UBound(Filter(Holidays, Sheet.Name)) > -1 Then
Application.DisplayAlerts = False
Sheet.Delete
Application.DisplayAlerts = True
End If
Next
End Sub
This should work just fine!
With the exception, that will happen regardless the code you are using, where there is only one sheet left and you are trying to delete it, it'll throw an error message!
See the use of LBound() and UBound() :
Sub DeleteSelectedSheets()
Dim i As Long
Dim Holidays() As Variant
Dim wS As Worksheet
Holidays = Array("12-3-2015", "12-4-2015")
For Each wS In ActiveWorkbook
For i = LBound(Holidays) To UBound(Holidays)
If wS.Name <> Holidays(i) Then
Else
Application.DisplayAlerts = False
wS.Delete
Application.DisplayAlerts = True
Exit For
End If
Next i
Next wS
End Sub
It may be better to simply try and delete the worksheet(s) named in the array. Of course, you will need to operate under an On Error Resume Next to guard against the condition where a worksheet named does not exist.
Sub DeleteSelectedSheets()
Dim h As Long, Holidays() As Variant
Holidays = Array("12-3-2015", "12-4-2015")
On Error Resume Next
Application.DisplayAlerts = False
For h = LBound(Holidays) To UBound(Holidays)
Worksheets(Holidays(h)).Delete
Next h
'alternate delete entire array (they need to be all there, fail on partial)
'Worksheets(Holidays).Delete
Application.DisplayAlerts = True
On Error GoTo 0
End Sub
This seems to work:
Sub DeleteSelectedSheets()
Dim i As Long
Dim Holidays() As Variant
Holidays = Array("12-3-2015", "12-4-2015")
For i = Sheets.Count To 1 Step -1
If Sheets(i).Name = Holidays(0) Or Sheets(i).Name = Holidays(1) Then
Application.DisplayAlerts = False
Sheets(i).Delete
Application.DisplayAlerts = True
End If
Next
End Sub

Loop Through Workbooks in the Same Folder and Do the Same Excel Task for All-VBA

I have more than 50 files needed to create the pivot table and each file has the same exact formort with different contents. So far, I have finished creating the code for the pivot and it works very well when running alone, however, it failed when I tried to run the code for all workbooks in the same folder. I don't know what happened and why it kept showing that no files could be found despite nothing wrong about the pathname.
Sub DoAllFiles()
Dim Filename, Pathname As String
Dim WB As Workbook
Pathname = "D:\Reports"
Filename = Dir(Pathname & "\*.xls*")
Do While Filename <> ""
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Set WB = Workbooks.Open(Pathname & Filename) 'open all files
PivotX WB
WB.Close SaveChanges:=True
Application.DisplayAlerts = True
Application.ScreenUpdating = True
Filename = Dir()
Loop
End Sub
Here is the code for pivot and it works very well when running it alone:
Sub PivotX(WB As Workbook)
Dim Lrow, Lcol As Long
Dim wsData As Worksheet
Dim rngRaw As Range
Dim PvtTabCache As PivotCache
Dim PvtTab As PivotTable
Dim wsPvtTab As Worksheet
Dim PvtFld As PivotField
Set wsData = ActiveSheet
Lrow = wsData.Cells(Rows.Count, "B").End(xlUp).Row
Lcol = wsData.Cells(1, Columns.Count).End(xlToLeft).Column
Set rngRaw = wsData.Range(Cells(1, 1), Cells(Lrow, Lcol))
Set wsPvtTab = Worksheets.Add
wsData.Select
ActiveWorkbook.PivotCaches.Create(SourceType:=xlDatabase, SourceData:=rngRaw, Version:=xlPivotTableVersion12).CreatePivotTable TableDestination:=wsPvtTab.Range("A3"), TableName:="PivotTable1", DefaultVersion:=xlPivotTableVersion12
Set PvtTab = wsPvtTab.PivotTables("PivotTable1")
PvtTab.ManualUpdate = True
Set PvtFld = PvtTab.PivotFields("Month")
PvtFld.Orientation = xlPageField
PvtTab.PivotFields("Month").ClearAllFilters
Set PvtFld = PvtTab.PivotFields("Year")
PvtFld.Orientation = xlPageField
PvtTab.PivotFields("Year").ClearAllFilters
Set PvtFld = PvtTab.PivotFields("Fund_Code")
PvtFld.Orientation = xlRowField
PvtFld.Position = 1
Set PvtFld = PvtTab.PivotFields("Curr")
PvtFld.Orientation = xlColumnField
PvtFld.Position = 1
wsPvtTab.PivotTables("PivotTable1").PivotFields("Curr").PivotItems("USD").Position = 1
With PvtTab.PivotFields("Trx_Amount")
.Orientation = xlDataField
.Function = xlSum
.NumberFormat = "#,##0;[red](#,##0)"
End With
wsPvtTab.PivotTables("Pivottable1").RowAxisLayout xlTabularRow
'Remove grand total
wsPvtTab.PivotTables("Pivottable1").RowGrand = False
For Each PvtTbCache In ActiveWorkbook.PivotCaches
On Error Resume Next
PvtTbCache.Refresh
Next PvtTbCache
'Determine filter value
Set PvtFld = PvtTab.PivotFields("Year")
PvtFld.ClearAllFilters
PvtFld.EnableMultiplePageItems = True
With PvtFld
.AutoSort xlmnual, .SourceName
For Each Pi In PvtFld.PivotItems
Select Case Pi.Name
Case "2014"
Case Else
Pi.Visible = False
End Select
Next Pi
.AutoSort xlAscending, .SourceName
End With
'determine filter value
Set PvtFld = PvtTab.PivotFields("Month")
PvtFld.ClearAllFilters
PvtFld.EnableMultiplePageItems = True
With PvtFld
.AutoSort xlmnual, .SourceName
For Each Pi In PvtFld.PivotItems
Select Case Pi.Name
Case "11"
Case Else
Pi.Visible = False
End Select
Next Pi
.AutoSort xlAscending, .SourceName
End With
PvtTab.ManualUpdate = False
End Sub
Any help would be very much appreciated. Thank you very much in advance.
This should solve your problem:
Set WB = Workbooks.Open(Pathname & "\" & Filename)
When I tried using your code, for some reason, it did not retain the backslash you put at the beginning of the "Filename" variable. That would explain why VBA couldn't find the files. Adding it back should between the path name and file name should make it work correctly
I believe you have the answer to your base problem above but I would offer the following 'tweaks' to avoid screen flashing and unrecovered variable assignment.
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Do While Filename <> ""
Set WB = Workbooks.Open(Pathname & "\" & Filename) 'open all files
Call PivotX(WB)
WB.Close SaveChanges:=True
Set WB = Nothing
Filename = Dir()
Loop
Application.DisplayAlerts = True
Application.ScreenUpdating = True
The Set WB = Nothing is really only purposeful on the last pass when WB is not reassigned but your PivotX sub could use several Set nnn = Nothing before exiting. While the reference count is supposed to be decremented (and memory consequently released), that is not always the case. (see Is there a need to set Objects to Nothing inside VBA Functions) In short, it is just good coding practise.
Finally, using Dim Filename, Pathname As String declares Filename as a variant, not a string type. It isn't making any difference here but you should be aware of what your variables are being declared as.

How can I automate populating an array using a named range on a specific worksheet?

The named range is SheetNames, on a worksheet named Consolidation, cell B3 to B101 and the array is Sheets(Array("Sheet1", "Sheet2")).Select:
Sub Insert_Formula()
Dim ws As Worksheet
Application.ScreenUpdating = False
Sheets(Array("Sheet1", "Sheet2")).Select
For Each ws In ActiveWindow.SelectedSheets
ws.Range("F3:F50").Formula = "=SUMIFS(Jul!$K:$K,Jul!$H:$H,$C$1,Jul!$J:$J,$C3)"
Next ws
Application.ScreenUpdating = True
End Sub
Hopefully this answers your question. If you are looking to iterate through every sheet in your workbook, instead of explicitly calling out their names you can just use
Sub Insert_Formula()
Application.ScreenUpdating = false
For Each ws In ActiveWorkbook.Sheets
ws.Range("F3:F50").Formula = "=SUMIFS(Jul!$K:$K,Jul!$H:$H,$C$1,Jul!$J:$J,$C3)"
Next
Application.ScreenUpdating = True
End Sub
Alternatively if you want to use the specific sheet name you specified with the worksheet names you can try this
Sub Insert_Formula()
For Each cell In Range("sheetNames")
Sheets(cell.Text).Range("F3:F50").Formula = "=SUMIFS(Jul!$K:$K,Jul!$H:$H,$C$1,Jul!$J:$J,$C3)"
Next
End Sub

Resources