How to find the true Last Cell in any Worksheet

前端 未结 5 1818
遇见更好的自我
遇见更好的自我 2021-01-13 01:12

This question is now answered elegantly, thanks to Chris Neilsen, see the answer below. It is the one I will use from now on. The solution reliably finds the last cell in

相关标签:
5条回答
  • 2021-01-13 01:39

    Based on @Gary's method, but optimised to work fast when the UsedRange is Large but not reflective of the True Last Cell (as can happen when a cell on the extreames of a worksheet is inadvertently formatted)

    It works by, starting with the UsedRange, counting cells in half the range and halving the referenced test range above or below the split point depending on the count result, and repeating until it reaches < 5 rows/columns, then uses a linear search from there.

    Function TrueLastCell( _
      ws As Excel.Worksheet, _
      Optional lRealLastRow As Long, _
      Optional lRealLastColumn As Long _
      ) As Range
        Dim lrTo As Long, lcTo As Long, i As Long
        Dim lrFrom As Long, lcFrom As Long
        Dim wf As WorksheetFunction
        Set wf = Application.WorksheetFunction
    
        With ws.UsedRange
            lrTo = .Rows.Count
            lcTo = .Columns.Count
    
            lrFrom = lrTo \ 2
            Do While (lrTo - lrFrom) > 2
                If wf.CountA(.Rows(lrFrom & ":" & lrTo)) = 0 Then
                    lrTo = lrFrom - 1
                    lrFrom = lrFrom \ 2
                Else
                    lrFrom = (lrTo + lrFrom) \ 2
                End If
            Loop
    
            If wf.CountA(.Rows(lrFrom & ":" & lrTo)) = 0 Then
                lrTo = lrFrom - 1
            Else
                For i = lrTo To lrFrom Step -1
                    If wf.CountA(.Rows(i)) <> 0 Then
                        Exit For
                    End If
                Next i
                lrTo = i
            End If
    
            lcFrom = lcTo \ 2
            Do While (lcTo - lcFrom) > 2
                If wf.CountA(Range(.Columns(lcFrom), .Columns(lcTo))) = 0 Then
                    lcTo = lcFrom - 1
                    lcFrom = lcFrom \ 2
                Else
                    lcFrom = (lcTo + lcFrom) \ 2
                End If
            Loop
    
    
            If wf.CountA(Range(.Columns(lcFrom), .Columns(lcTo))) = 0 Then
                lcTo = lcFrom - 1
            Else
                For i = lcTo To 1 Step -1
                    If wf.CountA(.Columns(i)) <> 0 Then
                        Exit For
                    End If
                Next i
                lcTo = i
            End If
    
            Set TrueLastCell = .Cells(lrTo, lcTo)
            lRealLastRow = lrTo + .Row - 1
            lRealLastColumn = lcTo + .Column - 1
        End With
    End Function
    

    On my hardware it runs in about 2ms on a sheet with UsedRange extending to the sheet limits and True Last Cell at F5, and 0.1ms when UsedRange reflects the True Last Cell at F5

    Edit: slightly more optimised search

    0 讨论(0)
  • 2021-01-13 01:47

    Great question.

    As you note, Find failes with AutoFilter. As an alternative to looping through the filters, or the range loop used by another answer you could

    • Copy the sheet and remove the AutoFilter
    • use xlformulas in the Find routine which caters to hidden cells

    So something lke this:

    Sub GetRange()
    'by Brettdj, http://stackoverflow.com/questions/8283797/return-a-range-from-a1-to-the-true-last-used-cell
        Dim rng1 As Range
        Dim rng2 As Range
        Dim rng3 As Range
        Dim ws As Worksheet
    
        With Application
            .EnableEvents = False
            .ScreenUpdating = False
        End With
    
        ActiveSheet.Copy
    
        Set ws = ActiveSheet
        With ws
        .AutoFilterMode = False
        Set rng1 = ws.Cells.Find("*", ws.[a1], xlFormulas, , xlByRows, xlPrevious)
        Set rng2 = ws.Cells.Find("*", ws.[a1], xlFormulas, xlPart, xlByColumns, xlPrevious)
        If Not rng1 Is Nothing Then
            Set rng3 = Range([a1], Cells(rng1.Row, rng2.Column))
            MsgBox "Range is " & rng3.Address(0, 0)
            Debug.Print "Brettdj's GetRange gives: Range is " & rng3.Address(0, 0)  'added for this test by ND
            'if you need to actual select the range (which is rare in VBA)
            Application.GoTo rng3
        Else
            MsgBox "sheet is blank", vbCritical
        End If
            .Parent.Close False
        End With
    
    
         With Application
            .EnableEvents = True
            .ScreenUpdating = True
        End With
    
    End Sub
    
    0 讨论(0)
  • 2021-01-13 01:53

    I think you can utilize the .UsedRange property from the Worksheet object. Try below:

    Option Explicit
    
    Function GetTrueLastCell(WS As Worksheet) As Range
        With WS
            If .UsedRange.Count = 1 Then
                Set GetTrueLastCell = .UsedRange
            Else
                Set GetTrueLastCell = .Range(Split(.UsedRange.Address, ":")(1))
            End If
        End With
    End Function
    
    0 讨论(0)
  • 2021-01-13 01:54

    Best way I know to find "true Last Cell" is to use 2 steps:

    1. Pick last cell of UsedRange (i.e. UsedRange.Cells.CountLarge)
    2. Move left & up until you find last non-empty row & column with CountA (i.e. WorksheetFunction.CountA(Range)), as it is fast, and works with Hidden / AutoFiltered / Grouped ranges.

    This takes some time, so I've written an optimized code for the second step. Then I found @Chris' code edited on Nov 30, 2019, and it looked similar, though I was wondering why so different. I compared (...did my best to do apple v apple), and was surprised by the results.

    If my tests are reliable, then all what matters is how many searches you do with CountA. I call it cycle - it is actually the number of CountA functions! My routine does up to 34 cycles, and @Chris' routine seems to do up to 32..80+ cycles. His code seems to test the same ranges repeatedly.

    Please have a look at the test table Link, see my test results in VBA notes, and watch Immediate for your live results. You may test with any content, or even use an ActiveSheet in your own WorkBook. Play with parameters in VBA at "==== PARAMETERS TO BE CHANGED ====". You may zoom to 10%-15% to see painted cells showing the search ranges for each cycle. That's where the number of cycles becomes visible.

    Note: I have not found any side-effects or errors with this so far. I avoid using Range.Find, and changing its parameters behind the scenes. Some users will learn it the hard way... - like I did, when I then replaced text in the entire workbook, just to find it out days later. Note2: This is my first post, please excuse possible glitches here.

    Function GetLastSheetCellRng(ws As Excel.Worksheet) As Range
    'Returns the [Range] of last used cell of the specified [Worksheet], located in the cross-section of the bottom row and right column with non-empty cells
    Dim wf As Excel.WorksheetFunction: Set wf = Application.WorksheetFunction
    Dim Xfound&, Yfound&, Xfirst&, Yfirst&, Xfrom&, Yfrom&, Xto&, Yto As Long
    
    With ws
        '1. step: UsedRange last cell
        Set GetLastSheetCellRng = .UsedRange.Cells(.UsedRange.Cells.CountLarge) 'Getting UsedRange last cell
        Yfound = GetLastSheetCellRng.Row: Xfound = GetLastSheetCellRng.Column
    
        '2. step: Check non-empty cells in UsedRange last cell row & column
        'If not found, then search up for last non-empty row, and search left for last non-empty column
        If (wf.CountA(.Rows(Yfound)) = 0) And (Yfound > 1) Then
            Yto = Yfound
            Yfrom = Yto \ 2
            Yfirst = 0
            Do
                If wf.CountA(.Range(.Rows(Yfrom), .Rows(Yto))) <> 0 Then
                    Yfirst = Yfrom
                    Yfrom = (Yfirst + Yto + 0.5) \ 2
                Else
                    Yto = Yfrom - 1
                    Yfrom = (Yfrom + Yfirst) \ 2
                End If
            Loop Until Yfirst = Yfrom
            If Yfirst = 0 Then
                Yfound = 1 'If no cell found, then 1st row returned
            Else
                Yfound = Yfirst
            End If
        End If
        If (wf.CountA(.Columns(Xfound)) = 0) And (Xfound > 1) Then
            Xto = Xfound
            Xfrom = Xto \ 2
            Xfirst = 0
            Do
                If wf.CountA(.Range(.Columns(Xfrom), .Columns(Xto))) <> 0 Then
                    Xfirst = Xfrom
                    Xfrom = (Xfirst + Xto + 0.5) \ 2
                Else
                    Xto = Xfrom - 1
                    Xfrom = (Xfrom + Xfirst) \ 2
                End If
            Loop Until Xfirst = Xfrom
            If Xfirst = 0 Then
                Xfound = 1 'If no cell found, then 1st column returned
            Else
                Xfound = Xfirst
            End If
        End If
        Set GetLastSheetCellRng = .Cells(Yfound, Xfound)
    End With
    End Function
    
    0 讨论(0)
  • 2021-01-13 02:01

    UsedRange may be erroneous, (it may be too large), but we can start with its outer limits and work inwards:

    Sub TrueLastCell()
        Dim lr As Long, lc As Long, i As Long
        Dim wf As WorksheetFunction
        Set wf = Application.WorksheetFunction
    
        ActiveSheet.UsedRange
        With ActiveSheet.UsedRange
            lr = .Rows.Count + .Row - 1
            lc = .Columns.Count + .Column - 1
        End With
    
        For i = lr To 1 Step -1
            If wf.CountA(Rows(i)) <> 0 Then
                Exit For
            End If
        Next i
    
        For i = lc To 1 Step -1
            If wf.CountA(Cells(lr, i)) <> 0 Then
                MsgBox "The TRUE last cell is " & Cells(lr, i).Address(0, 0)
                Exit Sub
            End If
        Next i
    End Sub
    

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