gra*_*eds 3 excel vba page-break
作为报告生成器的大修的一部分,我看到了我认为效率低下的代码.这部分代码在生成主报表后运行,以便在逻辑位置设置分页符.标准是这样的:
代码遵循以上格式: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
Run Code Online (Sandbox Code Playgroud)
看到改进的空间我着手修改这个.作为新要求之一,人们想要报告是在打印之前手动删除页面.所以我在另一个页面上添加了复选框并复制了选中的项目.为了方便我使用命名范围.我使用这些命名范围来满足第一个要求:
' 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
Run Code Online (Sandbox Code Playgroud)
所有范围都以P_为前缀(对于父级).使用蹩脚的Now()风格的粗略时间,这在我的短4站点报告和更具挑战性的15站点报告上慢了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
Run Code Online (Sandbox Code Playgroud)
一次通过也更优雅.但它快多少?在较小的测试中,与原始的45秒相比需要54秒,而在较大的测试中,我的代码在153到130秒时再次变慢.这也是3次运行的平均值.
所以我的问题是:为什么我的新代码比原版慢得多,尽管我看起来更快,我该怎么做才能加快代码的缓慢?
注意:Screen.Updating等已经关闭,计算等.
Oor*_*ang 13
我看到代码中的几个地方有改进的余地:
我重构了原始代码,为您提供了一些这些想法的示例.在不知道您的数据布局的情况下,很难确定此代码是否100%有效,因此我会仔细检查它是否存在逻辑错误.但它应该让你开始.
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
Run Code Online (Sandbox Code Playgroud)