Run macro on row most recently added - vba

I'm using a macro to generate an Outlook template based on data that has been entered into a workbook.
In the workbook I have 100 rows of data and 7 sheets.
I need to run the macro (on click of a button) on the most recent row's data and generate the template.
My rows contain time data (example 13:37, next row 14:02 etc) so I think that could be a good way to identify the latest row.
I'm using this code. I'm selecting the row using A203:G203
Sub NonConformanceGenerator()
ActiveSheet.Range("A203:G203").Select
Const HEADER_ROW As Long = 202 '<< the row with column headers
Const NUM_COLS As Long = 7 '<< how many columns of data
Const olMailItem = 0
Const olFolderInbox = 6
Dim ol As Object, fldr, ns, msg
Dim html As String, c As Range, colReq As Long, hdr As Range
Dim rw As Range
On Error Resume Next
Set ol = GetObject(, "outlook.application")
On Error GoTo 0
If ol Is Nothing Then
On Error Resume Next
Set ol = CreateObject("outlook.application")
Set ns = ol.GetNamespace("MAPI")
Set fldr = ns.GetDefaultFolder(olFolderInbox)
fldr.display
On Error GoTo 0
End If
If ol Is Nothing Then
MsgBox "Couldn't start Outlook to compose mail!", vbExclamation
Exit Sub
End If
Set msg = ol.CreateItem(olMailItem)
Set rw = Selection.Cells(1).EntireRow
msg.Subject = ""
html = "<style type='text/css'>"
html = html & "body, p {font:11pt calibri;padding:40px;}"
html = html & "table {border-collapse:collapse}"
html = html & "td {border:1px solid #000;padding:8px;}"
html = html & "</style>"
html = html & "<p>Hello,</p>"
html = html & "<table>"
For Each c In rw.Cells(1).Resize(1, NUM_COLS).Cells
If c.Column <> 0 Then '<<< This removes the 4th column if you type number 4 after the <> symbols
Set hdr = rw.Parent.Cells(HEADER_ROW, c.Column) '<< get the header text for this cell
html = html & "<tr><td style='background-color:#FFF;width:200px;'>" & _
hdr.Value & _
"</td><td style='width:400px;'>" & Trim(c.Value) & "</td></tr>"
End If 'we want this cell
Next c
html = html & "</table>"
msg.HTMLBody = html
msg.display
ActiveSheet.Range("A15").Select
End Sub

Is the newest row always at the bottom of the spreadsheet? If so, you can use Cells(Rows.Count, "A").End(xlUp).Row to return the last row with data in Column "A" for instance.
You could do something like this for use in your example.
With ActiveSheet
.Range("A" & .Cells(.Rows.Count, "A").End(xlUp).Row).Resize(1, 7).Select
End With

Related

How to search for Outlook item subject by Excel range to return email address?

I have minimal experience coding.
I have code to read invoice numbers as range c in one workbook - W1 -
and return corresponding values relating to invoice fees and due dates from another workbook - W2. The code runs as intended.
I would like to use the same range c to search sent items in Outlook for subject lines containing the c values, and return to W1 the recipient's email address and name.
For example, an invoice number could be displayed "201x/xxxx", the subject of the email would read "Invoice from ABC Ltd - 201x/xxxx", the code would return to W1 the required data.
I have attempted to apply the Like function.
Below is the code as it stands;
Sub UpdateDunningLog()
'defining source and target workbooks
Dim w1 As Worksheet, w2 As Worksheet
'c will be the matched value (invoice number)
Dim c As Range, FR As Long
'defining debtor log
Dim strfilename As String: strfilename = "xyz.xlsx"
Dim DL As Workbook
Application.ScreenUpdating = False
'sets active worksheet to Dunning Log
Set w2 = ActiveWorkbook.Sheets("Sheet1")
'sets debtor log to open (in background)
Set DL = Workbooks.Open(Filename:=strfilename, UpdateLinks:=3)
Set w1 = DL.Worksheets("Data")
Application.ScreenUpdating = False
'c is invoice number, macro begins reading at A4
' and continues until there are no remaining rows
For Each c In w1.Range("A4", w1.Range("A" & Rows.Count).End(xlUp))
FR = 0
On Error Resume Next
'matches invoice nummbers from debtor log to Dunning Log
FR = Application.Match(c, w2.Columns("E"), 0)
On Error GoTo 0
'if there is a match, client name is extracted
If FR <> 0 Then w2.Range("D" & FR).Value = c.Offset(0, 3)
'if there is a match, invoice value is extracted
If FR <> 0 Then w2.Range("G" & FR).Value = c.Offset(0, 15)
'if there is a match, overdue days are extracted
If FR <> 0 Then w2.Range("H" & FR).Value = c.Offset(0, 41)
Next c 'loops through each invoice number
Application.ScreenUpdating = True
'closes debtor log, ensuring it stays in the background throughout the process
DL.Close savechanges:=False
Dim olApp As Outlook.Application
Dim Folder As Outlook.MAPIFolder
Dim olNS As Namespace
Dim i As Integer, j As Integer
Dim MailBoxName As String, Pst_Folder_Name As String
Dim olMail As Object
MailBoxName = "xyz#xyz.xyz"
Pst_Folder_Name = "Sent Items"
Set olApp = New Outlook.Application
Set olNS = olApp.GetNamespace("MAPI")
Set Folder = olNS.GetDefaultFolder(olFolderSentMail)
i = 1
For Each olMail In Folder.Items
If olMail.Subject Like "*c*" Then _
w2.Range("A" & FR).Value = Folder.Items.Item(i).RecipientName
If olMail.Subject Like "*c*" Then _
w2.Range("B" & FR).Value = Folder.Items.Item(i).RecipientEmailAddress
On Error GoTo 0
i = i + 1
Next olMail
End Sub
This line olMail.Subject Like "*c*" is looking for emails that contain the letter C in the subject line. To extract the value from range object c:
Dim SearchFor As String
SearchFor = "*" & c.Value & "*"
If olMail.Subject Like SearchFor Then
In the example, I've used string concatenation to build the search pattern. I've used a separate variable, although you don't have to.
You mentioned that your code is growing in size and becoming muddled. This is a common problem. One way of staying on top is to break your code into a number of smaller units. Rough example:
' Code execution starts here.
Sub EntryPoint
Dim iNums As Range
Dim iNum As Range
Dim CurrentSubject As String
Set iNums = GetInvoiceNumbers()
For Each iNum In iNums
CurrentSubject = GetEmailSubject(iNum)
Next
End Sub
' Returns a list of invoice numbers.
Function GetInvoiceNumbers() As Range
' ...Code here...
End Function
' Checks Outlet mailbox.
Function GetEmailSubject(ByVal InvoiceNumber As String) As String
' ...Code here...
End Function
Make sure each sub/function has one, and only job. Give it a meaningful name and soon you'll be able to speed read your code while looking for the right place to make the next change.
Edit
Ok so I missed a few important details in the OPs code. This is my reworked answer:
I've added a new function that extracts email details for the current invoice.
' Checks the xyz mailbox for any items with the supplied
' invoice number in the sent items folder.
'
' InvoiceNumber Invoice to search for.
' RecipientNameCell Cell to write name to.
' RecipientEmailAddressCell Cell to write email address to.
Sub ExtractEmailDetails(ByVal InvoiceNumber As String, ByRef RecipientNameCell As Range, ByRef RecipientEmailAddressCell As Range)
Dim OlApp As Outlook.Application
Dim SentFolder As Outlook.MAPIFolder
Dim OlMail As Object
Set OlApp = New Outlook.Application
Set SentFolder = OlApp.GetNamespace("MAPI").GetDefaultFolder(olFolderSentMail)
For Each OlMail In SentFolder.Items
' Ignore notes and other items that might be stored in the folder.
If TypeName(OlMail) = "MailItem" Then
If OlMail.Subject Like "*" & InvoiceNumber & "*" Then
RecipientNameCell.Value = OlMail.Recipients.Item(1).Name
RecipientEmailAddressCell = OlMail.Recipients.Item(1).Address
End If
End If
Next
End Sub
You can call this from the existing loop in your code:
For Each c In w1.Range("A4", w1.Range("A" & Rows.Count).End(xlUp)) 'c is invoice number, macro begins reading at A4 and continues until there are no remaining rows
FR = 0
On Error Resume Next
FR = Application.Match(c, w2.Columns("E"), 0) 'matches invoice nummbers from debtor log to Dunning Log
On Error GoTo 0
If FR <> 0 Then w2.Range("D" & FR).Value = c.Offset(0, 3) 'if there is a match, client name is extracted
If FR <> 0 Then w2.Range("G" & FR).Value = c.Offset(0, 15) 'if there is a match, invoice value is extracted
If FR <> 0 Then w2.Range("H" & FR).Value = c.Offset(0, 41) 'if there is a match, overdue days are extracted
' NEW LINE BELOW.
ExtractEmailDetails c.Value, w2.Range("A" & FR).Value, w2.Range("B" & FR).Value
Next c 'loops through each invoice number
ExtractEmailDetails is executed once for each matched invoice number. It checks the entire sent box. At the moment if it finds more than 1 match only the last name/address found is written to Excel. To change this you'd need to allow for more rows or columns. Also, an email could have multiple recipients. Here details are extracted for the first. You could extract them all, either into a long field or additional rows/columns.
Without the spreadsheets, I couldn't fully test the code. The new function may require a little tweaking ;).

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

Delete chart series but keep their formatting

This is the code I use to dynamically create charts in Virtual Basic:
Dim Chart As Object
Set Chart = Charts.Add
With Chart
If bIssetSourceChart Then
CopySourceChart
.Paste Type:=xlFormats
End If
For Each s In .SeriesCollection
s.Delete
Next s
.ChartType = xlColumnClustered
.Location Where:=xlLocationAsNewSheet, Name:=chartTitle
Sheets(chartTitle).Move After:=Sheets(Sheets.count)
With .SeriesCollection.NewSeries
If Val(Application.Version) >= 12 Then
.values = values
.XValues = columns
.Name = chartTitle
Else
.Select
Names.Add "_", columns
ExecuteExcel4Macro "series.columns(!_)"
Names.Add "_", values
ExecuteExcel4Macro "series.values(,!_)"
Names("_").Delete
End If
End With
End With
#The CopySourceChart Sub:
Sub CopySourceChart()
If Not CheckSheet("Source chart") Then
Exit Sub
ElseIf TypeName(Sheets("Grafiek")) = "Chart" Then
Sheets("Grafiek").ChartArea.Copy
Else
Dim Chart As ChartObject
For Each Chart In Sheets("Grafiek").ChartObjects
Chart.Chart.ChartArea.Copy
Exit Sub
Next Chart
End If
End Sub
How can I keep the formatting of series that is applied in the If bIssetSourceChart part while deleting those series' data?
I have solved this issue before. I have charts that were created by macro but it only applied to the date I made them. So a made a refresh macro that runs after every Workbook open. I used source before and found that it deletes everything. then moved on to series only. I will paste my work here and try to explain. For quick navigation the second part of the code down there called sub aktualizacegrafu() might help you if you get lost find a reference in upper part of the code starting with sub generacegrafu()
Sub generacegrafu()
ThisWorkbook.Sheets("List1").CommandButton6.BackColor = &H0&
ThisWorkbook.Sheets("List1").CommandButton6.ForeColor = &HFFFFFF
Dim najdiposlradek As Object
Dim graf As Object
Dim vkladacistring As String
Dim vykreslenysloupec As Integer
Dim hledejsloupec As Object
Dim hledejsloupec2 As Object
Dim kvantifikator As Integer
Dim grafx As ChartObject
Dim shoda As Boolean
Dim jmenografu As String
Dim rngOrigSelection As Range
Cells(1, 1).Select
If refreshcharts = True Then
Set hledejsloupec = Range("11:11").Find(What:=prvnislovo, LookIn:=xlValues)
'dynamicaly generated, prvnislovo is for first word in graph and the macro looks for match in row 11 if it doesnt find any then
Else
'then it looks for match in option box
Set hledejsloupec = Range("11:11").Find(What:=ThisWorkbook.Sheets("List1").ComboBox1.Value, LookIn:=xlValues)
End If
If hledejsloupec Is Nothing Then
MsgBox "Zadaný sloupec v první nabídce nebyl nalezen."
Else
If refreshcharts = True Then
Set hledejsloupec2 = Range("11:11").Find(What:=druheslovo, LookIn:=xlValues)
Else
Set hledejsloupec2 = Range("11:11").Find(What:=ThisWorkbook.Sheets("List1").ComboBox2.Value, LookIn:=xlValues)
End If
If hledejsloupec2 Is Nothing Then
MsgBox "Zadaný sloupec v druhé nabídce nebyl nalezen."
Else
jmenografu = Cells(11, hledejsloupec.Column).Value & "_" & Cells(11, hledejsloupec2.Column).Value
Set najdiposlradek = Range("A:A").Find(What:=Date, LookIn:=xlValues)
Application.ScreenUpdating = False
Set rngOrigSelection = Selection
'This one selects series for new graph to be created
Cells(1048576, 16384).Select
Set graf = ThisWorkbook.Sheets("List1").Shapes.AddChart
rngOrigSelection.Parent.Parent.Activate
rngOrigSelection.Parent.Select
rngOrigSelection.Select 'trouble with annoing excel feature to unselect graphs
Application.ScreenUpdating = True
graf.Select
kvantifikator = 1
Do
shoda = False
For Each grafx In ThisWorkbook.Sheets("List1").ChartObjects
If grafx.Name = jmenografu Then
shoda = True
jmenografu = jmenografu & "(" & kvantifikator & ")"
kvantifikator = kvantifikator + 1
End If
Next grafx
'this checks if graph has younger brother in sheet
'but no we get to the part that matter do not bother playing with source of the graph because I have found it is quite hard to make it work properly
Loop Until shoda = False
'here it starts
ActiveChart.Parent.Name = jmenografu
ActiveChart.SeriesCollection.NewSeries 'add only series!
vkladacistring = "=List1!R12C" & hledejsloupec.Column & ":R" & najdiposlradek.Row & "C" & hledejsloupec.Column 'insert this into series
ActiveChart.SeriesCollection(1).Values = vkladacistring
vkladacistring = "=List1!R11C" & hledejsloupec.Column
ActiveChart.SeriesCollection(1).Name = vkladacistring
vkladacistring = "=List1!R12C" & hledejsloupec2.Column & ":R" & najdiposlradek.Row & "C" & hledejsloupec2.Column
ActiveChart.SeriesCollection(1).XValues = vkladacistring
'here it ends and onward comes formating
ActiveChart.Legend.Delete
ActiveChart.ChartType = xlConeColClustered
ActiveChart.ClearToMatchStyle
ActiveChart.ChartStyle = 41
ActiveChart.ClearToMatchStyle
ActiveSheet.Shapes(jmenografu).Chart.ChartArea.Format.ThreeD.RotationY = 90
ActiveSheet.Shapes(jmenografu).Chart.ChartArea.Format.ThreeD.RotationX = 0
ActiveChart.Axes(xlValue).MajorUnit = 8.33333333333333E-02
ActiveChart.Axes(xlValue).MinimumScale = 0.25
ActiveChart.Walls.Format.Fill.Visible = msoFalse
ActiveChart.Axes(xlCategory).MajorUnitScale = xlMonths
ActiveChart.Axes(xlCategory).MajorUnit = 1
ActiveChart.Axes(xlCategory).BaseUnit = xlDays
End If
End If
Call aktualizacelistboxu
ThisWorkbook.Sheets("List1").CommandButton6.BackColor = &H8000000D
ThisWorkbook.Sheets("List1").CommandButton6.ForeColor = &H0&
End Sub
the result i found is that you cannot keep formating completely when you close chart because source of chart doesnt work very well and when you delete it some format will be lost
I will post my actualization of chart as well
Sub aktualizacegrafu()
Dim grafx As ChartObject
Dim hledejsloupec As Object
Dim hledejsloupec2 As Object
Dim vkladacistring As String
Dim najdiposlradek As Object
For Each grafx In ThisWorkbook.Sheets("List1").ChartObjects
prvnislovo = Left(grafx.Name, InStr(1, grafx.Name, "_") - 1)
druheslovo = Right(grafx.Name, Len(grafx.Name) - InStr(1, grafx.Name, "_"))
'now it checks the names of charts .. the data loads from respective columns that are named the same way so I ussualy choose what statistic I want by choosing the columns needed
'for example I want to reflect my arrivals to work according to the hours I worked or to the date so I set 1st option to arrival and 2nd to date
grafx.Activate
Set najdiposlradek = Range("A:A").Find(What:=Date, LookIn:=xlValues)
Set hledejsloupec = Range("11:11").Find(What:=prvnislovo, LookIn:=xlValues)
If hledejsloupec Is Nothing Then
MsgBox "Hodnota v grafu již není mezi sloupci v tabulce. Aktualizace grafu " & grafx.Name & " bude ukončena."
Else
Set hledejsloupec2 = Range("11:11").Find(What:=druheslovo, LookIn:=xlValues)
If hledejsloupec2 Is Nothing Then
MsgBox "Hodnota v grafu již není mezi sloupci v tabulce. Aktualizace grafu " & grafx.Name & " bude ukončena."
Else
here it enters string that contains adress of desired cell I always enter it as string cause its easier to see with debug.print what is being entered
result looks like this List means Sheet in czech
activechart.seriescollection(1).values=List1!R12C1:R13C16
activechart.seriescollection(1).name=List1!R1C1:R1C15
vkladacistring = "=List1!R12C" & hledejsloupec.Column & ":R" & najdiposlradek.Row & "C" & hledejsloupec.Column
ActiveChart.SeriesCollection(1).Values = vkladacistring
vkladacistring = "=List1!R11C" & hledejsloupec.Column
ActiveChart.SeriesCollection(1).Name = vkladacistring
vkladacistring = "=List1!R12C" & hledejsloupec2.Column & ":R" & najdiposlradek.Row & "C" & hledejsloupec2.Column
ActiveChart.SeriesCollection(1).XValues = vkladacistring
End If
End If
Next grafx
Call aktualizacelistboxu
End Sub
so result of this is when you actually have a chart already but want to make slight changes to the area it applies to then it keeps the formating
hope this helped a bit if not I am sorry if it did keep the revard. It just got me curious because I was solving the same problem recently
if you need any further explanation comment this and I will try to explain

Choose from Excel dropdown programmatically

I want to write a macro that will pick a particular value (in my case, stored in cell A1) from a dropdown list (in my case, in cell D6).
Here's what I have so far:
sr_par2 = Array ("TEXT", 'TEXT2", "TEXT3")
sr = Range("A1").Value
(...)
Dim i As Integer
i = 0
Range("D6").Select
Do While (sr <> ActiveCell.FormulaR1C1)
Range("D6").Select
ActiveCell.FormulaR1C1 = sr_par2(i)
i = i + 1
Loop
Is this what you are trying? I have commented the code so that you will not have a problem understanding it. Still if you do then simply ask :)
Sub Sample()
Dim ws As Worksheet
Dim rngIn As Range, rngOut As Range
Dim MyAr
Dim sFormula As String
Dim i As Long
'~~> Replace this with the relevant worksheet
Set ws = ThisWorkbook.Sheets("Sheet1")
With ws
'~~> Set your input and output range here
Set rngIn = .Range("A1")
Set rngOut = .Range("D6")
'~~> Get the validation list if there is one
On Error Resume Next
sFormula = rngOut.Validation.Formula1
On Error GoTo 0
If sFormula = "" Then
'~~> If no validation list then directly populate the value
rngOut.Value = rngIn.Value
Else
'validation list TEXT1,TEXT2,TEXT3
MyAr = Split(sFormula, ",")
'~~> Loop through the list and compare
For i = LBound(MyAr) To UBound(MyAr)
If UCase(Trim(rngIn.Value)) = UCase(Trim(MyAr(i))) Then
rngOut.Value = MyAr(i)
Exit For
End If
Next i
'~~> Check if the cell is still blank. If it is then it means that
'~~> Cell A1 has a value which is not part of the list
If Len(Trim(rngOut.Value)) = 0 Then
MsgBox "The value in " & rngOut.Address & _
" cannot be set as the value you are copying is not part of the list"
End If
End If
End With
End Sub
If I understood correctly, this should do what you want :
sr_par2 = Array("TEXT", "TEXT2", "TEXT3")
sr = Range("A1").Value
Dim i As Integer
i = 0
On Error GoTo Handler
Do While (sr <> sr_par2(i))
i = i + 1
Loop
Range("D6").FormulaR1C1 = sr_par2(i)
Exit Sub
Handler:
MsgBox "Value not in the list", vbCritical + vbOKOnly, "Value not found"

Store ActiveWindow.SelectedSheets as an object to refer to later

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