As part of an overhaul of a report generator I saw what I believed to be inefficient code. This part of the code runs after the main report is generated to set the page brea
I took a quick view of your code and my first thought is that this line:
pctProgress.ProgressText = "Setting Page Break " & CStr(i) & " of " & CStr(shtDeliveryVariance.HPageBreaks.Count)
may be a cause of some of the delay. The location of this code means that the system has to go and recalculate the .Count value since it comes at the beginning of the loop in your code, but this recalculation does not happen in the original.
Other thoughts:
Depending on the spreadsheet size, going out and remeasuring this value may be slowing things down. Why not just manually increment a breaks count tracking variable when you actually perform the addition of a new break instead of having the system go and count it, or get rid of the counting in the loop (since you're not updating the display anyways during this process) and put the counting of page breaks in to its own code segment that runs through the content at the end of the whole formatting process when a final number of page breaks can easily be determined with a single call?
I see room for improvement in a couple spots in your code:
I refactored the original code to give you an example of some of these ideas. Without knowing your data layout, it's hard to be sure if this code is 100% valid, so I would double check it for logic errors. But it should get you started.
Public Sub PageBreak(ByRef wstWorksheet As Excel.Worksheet, ByVal pctProgress As ProgressCtl.ProgressContro)
Const lngColSiteID_c As Long = 2&
Const lngColSiteIDSecondary_c As Long = 1&
Const lngOffset_c As Long = 1&
Dim breaksMoved As Boolean
Dim lngRowBtm As Long
Dim lngRow As Long
Dim p As Excel.HPageBreak
Dim i As Integer
Dim passes As Long
Dim lngHBrksUprBnd As Long
LockInterface True
' Marks that no rows/columns are to be repeated on each page
wstWorksheet.Activate
wstWorksheet.PageSetup.PrintTitleRows = vbNullString
wstWorksheet.PageSetup.PrintTitleColumns = vbNullString
'If this isn't performed beforehand, then the HPageBreaks object isn't available
'***Not true:)***
'ActiveWindow.View = xlPageBreakPreview
'Defaults the print area to be the entire sheet
wstWorksheet.DisplayPageBreaks = False
wstWorksheet.PageSetup.PrintArea = vbNullString
' add breaks after each site
lngRowBtm = wstWorksheet.UsedRange.Rows.Count
For lngRow = 4& To lngRowBtm
'LCase is to make comparison case insensitive.
If LCase$(wstWorksheet.Cells(lngRow, lngColSiteID_c).value) = "site id" Then
wstWorksheet.Cells(lngRow, lngColSiteID_c).PageBreak = xlPageBreakManual
End If
pctProgress.ProgressText = ("Row " & CStr(lngRow)) & (" of " & CStr(lngRowBtm))
Next
lngHBrksUprBnd = wstWorksheet.HPageBreaks.Count - lngOffset_c
Do 'Using post test.
passes = passes + lngOffset_c
breaksMoved = False
For i = 1 To lngHBrksUprBnd
Set p = wstWorksheet.HPageBreaks.Item(i)
'Move the intended break point up to the first blank section
lngRow = p.Location.Row - lngOffset_c
For lngRow = p.Location.Row - lngOffset_c To 1& Step -1&
'Checking the LenB is faster than a string check.
If LenB(wstWorksheet.Cells(lngRow, lngColSiteIDSecondary_c).Formula) = 0& Then
lngRow = lngRow - lngOffset_c
If LCase$(wstWorksheet.Cells(lngRow, lngColSiteIDSecondary_c).value) = "site id" Then
breaksMoved = True
wstWorksheet.HPageBreaks.Add wstWorksheet.Cells(lngRow, lngColSiteIDSecondary_c)
End If
Exit For
End If
Next
pctProgress.ProgressText = "Set break point " & (CStr(passes) & "." & CStr(i))
Next
Loop While breaksMoved
LockInterface False
End Sub
Private Sub LockInterface(ByVal interfaceOff As Boolean)
With Excel.Application
If interfaceOff Then
.ScreenUpdating = False
.EnableEvents = False
.Cursor = xlWait
.StatusBar = "Working..."
Else
.ScreenUpdating = True
.EnableEvents = True
.Cursor = xlDefault
.StatusBar = False
End If
End With
End Sub
The easy answer is that you use ActiveCell
and Select
and Activate
. Excel actually selects the cells as your code is running, making the code run slower (as you've noticed).
I would recommend using a Range
as a reference and do all the tests "in memory".
Dim a range for tracking (dim rngCurrentCell as range
) and use that instead of the selecting the cells.
So, for the first appearance of Select
in your code Range("A3").Select
, you would 'Set' it as Set rngCurrentCell = Range("A3")
. The same for the Next B4 line.
Then:
' add breaks after each site
Do While ActiveCell.Row <= wstWorksheet.UsedRange.Rows.Count
If ActiveCell.FormulaR1C1 = "Site ID" Then
ActiveCell.PageBreak = xlPageBreakManual
End If
' Offset the row by one and set our new range
set rngCurrentCell = rngCurrentCell.Offset(1, 0)
pctProgress.ProgressText = "Row " & CStr(ActiveCell.Row) & " of " & CStr(wstWorksheet.UsedRange.Rows.Count)
Loop
And so forth.
Now to test values use the same syntax as the ActiveCell
.
If you have any questions, let me know.