Excel page breaks via VBA

前端 未结 3 565
囚心锁ツ
囚心锁ツ 2020-12-19 23:35

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

相关标签:
3条回答
  • 2020-12-20 00:09

    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?

    0 讨论(0)
  • 2020-12-20 00:10

    I see room for improvement in a couple spots in your code:

    1. Don't access properties that are implemented slowly, like usedrange.rows.count more than once(particularly inside a loop) unless you think they may have changes. Instead store them in a variable.
    2. Don't do text comparisons if you can avoid it (Ex: .Value = ""), instead use the LenB function to check for emptiness, it will execute faster as it's just reading the length of the string header instead of launching into a byte by byte string comparison. (You might enjoy this for reading.)
    3. Don't use "Activate" or "Select" to move around the ActiveCell, just access the range directly.
    4. When looping, structure your loop to have to perform as few tests as possible. If the loop must always execute once, then you want a post-test loop.
    5. Make sure you have the Excel interface locked, as running events and screen-updating etc, can slow your code down a lot. (Especially events.)
    6. Finally, I noticed that you are making assumptions about the case of "Site ID", unless there is no possible way it could be cased otherwise, it's best to do a case insensitive comparison. If you know for a fact that it will be Cased that way you can of course remove the calls to LCase$ that I added.

    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
    
    0 讨论(0)
  • 2020-12-20 00:10

    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.

    0 讨论(0)
提交回复
热议问题