作为报告生成器大修的一部分,我看到了我认为是低效的代码。这部分代码在生成主报表后运行,用于在逻辑位置设置分页符。标准是这样的:
- 每个站点都从一个新页面开始。
- 不允许跨页面破坏组。
代码遵循上述格式:2 个循环完成这些工作。
这是原始代码(对不起,长度):
Public Sub PageBreak(ByRef wstWorksheet As Excel.Worksheet, ByVal pctProgress As ProgressCtl.ProgressControl)
Dim breaksMoved As Integer
Dim p As HPageBreak
Dim i As Integer
'Used as a control value
breaksMoved = 1
' Marks that no rows/columns are to be repeated on each page
wstWorksheet.Activate
wstWorksheet.PageSetup.PrintTitleRows = ""
wstWorksheet.PageSetup.PrintTitleColumns = ""
'If this isn't performed beforehand, then the HPageBreaks object isn't available
Range("A3").Select
ActiveWindow.View = xlPageBreakPreview
'Defaults the print area to be the entire sheet
wstWorksheet.DisplayPageBreaks = False
wstWorksheet.PageSetup.PrintArea = ""
Range("$B$4").Select
' add breaks after each site
Do While ActiveCell.Row <= wstWorksheet.UsedRange.Rows.Count
If ActiveCell.FormulaR1C1 = "Site ID" Then
ActiveCell.PageBreak = xlPageBreakManual
End If
ActiveCell.Offset(1, 0).Activate
pctProgress.ProgressText = "Row " & CStr(ActiveCell.Row) & " of " & CStr(wstWorksheet.UsedRange.Rows.Count)
Loop
Dim passes As Long
Do While breaksMoved = 1
passes = passes + 1
breaksMoved = 0
For i = 1 To wstWorksheet.HPageBreaks.Count - 1
Set p = wstWorksheet.HPageBreaks.Item(i)
'Selects the first page break
Range(p.Location.Address).Select
'Sets the ActiveCell to 1 row above the page break
ActiveCell.Offset(-1, 0).Activate
'Move the intended break point up to the first blank section
Do While Not ActiveCell.FormulaR1C1 = ""
ActiveCell.Offset(-1, 0).Activate
breaksMoved = 1
Loop
'Add the page break
If ActiveCell.FormulaR1C1 <> "Site ID" Then
ActiveCell.Offset(1, 0).Activate
wstWorksheet.HPageBreaks.Add ActiveCell
End If
pctProgress.ProgressText = "Set break point " & CStr(passes) & "." & CStr(i)
Next
Loop
'Reset the view to normal
wstWorksheet.DisplayPageBreaks = True
ActiveWindow.View = xlNormalView
Range("A3").Select
End Sub
看到改进的空间,我开始修改它。作为新要求之一,想要报告的人在打印之前手动删除页面。所以我在另一个页面上添加了复选框并复制了选中的项目。为了缓解我使用命名范围的问题。我使用这些命名范围来满足第一个要求:
' add breaks after each site
For Each RangeName In ActiveWorkbook.Names
If Mid(RangeName.Name, 1, 1) = "P" Then
Range(RangeName).Activate
ActiveCell.Offset(Range(RangeName).Rows.Count - 1, 0).Select
ActiveCell.PageBreak = xlPageBreakManual
End If
Next RangeName
所有范围都以 P_ 为前缀(表示父级)。在我的简短的 4 站点报告和更具挑战性的 15 站点报告中,使用蹩脚的 Now() 粗略计时风格慢了 1 秒。它们分别有 606 和 1600 行。
1秒还不错。让我们看看下一个标准。每个逻辑组被一个空白行分隔,因此最简单的方法是找到下一个分页符,后退一步,直到找到下一个空白行并插入新的分页符。冲洗并重复。
那么为什么原版会多次运行呢?我们也可以改进它(循环外的样板是相同的)。
Dim i As Long
Dim oPageBreak As HPageBreak
Do While i < shtDeliveryVariance.HPageBreaks.Count - 1
i = i + 1
pctProgress.ProgressText = "Setting Page Break " & CStr(i) & " of " & CStr(shtDeliveryVariance.HPageBreaks.Count)
Set oPageBreak = shtDeliveryVariance.HPageBreaks.Item(i)
' select the page break
Range(oPageBreak.Location.Address).Select
ActiveCell.Offset(-1, 0).Activate
' move up to a free row
Do While Not ActiveCell.FormulaR1C1 = ""
ActiveCell.Offset(-1, 0).Activate
Loop
'Add the page break
If ActiveCell.FormulaR1C1 <> "Site ID" Then
ActiveCell.Offset(1, 0).Activate
shtDeliveryVariance.HPageBreaks.Add ActiveCell
End If
Loop
一次通过,也更优雅。但它快了多少?在小型测试中,与原来的 45 秒相比需要 54 秒,而在较大的测试中,我的代码再次变慢,为 153 到 130 秒。这也是 3 次运行的平均值。
所以我的问题是:为什么我的新代码比原来的代码慢得多,尽管我的看起来更快,我能做些什么来加快代码的速度?
注意:Screen.Updating 等已关闭,Calculation 等也已关闭。