Store ActiveWindow.SelectedSheets as an object to refer to later - vba

I'm trying to write a macro that will create a table of contents, listing the name of each of the worksheets currently selected by the user, together with the number of the page on which it starts when printed. I've taken the code from this page and adapted it a little as below.
However, when the new worksheet ("Contents") is created, that becomes the active, selected sheet, such that I can no longer use ActiveWindow.SelectedSheets to refer back to the collection of worksheets selected by the user. So I would like to store that information before creating the new sheet. How can I do this?
I have tried assigning it to a variable of type Worksheets as you can see, but this generates an error message. (I also tried Collection but to no avail.)
Sub CreateTableOfContents()
' Determine if there is already a Table of Contents
' Assume it is there, and if it is not, it will raise an error
' if the Err system variable is > 0, you know the sheet is not there
Dim WST As Worksheet
Dim SelSheets As Worksheets
Set SelSheets = ActiveWindow.SelectedSheets
On Error Resume Next
Set WST = Worksheets("Contents")
If Not Err = 0 Then
' The Table of contents doesn't exist. Add it
Set WST = Worksheets.Add(Before:=Worksheets("blankMagnitude"))
WST.Name = "Contents"
End If
On Error GoTo 0
' Set up the table of contents page
WST.[A2] = "Table of Contents"
With WST.[A6]
.CurrentRegion.Clear
.Value = "Subject"
End With
WST.[B6] = "Page(s)"
WST.Range("A1:B1").ColumnWidth = Array(36, 12)
TOCRow = 7
PageCount = 0
' Do a print preview on all sheets so Excel calcs page breaks
' The user must manually close the PrintPreview window
Msg = "Excel needs to do a print preview to calculate the number of pages." & vbCrLf & "Please dismiss the print preview by clicking close."
MsgBox Msg
SelSheets.PrintPreview
' Loop through each sheet, collecting TOC information
For Each S In SelSheets
If S.Visible = -1 Then
S.Select
ThisName = ActiveSheet.Name
HPages = ActiveSheet.HPageBreaks.Count + 1
VPages = ActiveSheet.VPageBreaks.Count + 1
ThisPages = HPages * VPages
' Enter info about this sheet on TOC
WST.Select
Range("A" & TOCRow).Value = ThisName
Range("B" & TOCRow).NumberFormat = "#"
If ThisPages = 1 Then
Range("B" & TOCRow).Value = PageCount + 1 & " "
Else
Range("B" & TOCRow).Value = PageCount + 1 & " " ' & - " & PageCount + ThisPages
End If
PageCount = PageCount + ThisPages
TOCRow = TOCRow + 1
End If
Next S
End Sub

I just amended your code. Is this what you are trying? Honestly all you had to do was
Change Dim SelSheets As Worksheets to Dim SelSheets and your original code would have worked :)
Option Explicit
Sub CreateTableOfContents()
Dim WST As Worksheet, S As Worksheet
Dim SelSheets
Dim msg As String
Dim TOCRow As Long, PageCount As Long, ThisPages As Long
Dim HPages As Long, VPages As Long
Set SelSheets = ActiveWindow.SelectedSheets
On Error Resume Next
Application.DisplayAlerts = False
Worksheets("Contents").Delete
Application.DisplayAlerts = True
On Error GoTo 0
Set WST = Worksheets.Add(Before:=Worksheets("blankMagnitude"))
With WST
.Name = "Contents"
.[A2] = "Table of Contents"
.[A6] = "Subject"
.[B6] = "Page(s)"
.Range("A1:B1").ColumnWidth = Array(36, 12)
End With
TOCRow = 7: PageCount = 0
msg = "Excel needs to do a print preview to calculate the number of pages." & vbCrLf & "Please dismiss the print preview by clicking close."
MsgBox msg
SelSheets.PrintPreview
For Each S In SelSheets
With S
HPages = .HPageBreaks.Count + 1
VPages = .VPageBreaks.Count + 1
ThisPages = HPages * VPages
WST.Range("A" & TOCRow).Value = .Name
WST.Range("B" & TOCRow).NumberFormat = "#"
If ThisPages = 1 Then
WST.Range("B" & TOCRow).Value = PageCount + 1 & " "
Else
WST.Range("B" & TOCRow).Value = PageCount + 1 & " " ' & - " & PageCount + ThisPages
End If
PageCount = PageCount + ThisPages
TOCRow = TOCRow + 1
End With
Next S
End Sub
EDIT: One important thing. It's always good to use OPTION EXPLICIT :)

You could store references to each sheet;
function getSheetsSnapshot() as Worksheet()
dim shts() As Worksheet, i As long
redim shts(ActiveWindow.SelectedSheets.Count - 1)
for i = 0 to ActiveWindow.SelectedSheets.Count - 1
set shts(i) = ActiveWindow.SelectedSheets(i + 1)
next
getSheetsSnapshot = shts
end function
fetch & store them:
dim oldsel() as Worksheet: oldsel = getSheetsSnapshot()
do your stuff then refer back to the original selected sheets;
for i = 0 to ubound(oldsel)
msgbox oldsel(i).name
next

Dim wks as Worksheet, strName as String
For each wks in SelSheets
strName = strName & wks.Name & ","
Next
strName = Left(strName, Len(strName) -1)
Dim arrWks() as String
arrWks = Split(strName,",")
End Sub
Your will have all the selected sheets, by name, in an arrWks, which you can then process through. You could also add each sheet name to a collection as well in the loop making it smoother.
It's best to stay away from ActiveSheet as much as possible. In this way you can loop through array with a counter and process
So:
Dim intCnt as Ingeter
For intCnt = Lbound(arrWks) to UBound(arrWks)
Worksheets(arrWks(intCnt)).Activate
.... rest of code ....
Next
replaces
For Each S In SelSheets

Related

How to make buttons run code on correct row after sorting the rows?

I have a spreadsheet where the rows are individual projects and the columns are the information about of that project.
The columns I am dealing with are for a technician to make a service call so I have: Tech Name, Site Contact, Date, Time and duration.
In the next column I have a button that once clicked, will run my VBA code to generate a meeting request that is sent to that technician which also placed the event on my outlook calendar. This code has been proven and is fine.
I generated buttons for about 300 rows. Upon a click, the code SHOULD check the button's location with the Application.Caller and get row and column numbers which I use to pull info for the meeting request.
Initially the button press works.
The issue is that I have the sheet set to auto sort with AutoFilter.ApplyFilter. So when I enter a job a row 92 with associated button 92 and the row auto sorts to say 30, the button 30 now tries to schedule 31 and button 92 is now scheduling row 30 (not sure where the button that schedules row 30 goes).
When clicked, the button should be reporting the cell directly under it.
Code to create buttons:
Option Explicit
Public Sub CreateButtons()
Application.ScreenUpdating = False
Dim wb As Workbook
Set wb = ThisWorkbook
Dim ws As Worksheet
Set ws = wb.Sheets("Projects")
Dim BTN As Button
Dim btn1 As Range
Dim btn2 As Range
Dim btn3 As Range
Dim i As Long
For i = 2 To 95
Set btn1 = ws.Cells(i, 22)
Set BTN = ws.Buttons.Add(btn1.Left, btn1.Top, btn1.Width, btn1.Height)
With BTN
.Caption = "Schedule" & i
.OnAction = "TASKSCHEDULER"
End With
Set btn2 = ws.Cells(i, 31)
Set BTN = ws.Buttons.Add(btn2.Left, btn2.Top, btn2.Width, btn2.Height)
With BTN
.Caption = "Schedule"
.OnAction = "TASKSCHEDULER"
End With
Set btn3 = ws.Cells(i, 40)
Set BTN = ws.Buttons.Add(btn3.Left, btn3.Top, btn3.Width, btn3.Height)
With BTN
.Caption = "Schedule"
.OnAction = "TASKSCHEDULER"
End With
Next i
Application.ScreenUpdating = True
End Sub
Code for Button Click:
Option Explicit
Public Sub TASKSCHEDULER()
Dim wb As Workbook
Set wb = ThisWorkbook
Dim ws As Worksheet
Set ws = wb.Sheets("Projects")
Dim b As Object
Dim r As Long
Dim c As Long
Set b = ws.Buttons(Application.Caller)
With b.TopLeftCell
r = .Row
c = .Column
End With
Dim TECH As String
Dim sitecon As String
Dim connum As String
Dim schdate As String
Dim schtime As String
Dim schdur As String
If IsEmpty(ws.Cells(r, c - 8)) Then 'VerIfy data has been entered into all fields needed - works
TECH = "Technician" & vbCrLf
Else
TECH = vbNullString
End If
If IsEmpty(ws.Cells(r, c - 5)) Then
sitecon = "Site Contact" & vbCrLf
Else
sitecon = vbNullString
End If
If IsEmpty(ws.Cells(r, c - 4)) Then
connum = "Site Contact Phone #" & vbCrLf
Else
connum = vbNullString
End If
If IsEmpty(ws.Cells(r, c - 3)) Then
schdate = "Date" & vbCrLf
Else
schdate = vbNullString
End If
If IsEmpty(ws.Cells(r, c - 2)) Then
schtime = "Start Time" & vbCrLf
Else
schtime = vbNullString
End If
If IsEmpty(ws.Cells(r, c - 1)) Then
schdur = "Duration" & vbCrLf
Else
schdur = vbNullString
End If 'End field verify
If IsEmpty(ws.Cells(r, c - 7)) Or IsEmpty(ws.Cells(r, c - 5)) Or IsEmpty(ws.Cells(r, c - 4)) Or IsEmpty(ws.Cells(r, c - 3)) Or IsEmpty(ws.Cells(r, c - 2)) Or IsEmpty(ws.Cells(r, c - 1)) Then 'Call out missing fields If present
MsgBox "Missing Fields: " & vbCrLf & vbCrLf & TECH & sitecon & connum & schdate & schtime & schdur
Else
SCHMTG 'Schedule Meeting
End If
'Else
'CNCLMTG 'Cancel Meeting
End Sub
Maybe try shrinking your button a bit so it fits more accurately within the underlying cell space:
Set BTN = ws.Buttons.Add(btn1.Left+2, btn1.Top+2, btn1.Width-4, btn1.Height-4)

Excel VBA code only works in debug mode

I've already searched in a bunch of topics and no solution seemed to work for me.
I've an Excel macro file that sometimes works fine, but sometimes only works in stepping mode.
This is a sub inside a main sub that passes a value (message) to a spreadsheet from an Outlook Calendar by category (key). (for this code I adapted from Script to total hours by calendar category in Outlook) .The value goes into the row with the same name as the category and the week value in the column. I've tried the DoEvents and I thought it had worked, but when I tried to run it in a different computer it failed again.
Any ideas?
Option Explicit
Public keyArray
Sub totalCategories()
Dim app As New Outlook.Application
Dim namespace As Outlook.namespace
Dim calendar As Outlook.Folder
Dim appt As Outlook.AppointmentItem
Dim apptList As Outlook.Items
Dim apptListFiltered As Outlook.Items
Dim startDate As String
Dim endDate As String
Dim category As String
Dim duration As Integer
Dim outMsg As String
Dim firstDayOfTheYear As Date
'Going to be used to get start and end date
firstDayOfTheYear = Date
firstDayOfTheYear = "01/01/" & Right(firstDayOfTheYear, 4)
' Access appointment list
Set namespace = app.GetNamespace("MAPI")
Set calendar = namespace.GetDefaultFolder(olFolderCalendar)
Set apptList = calendar.Items
' Include recurring appointments and sort the list
apptList.IncludeRecurrences = True
apptList.Sort "[Start]"
' Get selected date
startDate = firstDayOfTheYear + 7 * (CInt(SelectWeek.week) - 1)
endDate = firstDayOfTheYear + 7 * (CInt(SelectWeek.week) - 1) + 6
startDate = Format(startDate, "dd/MM/yyyy") & " 00:01"
endDate = Format(endDate, "dd/MM/yyyy") & " 11:59 PM"
' Filter the appointment list
Dim strFilter As String
strFilter = "[Start] >= '" & startDate & "'" & " AND [End] <= '" & endDate & "'"
Set apptListFiltered = apptList.Restrict(strFilter)
' Loop through the appointments and total for each category
Dim catHours
Set catHours = CreateObject("Scripting.Dictionary")
For Each appt In apptListFiltered
category = appt.Categories
duration = appt.duration
If catHours.Exists(category) Then
catHours(category) = catHours(category) + duration
Else
catHours.Add category, duration
End If
Next
' Loop through the categories
Dim key
keyArray = catHours.Keys
DoEvents 'prevents a bug from happening --> in some cases the total hours weren't divided by categories
For Each key In keyArray
outMsg = catHours(key) / 60
'Print in Realizado sheet --> activities must be in range (name manager) as "atividades"
writeReport SelectWeek.week, outMsg, key
Next
' Clean up objects
Set app = Nothing
Set namespace = Nothing
Set calendar = Nothing
Set appt = Nothing
Set apptList = Nothing
Set apptListFiltered = Nothing
End Sub
Sub writeReport(week, message As String, key)
Dim ws As Worksheet
Dim i As Integer
Dim Activities, nActivities As Integer
Set ws = Sheets("5")
Activities = Range("activities")
nActivities = UBound(Activities)
DoEvents
For i = 1 To nActivities
DoEvents
If key = Cells(i + 8, 2).Value Then
ws.Cells(i + 8, week + 3).Value = CDbl(message)
Exit For
End If
Next i
End Sub
You need to handle errors explicitly so you know exactly what is going on. Trust me that this will save you HEAPS of time troubleshooting your own code, especially in VBA.
Common practice is something like "try, catch, finally".
Dim position as string
Sub foo()
position = "sub function short description"
On Error GoTo catch
Err.Clear
'do stuff
finally:
On Error Resume Next
'do cleanup stuff
Exit Sub
catch:
Debug.Print Right(ThisWorkbook.FullName, Len(ThisWorkbook.FullName) - 3) & ", _
Position: " & errorPosition & " , Error Code: [ " & Hex(Err.number) & "], _
Description: " & Err.Description & ""
Resume finally
End Sub
Problem solved!
From this:
If key = Cells(i + 8, 2).Value Then
ws.Cells(i + 8, week + 3).Value = CDbl(message)
Exit For
End If
To this:
If key = Activities(i, 1) Then
ws.Cells(i + 8, week + 3).Value = CDbl(message)
Exit For
End If

Excel VBA crashing due to size

I made a script in VBA that should read a very long Pivot Table with over 190,000 entries in the "Data" sheet, and according with the value in the column "J", it should write the info from that row in a sheet called "Temp".
When the value from column "A" changes, it should read from sheet "Regioner" a list of over 600 entries and check if each value is presented in the previous arrays of values.
The code I wrote works, but it takes forever to write down the expected 220,000 entries in the "Temp" sheet. In my laptop, i5 6th generation with 8Gb RAM, it simply crashes.
The current code is as per below.
Many thanks to all!
Public Sub FindWithoutOrder()
Dim DataRowCounter As Long
Dim TempRowCounter As Long
Dim RegiRowCounter As Long
Dim DataOldCounter As Long
Dim DataNewCounter As Long
Dim loopCounter As Long
Dim DataOldProd As Range
Dim DataNewProd As Range
Dim DataPurchase As Range
Dim RegiButikk As Range
Dim ButikkFlag As Boolean
'Code optimization to run faster.
Application.ScreenUpdating = False
Application.DisplayAlerts = False
'Initialize variables.
'----------------------------------------------------------------------------------------------------------
DataRowCounter = 11
TempRowCounter = 1
DataOldCounter = 11
DataNewCounter = 11
Set DataOldProd = ActiveWorkbook.Sheets("Data").Range("A" & DataRowCounter)
Set DataNewProd = ActiveWorkbook.Sheets("Data").Range("A" & DataRowCounter)
Set DataPurchase = ActiveWorkbook.Sheets("Data").Range("J" & DataRowCounter)
'Start of loop that verifies all values inside "Data" sheet.
'----------------------------------------------------------------------------------------------------------
Do Until (IsEmpty(DataOldProd) And IsEmpty(DataNewProd))
'Verify if the product of new line is still the same or different.
'------------------------------------------------------------------------------------------------------
If DataNewProd.Value = DataOldProd.Value Then
DataNewCounter = DataNewCounter + 1
Else
'Initialize variables from "Regioner" sheet.
'------------------------------------------------------------------------------------------
ButikkFlag = False
RegiRowCounter = 11
Set RegiButikk = ActiveWorkbook.Sheets("Regioner").Range("C" & RegiRowCounter)
'Verify list of supermarkets and match them with purchases list.
'--------------------------------------------------------------------------------------------------
Do Until IsEmpty(RegiButikk)
'Check all supermarkets in the product range.
'----------------------------------------------------------------------------------------------
For loopCounter = DataOldCounter To DataNewCounter - 1
'Compare both entries and register them if it doesn't exist in the product list.
'------------------------------------------------------------------------------------------
If RegiButikk.Value = ActiveWorkbook.Sheets("Data").Range("D" & loopCounter).Value Then
ButikkFlag = True
RegiRowCounter = RegiRowCounter + 1
Set RegiButikk = ActiveWorkbook.Sheets("Regioner").Range("C" & RegiRowCounter)
Exit For
Else
ButikkFlag = False
End If
Next loopCounter
'Add to list supermarkets not present in the purchases list.
'------------------------------------------------------------------------------------------
If ButikkFlag = False Then
ActiveWorkbook.Sheets("Temp").Range("B" & TempRowCounter & ":D" & TempRowCounter).Value = ActiveWorkbook.Sheets("Regioner").Range("A" & RegiRowCounter & ":C" & RegiRowCounter).Value
ActiveWorkbook.Sheets("Temp").Range("A" & TempRowCounter).Value = ActiveWorkbook.Sheets("Data").Range("A" & DataRowCounter - 1).Value
TempRowCounter = TempRowCounter + 1
RegiRowCounter = RegiRowCounter + 1
Set RegiButikk = ActiveWorkbook.Sheets("Regioner").Range("C" & RegiRowCounter)
End If
Loop
'Reset the product range.
'--------------------------------------------------------------------------------------------------
DataOldCounter = DataNewCounter
DataNewCounter = DataNewCounter + 1
End If
'Validate if item was purchased in the defined period and copy it.
'------------------------------------------------------------------------------------------------------
If DataPurchase.Value = 0 Then
ActiveWorkbook.Sheets("Temp").Range("A" & TempRowCounter & ":D" & TempRowCounter).Value = ActiveWorkbook.Sheets("Data").Range("A" & DataRowCounter & ":D" & DataRowCounter).Value
TempRowCounter = TempRowCounter + 1
End If
'Update row counter and values for previous and new product readed.
'------------------------------------------------------------------------------------------------------
Set DataOldProd = ActiveWorkbook.Sheets("Data").Range("A" & DataRowCounter)
DataRowCounter = DataRowCounter + 1
Set DataNewProd = ActiveWorkbook.Sheets("Data").Range("A" & DataRowCounter)
Set DataPurchase = ActiveWorkbook.Sheets("Data").Range("J" & DataRowCounter)
Loop
'Code optimization to run faster.
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
Instead of having this code scattered all over the place:
'Code optimization to run faster.
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Use this procedure:
Public Sub ToggleWaitMode(ByVal wait As Boolean)
Application.Cursor = IIf(wait, XlMousePointer.xlWait, XlMousePointer.xlDefault)
Application.StatusBar = IIf(wait, "Working...", False)
Application.Calculation = IIf(wait, XlCalculation.xlCalculationManual, XlCalculation.xlCalculationAutomatic)
Application.ScreenUpdating = Not wait
Application.EnableEvents = Not wait
End Sub
Like this:
Public Sub DoSomething()
ToggleWaitMode True
On Error GoTo CleanFail
'do stuff
CleanExit:
ToggleWaitMode False
Exit Sub
CleanFail:
'handle errors
Resume CleanExit
End Sub
Disabling automatic calculation and worksheet events should already help quite a lot... but it's by no means "optimizing" anything. It simply makes Excel work [much] less, whenever a cell is modified.
If your code works but is just slow, take it to Code Review Stack Exchange and present it to the VBA reviewers: they'll go out of their ways to help you actually optimize your code. I know, I'm one of them =)

Selecting multiple cells

I have this code that check if the attachment size of the attachment is greater than 10MB. Now, if the attachment is greater than 10MB, it displays the file names on a msgbox then I want to select or highlight the cells that has this attachment greater than 10 MB but dunno how to do it.
Here's what I've tried:
Function checkAttSize()
Application.ScreenUpdating = False
Dim attach As Object
Dim attSize() As String
Dim loc() As String
Dim num As Long
Dim rng As Range
Set objOutlook = CreateObject("Outlook.Application")
Set objMail = objOutlook.CreateItem(0)
Set main = ThisWorkbook.Sheets("Main")
lRow = Cells(Rows.count, 15).End(xlUp).Row
efCount = 0
num = 0
With objMail
If lRow > 22 Then
On Error GoTo errHandler
For i = 23 To lRow
'attach.Add main.Range("O" & i).value
'totalSize = totalSize +
If (FileLen(main.Cells(i, "O").value) / 1000000) > 10 Then
ReDim Preserve attSize(efCount)
ReDim Preserve loc(num)
'store file names
attSize(efCount) = Dir(main.Range("O" & i))
'store cell address
loc(num) = i
efCount = efCount + 1
num = num + 1
found = True
End If
Next i
End If
End With
If found = True Then
MsgBox "Following File(s) Exceeds 10MB Attachment Size Limit:" & vbCrLf & vbCrLf & Join(attSize, vbCrLf) _
& vbCrLf & vbCrLf & "Please try removing the file(s) and try again.", vbCritical, "File Size Exceed"
'trying to select the cell addresses
For i = 1 To num
rng = rng + main.Range("O" & loc(i)).Select ' Ive also tried &
Next i
checkAttSize = True
Exit Function
End If
Exit Function
errHandler:
MsgBox "Unexpected Error Occured.", vbCritical, "Error"
checkAttSize = True
End Function
Thanks for the help.
No need to select the range. A single miss click by the user take take the focus away from the range. Also using .Select recklessly may cause run time errors. Color them instead.
After this line
If (FileLen(main.Cells(i, "O").value) / 1000000) > 10 Then
Add this line
main.Cells(i, "O").Interior.ColorIndex = 3
The cells now will be colored in red.
And in the end, alert the user with the message
If found = True Then
MsgBox "File(s) Exceeding 10MB Attachment Size Limit has been colored in red:"
End If

Not able to get the cell value in foreach loop using excel vba

Hi,
I have enclosed the sheet image.
My requirement is:
I want to get all the "G" column values for the organization matching to a specific organization name (Ex:360 evaluations).
I am getting null value after first loop for the G Column
Sub UsageWeekTrend()
Dim customerName As String
Dim sheetName As String
Dim dataFound As Boolean
Dim selectedCell As Range
Dim rowNumber As Integer
Dim weekMinutes As Double
Dim trendsFile As Workbook
Dim trendsSheet As Worksheet
On Error GoTo errorHandling
sheetName = ActiveSheet.Name
customerName = ActiveSheet.Range("A" & (ActiveCell.row)).Value
dataFound = False
For Each selectedCell In ActiveSheet.Range("A1:A1000")
If UCase(selectedCell.Value) = UCase(customerName) Then
weekMinutes = ActiveSheet.Range("G" & selectedCell.row).Value
Debug.Print weekMinutes
Debug.Print "G" & selectedCell.row
If dataFound = False Then
If trendsFile Is Nothing Then
Set trendsFile = Workbooks.Add()
trendsFile.Activate
Set trendsSheet = trendsFile.ActiveSheet
Else
' add a new sheet to the trends workbook
trendsFile.Activate
Set trendsSheet = Sheets.Add
End If
dataFound = True
rowNumber = 1
trendsSheet.Name = Left(customerName, 10) + " " + Format(Date, "MMDD")
trendsSheet.Cells(rowNumber, 1) = "Users"
trendsSheet.Cells(rowNumber, 2) = "Minutes"
rowNumber = rowNumber + 1
End If
' if a sheet has been created, then we have at least one non-zero value so add data
If dataFound = True Then
trendsSheet.Cells(rowNumber, 1) = customerName
trendsSheet.Cells(rowNumber, 2) = weekMinutes
rowNumber = rowNumber + 1
End If
End If
Next selectedCell
' if we have data, create the chart
If dataFound = True Then
' make sure the trends sheet is active for chart insertion
trendsSheet.Activate
Dim chtChart As ChartObject
Dim chartName As String
Dim endRange As String
' define the end of the range for the chart
endRange = "C" & CStr(rowNumber - 1)
' add chart to current sheet
Set chtChart = ActiveSheet.ChartObjects.Add(Left:=200, Top:=200, Width:=900, Height:=400)
chtChart.Activate
ActiveChart.ChartType = xlLineStacked
ActiveChart.SetSourceData Source:=trendsSheet.Range("A2", endRange)
ActiveChart.HasTitle = True
ActiveChart.ChartTitle.Text = customerName
ActiveChart.ApplyLayout (5)
Else
MsgBox ("No usage data found for customer " + customerName)
End If
Exit Sub
errorHandling:
MsgBox (Err.Description)
End Sub
When you run this line:
trendsFile.Activate
You change the Activesheet, so the 2nd time on the loop you again look at the activesheet
weekMinutes = ActiveSheet.Range("G" & selectedCell.row).Value
but the activesheet has changed. I would change those Activesheet calls to a worksheet object that you assign at the top.
This is always a good read for those new to VBA programming: How to avoid using Select in Excel VBA macros
The issue is that you're using ActiveSheet, and the active sheet is being changed in your code.
As soon as trendsFile.Activate is executed, these two references will have new meanings ActiveSheet.Range("A1:A1000") and ActiveSheet.Range("G" & selectedCell.row).Value.
You've created workbook & worksheet variables for your Trends file, and use those, you also need to create a worksheet variable for your "source" worksheet (not sure how you'd refer to it).
Also, I'd be a bit concerned about this section of code:
If trendsFile Is Nothing Then
Set trendsFile = Workbooks.Add()
trendsFile.Activate
Set trendsSheet = trendsFile.ActiveSheet
Else
' add a new sheet to the trends workbook
trendsFile.Activate
Set trendsSheet = Sheets.Add
End If
I believe you'll be adding a new sheet every time through the loop.
Try something like this:
Sub UsageWeekTrend()
Dim customerName As String
Dim sheetName As String
Dim dataFound As Boolean
Dim selectedCell As Range
Dim rowNumber As Integer
Dim weekMinutes As Double
Dim trendsFile As Workbook
Dim trendsSheet As Worksheet
Dim SourceSheet as worksheet 'this is the place where you start, call it what you will
On Error GoTo errorHandling
set SourceSheet = activesheet 'this will now always be THIS sheet, and won't change
sheetName = SourceSheet.Name
customerName = SourceSheet.Range("A" & (ActiveCell.row)).Value
dataFound = False
For Each selectedCell In SourceSheet.Range("A1:A1000")
If UCase(selectedCell.Value) = UCase(customerName) Then
weekMinutes = SourceSheet.Range("G" & selectedCell.row).Value
Debug.Print weekMinutes
Debug.Print "G" & selectedCell.row
If dataFound = False Then
If trendsFile Is Nothing Then
Set trendsFile = Workbooks.Add()
'trendsFile.Activate - never needed
Set trendsSheet = trendsFile.Sheets("Sheet1") 'use the first sheet, since you just created a brand new workbook
Else
' add a new sheet to the trends workbook
'trendsFile.Activate -- you never need this when you're working with an object instead of "Active"
'you'll find that this line will add a new sheet every time you execute the loop
'once you've created your "trendsFile" workbook. you'll need to do some tweaking here
'to prevent you getting one loop worth of data on each sheet
Set trendsSheet = Sheets.Add
End If
dataFound = True
rowNumber = 1
trendsSheet.Name = Left(customerName, 10) + " " + Format(Date, "MMDD")
trendsSheet.Cells(rowNumber, 1) = "Users"
trendsSheet.Cells(rowNumber, 2) = "Minutes"
rowNumber = rowNumber + 1
End If
' if a sheet has been created, then we have at least one non-zero value so add data
If dataFound = True Then
trendsSheet.Cells(rowNumber, 1) = customerName
trendsSheet.Cells(rowNumber, 2) = weekMinutes
rowNumber = rowNumber + 1
End If
End If
Next selectedCell
'The rest of your routine here...
End Sub