Błąd podczas wyszukiwania ostatnio używanej komórki w Excelu za pomocą VBA

179

Kiedy chcę znaleźć ostatnio używaną wartość komórki, używam:

Dim LastRow As Long

LastRow = Range("E4:E48").End(xlDown).Row

Debug.Print LastRow

Otrzymuję nieprawidłowy wynik, gdy wstawiam pojedynczy element do komórki. Ale gdy wstawię więcej niż jedną wartość do komórki, wynik jest poprawny. Jaki jest tego powód?

James
źródło

Odpowiedzi:

309

UWAGA : Zamierzam uczynić z tego „postu z jednego miejsca”, w którym można znaleźć Correctsposób, aby znaleźć ostatni wiersz. Dotyczy to również najlepszych praktyk, które należy stosować przy znajdowaniu ostatniego rzędu. Dlatego będę go aktualizować za każdym razem, gdy napotkam nowy scenariusz / informacje.


Niewiarygodne sposoby znalezienia ostatniego rzędu

Niektóre z najczęstszych sposobów znajdowania ostatniego rzędu, które są wysoce niewiarygodne i dlatego nigdy nie powinny być używane.

  1. UsedRange
  2. xlDown
  3. CountA

UsedRangepowinna nigdy być używane, aby znaleźć ostatnią komórkę, która ma dane. Jest wysoce zawodny. Wypróbuj ten eksperyment.

Wpisz coś w komórce A5. Teraz, kiedy obliczysz ostatni wiersz dowolną z metod podanych poniżej, da ci 5. Teraz pokoloruj komórkę na A10czerwono. Jeśli teraz użyjesz dowolnego z poniższych kodów, nadal otrzymasz 5. Jeśli skorzystasz z Usedrange.Rows.Counttego, co otrzymasz? To nie będzie 5.

Oto scenariusz pokazujący, jak UsedRangedziała.

wprowadź opis zdjęcia tutaj

xlDown jest równie niewiarygodne.

Rozważ ten kod

lastrow = Range("A1").End(xlDown).Row

Co by się stało, gdyby tylko jedna komórka ( A1) posiadała dane? W końcu dotrzesz do ostatniego wiersza w arkuszu! To tak, jakby wybrać komórkę, A1a następnie nacisnąć Endklawisz, a następnie nacisnąć Down Arrowklawisz. Zapewni to również niewiarygodne wyniki, jeśli w zakresie będą puste komórki.

CountA jest również niewiarygodna, ponieważ da ci niepoprawny wynik, jeśli pomiędzy nimi będą puste komórki.

A więc należy unikać stosowania UsedRange, xlDowni CountAaby znaleźć ostatnią komórkę.


Znajdź ostatni wiersz w kolumnie

Aby znaleźć ostatni wiersz w kolumnie E, użyj tego

With Sheets("Sheet1")
    LastRow = .Range("E" & .Rows.Count).End(xlUp).Row
End With

Jeśli zauważysz, że mamy .wcześniej Rows.Count. Często zdecydowaliśmy się to zignorować. Zobacz TO pytanie dotyczące możliwego błędu, który możesz otrzymać. Zawsze radzę używać .przed Rows.Counti Columns.Count. To pytanie jest klasycznym scenariuszem, w którym kod zawiedzie, ponieważ Rows.Countzwraca wyniki 65536dla Excela 2003 i wcześniejszych oraz 1048576dla Excela 2007 i późniejszych. Podobnie Columns.Countzwraca 256i 16384odpowiednio.

Powyższy fakt, że Excel 2007+ ma 1048576wiersze, podkreśla również fakt, że zawsze powinniśmy zadeklarować zmienną, która będzie przechowywać wartość wiersza, ponieważ Longzamiast tego Integerpojawi się Overflowbłąd.

Zauważ, że to podejście spowoduje pominięcie ukrytych wierszy. Patrząc wstecz na mój zrzut ekranu powyżej dla kolumny A , jeśli wiersz 8 byłby ukryty, to podejście powróciłoby 5zamiast 8.


Znajdź ostatni wiersz w arkuszu

Aby znaleźć Effectiveostatni wiersz w arkuszu, użyj tego. Zwróć uwagę na użycie Application.WorksheetFunction.CountA(.Cells). Jest to wymagane, ponieważ jeśli nie ma komórek z danymi w arkuszu, to .Findda ciRun Time Error 91: Object Variable or With block variable not set

With Sheets("Sheet1")
    If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
        lastrow = .Cells.Find(What:="*", _
                      After:=.Range("A1"), _
                      Lookat:=xlPart, _
                      LookIn:=xlFormulas, _
                      SearchOrder:=xlByRows, _
                      SearchDirection:=xlPrevious, _
                      MatchCase:=False).Row
    Else
        lastrow = 1
    End If
End With

Znajdź ostatni wiersz w tabeli (ListObject)

Obowiązują te same zasady, na przykład aby uzyskać ostatni wiersz w trzeciej kolumnie tabeli:

Sub FindLastRowInExcelTableColAandB()
Dim lastRow As Long
Dim ws As Worksheet, tbl as ListObject
Set ws = Sheets("Sheet1")  'Modify as needed
'Assuming the name of the table is "Table1", modify as needed
Set tbl = ws.ListObjects("Table1")

With tbl.ListColumns(3).Range
    lastrow = .Find(What:="*", _
                After:=.Cells(1), _
                Lookat:=xlPart, _
                LookIn:=xlFormulas, _
                SearchOrder:=xlByRows, _
                SearchDirection:=xlPrevious, _
                MatchCase:=False).Row
End With

End Sub
Siddharth Rout
źródło
9
@phan: Wpisz coś w komórce A5. Teraz, gdy obliczysz ostatni wiersz dowolną z metod podanych powyżej, da ci 5. Teraz pokoloruj komórkę A10 na czerwono. Jeśli teraz użyjesz dowolnego z powyższych kodów, nadal otrzymasz 5. Jeśli skorzystasz z Usedrange.Rows.Counttego, co otrzymujesz? To nie będzie 5. Usedrange jest bardzo zawodny w znalezieniu ostatniego rzędu.
Siddharth Rout
6
Zauważ, że .Find niestety psuje ustawienia użytkownika w oknie dialogowym Znajdź - tzn. Excel ma tylko 1 zestaw ustawień dla okna dialogowego, a użycie .Find je zastępuje. Inną sztuczką jest nadal używanie UsedRange, ale używanie go jako absolutnego (ale zawodnego) maksimum, od którego określa się prawidłowe maksimum.
Carl Colijn
4
@CarlColijn: Nie nazwałbym tego bałaganem. :) Excel po prostu remembersostatnie ustawienie. Nawet jeśli wykonasz ręcznie a Find, pamięta ostatnie ustawienie, które jest dobrodziejstwem, jeśli ktoś zna ten „fakt”
Siddharth Rout
3
@KeithPark: Proszę śmiało :) Wiedza ma znaczenie tylko wtedy, gdy się rozprzestrzenia :)
Siddharth Rout
9
Myślę, że twój opis UsedRange( znajdowanie ostatniej komórki zawierającej dane jest bardzo zawodny ) jest mylący. UsedRangepo prostu nie jest przeznaczony do tego celu, nawet jeśli w niektórych przypadkach może dać poprawny wynik. Myślę, że zaproponowany eksperyment zwiększa zamieszanie. Wynik uzyskany za pomocą UsedRange($ A $ 1: $ A $ 8) nie zależy od pierwszego wprowadzenia danych i ich usunięcia. Liczba po prawej stronie pozostanie taka sama, nawet bez wpisania danych i ich usunięcia. Proszę zobaczyć moją odpowiedź.
sancho.s ReinstateMonicaCellio
34

Uwaga: odpowiedź została uzasadniona tym komentarzem . Cel UsedRangejest inny niż wymieniony w powyższej odpowiedzi.

Jeśli chodzi o właściwy sposób znalezienia ostatnio używanej komórki, najpierw należy zdecydować, co uważa się za wykorzystane , a następnie wybrać odpowiednią metodę . Rozumiem co najmniej trzy znaczenia:

  1. Używane = niepuste, tzn. Posiadające dane .

  2. Używany = „... w użyciu, co oznacza sekcję zawierającą dane lub formatowanie ”. Zgodnie z oficjalną dokumentacją jest to kryterium stosowane przez program Excel podczas zapisywania. Zobacz także tę oficjalną dokumentację . Jeśli nie jest się tego świadomym, kryterium może dawać nieoczekiwane wyniki, ale może być również celowo wykorzystywane (rzadziej, na pewno), np. W celu wyróżnienia lub wydrukowania określonych regionów, które ostatecznie mogą nie zawierać danych. I, oczywiście, pożądane jest jako kryterium zakresu do zastosowania podczas zapisywania skoroszytu, aby nie stracić części pracy.

  3. Używany = „... w użyciu, co oznacza sekcję zawierającą dane lub formatowanielub formatowanie warunkowe. To samo co 2., ale także z komórkami, które są celem dowolnej reguły formatowania warunkowego.

Jak znaleźć ostatnio używaną komórkę, zależy od tego, czego chcesz (twoje kryterium) .

W przypadku kryterium 1 proponuję przeczytać tę odpowiedź . Zauważ, że UsedRangejest cytowany jako niewiarygodny. Myślę, że jest to mylące (tzn. „Niesprawiedliwe” UsedRange), ponieważ UsedRangepo prostu nie ma na celu zgłoszenia ostatniej komórki zawierającej dane. Dlatego nie należy go stosować w tym przypadku, jak wskazano w tej odpowiedzi. Zobacz także ten komentarz .

Dla kryterium 2 UsedRangejest najbardziej niezawodną opcją w porównaniu z innymi opcjami również przeznaczonymi do tego zastosowania. Nie trzeba nawet zapisywać skoroszytu, aby upewnić się, że ostatnia komórka została zaktualizowana. Ctrl+ Endprzejdzie do niewłaściwej komórki przed zapisaniem („Ostatnia komórka nie jest resetowana, dopóki nie zapiszesz arkusza roboczego”, z http://msdn.microsoft.com/en-us/library/aa139976%28v=office.10% 29.aspx . Jest to stare odniesienie, ale pod tym względem ważne).

Dla kryterium 3 nie znam żadnej wbudowanej metody . Kryterium 2 nie uwzględnia formatowania warunkowego. Można mieć sformatowane komórki oparte na formułach, które nie są wykrywane przez + UsedRangelub . Na rysunku ostatnia komórka to B3, ponieważ formatowanie zostało do niej wyraźnie zastosowane. Komórki B6: D7 mają format wywodzący się z reguły formatowania warunkowego, a nawet nie jest to wykrywane . Uwzględnienie tego wymagałoby programowania VBA.CtrlEndUsedRange

wprowadź opis zdjęcia tutaj


Co do twojego konkretnego pytania : jaki jest tego powód?

Twój kod wykorzystuje pierwszą komórkę w swoim przedziale E4: E48 jako trampoliny, na skoki w dół End(xlDown).

„Błędne” dane wyjściowe zostaną uzyskane, jeśli w twoim zakresie nie ma niepustych komórek oprócz pierwszego. Następnie przeskakujesz w ciemności , tj. W dół arkusza roboczego (powinieneś zauważyć różnicę między pustym i pustym ciągiem !).

Uwaga:

  1. Jeśli twój zakres zawiera niesąsiadujące niepuste komórki, to również da zły wynik.

  2. Jeśli istnieje tylko jedna niepusta komórka, ale nie jest to pierwsza, kod nadal będzie dawał poprawny wynik.

sancho.s ReinstateMonicaCellio
źródło
3
Zgadzam się, że najpierw należy zdecydować, co uważa się za wykorzystane . Widzę co najmniej 6 znaczeń. Komórka ma: 1) dane, tj. Formułę, co może skutkować pustą wartością; 2) wartość, tj. Nieprzezroczysta formuła lub stała; 3) formatowanie; 4) formatowanie warunkowe; 5) kształt (wraz z komentarzem) nakładający się na komórkę; 6) zaangażowanie w tabelę (obiekt listy). Którą kombinację chcesz przetestować? Niektóre (takie jak Tabele) mogą być trudniejsze do przetestowania, a niektóre mogą być rzadkie (takie jak kształt poza zakresem danych), ale inne mogą się różnić w zależności od sytuacji (np. Formuły z pustymi wartościami).
GlennFromIowa
20

Stworzyłem tę funkcję one-stop do określania ostatniego wiersza, kolumny i komórki, czy to dla danych, sformatowanych (zgrupowanych / skomentowanych / ukrytych) komórek, czy formatowania warunkowego .

Sub LastCellMsg()
    Dim strResult As String
    Dim lngDataRow As Long
    Dim lngDataCol As Long
    Dim strDataCell As String
    Dim strDataFormatRow As String
    Dim lngDataFormatCol As Long
    Dim strDataFormatCell As String
    Dim oFormatCond As FormatCondition
    Dim lngTempRow As Long
    Dim lngTempCol As Long
    Dim lngCFRow As Long
    Dim lngCFCol As Long
    Dim strCFCell As String
    Dim lngOverallRow As Long
    Dim lngOverallCol As Long
    Dim strOverallCell As String

    With ActiveSheet

        If .ListObjects.Count > 0 Then
            MsgBox "Cannot return reliable results, as there is at least one table in the worksheet."
            Exit Sub
        End If

        strResult = "Workbook name: " & .Parent.Name & vbCrLf
        strResult = strResult & "Sheet name: " & .Name & vbCrLf

        'DATA:
        'last data row
        If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
            lngDataRow = .Cells.Find(What:="*", _
             After:=.Range("A1"), _
             Lookat:=xlPart, _
             LookIn:=xlFormulas, _
             SearchOrder:=xlByRows, _
             SearchDirection:=xlPrevious, _
             MatchCase:=False).Row
        Else
            lngDataRow = 1
        End If
        'strResult = strResult & "Last data row: " & lngDataRow & vbCrLf

        'last data column
        If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
            lngDataCol = .Cells.Find(What:="*", _
             After:=.Range("A1"), _
             Lookat:=xlPart, _
             LookIn:=xlFormulas, _
             SearchOrder:=xlByColumns, _
             SearchDirection:=xlPrevious, _
             MatchCase:=False).Column
        Else
            lngDataCol = 1
        End If
        'strResult = strResult & "Last data column: " & lngDataCol & vbCrLf

        'last data cell
        strDataCell = Replace(Cells(lngDataRow, lngDataCol).Address, "$", vbNullString)
        strResult = strResult & "Last data cell: " & strDataCell & vbCrLf

        'FORMATS:
        'last data/formatted/grouped/commented/hidden row
        strDataFormatRow = StrReverse(Split(StrReverse(.UsedRange.Address), "$")(0))
        'strResult = strResult & "Last data/formatted row: " & strDataFormatRow & vbCrLf

        'last data/formatted/grouped/commented/hidden column
        lngDataFormatCol = Range(StrReverse(Split(StrReverse(.UsedRange.Address), "$")(1)) & "1").Column
        'strResult = strResult & "Last data/formatted column: " & lngDataFormatCol & vbCrLf

        'last data/formatted/grouped/commented/hidden cell
        strDataFormatCell = Replace(Cells(strDataFormatRow, lngDataFormatCol).Address, "$", vbNullString)
        strResult = strResult & "Last data/formatted cell: " & strDataFormatCell & vbCrLf

        'CONDITIONAL FORMATS:
        For Each oFormatCond In .Cells.FormatConditions

            'last conditionally-formatted row
            lngTempRow = CLng(StrReverse(Split(StrReverse(oFormatCond.AppliesTo.Address), "$")(0)))
            If lngTempRow > lngCFRow Then lngCFRow = lngTempRow

            'last conditionally-formatted column
            lngTempCol = Range(StrReverse(Split(StrReverse(oFormatCond.AppliesTo.Address), "$")(1)) & "1").Column
            If lngTempCol > lngCFCol Then lngCFCol = lngTempCol
        Next
        'no results are returned for Conditional Format if there is no such
        If lngCFRow <> 0 Then
            'strResult = strResult & "Last cond-formatted row: " & lngCFRow & vbCrLf
            'strResult = strResult & "Last cond-formatted column: " & lngCFCol & vbCrLf

            'last conditionally-formatted cell
            strCFCell = Replace(Cells(lngCFRow, lngCFCol).Address, "$", vbNullString)
            strResult = strResult & "Last cond-formatted cell: " & strCFCell & vbCrLf
        End If

        'OVERALL:
        lngOverallRow = Application.WorksheetFunction.Max(lngDataRow, strDataFormatRow, lngCFRow)
        'strResult = strResult & "Last overall row: " & lngOverallRow & vbCrLf
        lngOverallCol = Application.WorksheetFunction.Max(lngDataCol, lngDataFormatCol, lngCFCol)
        'strResult = strResult & "Last overall column: " & lngOverallCol & vbCrLf
        strOverallCell = Replace(.Cells(lngOverallRow, lngOverallCol).Address, "$", vbNullString)
        strResult = strResult & "Last overall cell: " & strOverallCell & vbCrLf

        MsgBox strResult
        Debug.Print strResult

    End With

End Sub

Wyniki wyglądają następująco:
określ ostatnią komórkę

Aby uzyskać bardziej szczegółowe wyniki, niektóre wiersze w kodzie można odkomentować:
ostatnia kolumna, wiersz

Istnieje jedno ograniczenie - jeśli w arkuszu znajdują się tabele, wyniki mogą stać się niewiarygodne, dlatego postanowiłem w tym przypadku unikać uruchamiania kodu:

If .ListObjects.Count > 0 Then
    MsgBox "Cannot return reliable results, as there is at least one table in the worksheet."
    Exit Sub
End If
ZygD
źródło
2
@franklin - Właśnie zauważyłem wiadomość z Twoją poprawką, która została odrzucona przez recenzentów. Poprawiłem ten błąd. Już raz użyłem tej funkcji, kiedy jej potrzebowałem i będę jej używał ponownie, więc naprawdę wielkie dzięki, przyjacielu!
ZygD
11

Jedna ważna uwaga, o której należy pamiętać podczas korzystania z rozwiązania ...

LastRow = ws.Cells.Find(What:="*", After:=ws.range("a1"), SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row

... ma zapewnić, że twoja LastRowzmienna jest Longtypu:

Dim LastRow as Long

W przeciwnym razie w niektórych skoroszytach .XLSX wystąpią błędy PRZEPŁYWU w niektórych sytuacjach

To jest moja enkapsulowana funkcja, którą upuszczam do różnych zastosowań kodu.

Private Function FindLastRow(ws As Worksheet) As Long
    ' --------------------------------------------------------------------------------
    ' Find the last used Row on a Worksheet
    ' --------------------------------------------------------------------------------
    If WorksheetFunction.CountA(ws.Cells) > 0 Then
        ' Search for any entry, by searching backwards by Rows.
        FindLastRow = ws.Cells.Find(What:="*", After:=ws.range("a1"), SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
    End If
End Function
Biskup
źródło
8

Dodałbym do odpowiedzi udzielonej przez Siddarth Routa, aby powiedzieć, że wywołanie CountA może zostać pominięte, ponieważ funkcja Znajdź zwraca obiekt Range zamiast numeru wiersza, a następnie przetestuj zwracany obiekt Range, aby sprawdzić, czy jest to Nic (pusty arkusz roboczy) .

Chciałbym również, aby moja wersja dowolnej procedury LastRow zwróciła zero dla pustego arkusza roboczego, a następnie mogę wiedzieć, że jest pusta.

bez zrozumienia
źródło
8

Zastanawiam się, że nikt o tym nie wspominał, ale najłatwiejszym sposobem uzyskania ostatnio używanej komórki jest:

Function GetLastCell(sh as Worksheet) As Range
    GetLastCell = sh.Cells(1,1).SpecialCells(xlLastCell)
End Function

To zasadniczo zwraca tę samą komórkę, którą otrzymujesz przez Ctrl+ Endpo wybraniu Komórki A1.

Uwaga: program Excel śledzi najbardziej prawą dolną komórkę, jaką kiedykolwiek użyto w arkuszu. Więc jeśli na przykład wpiszesz coś w B3, a coś innego w H8, a następnie usuniesz zawartość H8 , naciśnięcie Ctrl+ Endnadal przeniesie Cię do komórki H8 . Powyższa funkcja będzie miała takie samo zachowanie.

dotNET
źródło
2
Last Cellw programie Excel czasami odnosi się do pustej komórki (od Used Range) innej niż Last Used Cell;).
shA.t
1
OP potrzebował tylko ostatniego rzędu, ale masz rację, ostatnia komórka powinna być H5 ; Ale możesz przetestować swoją funkcję po usunięciu wartości w A5. Zobaczysz, że ostatnia komórka to ta pusta komórka, i myślę, że twój kod wymaga pewnych zmian, ponieważ Cells(1,1).Select()jest to nieprawda, być może ActiveSheet.Cells(1,1).Select; Również w VBA nie zaleca się używania Select;).
shA.t
5
To łamie dwie podstawowe zasady dla Excel VBA: Nie używaj Wybierz! I nie zakładaj, że arkusz, który chcesz jest aktywny.
Rachel Hettinger
1
To stara odpowiedź, ale brakuje jej Set.
BigBen,
8

Ponieważ oryginalne pytanie dotyczy problemów ze znalezieniem ostatniej komórki, w tej odpowiedzi wymienię różne sposoby uzyskania nieoczekiwanych wyników ; zobacz moją odpowiedź na „Jak znaleźć ostatni wiersz zawierający dane w arkuszu Excel z makrem?” za moje podejście do rozwiązania tego.

Zacznę od rozwinięcia odpowiedzi sancho.s i komentarza GlennFromIowa , dodając jeszcze więcej szczegółów:

[...] najpierw należy zdecydować, co uważa się za wykorzystane. Widzę co najmniej 6 znaczeń. Komórka ma:

  • 1) dane, tj. Formuła, co może skutkować pustą wartością;
  • 2) wartość, tj. Nieprzezroczysta formuła lub stała;
  • 3) formatowanie;
  • 4) formatowanie warunkowe;
  • 5) kształt (wraz z komentarzem) nakładający się na komórkę;
  • 6) zaangażowanie w tabelę (obiekt listy).

Którą kombinację chcesz przetestować? Niektóre (takie jak Tabele) mogą być trudniejsze do przetestowania, a niektóre mogą być rzadkie (takie jak kształt poza zakresem danych), ale inne mogą się różnić w zależności od sytuacji (np. Formuły z pustymi wartościami).

Inne rzeczy, które warto rozważyć:

  • A) Czy mogą być ukryte wiersze (np. Autofiltr), puste komórki lub puste rzędy?
  • B) Jakiego rodzaju wydajność jest akceptowalna?
  • C) Czy makro VBA może w jakikolwiek sposób wpływać na skoroszyt lub ustawienia aplikacji?

Mając to na uwadze, zobaczmy, w jaki sposób wspólne sposoby uzyskiwania „ostatniej komórki” mogą dawać nieoczekiwane wyniki:

  • .End(xlDown)Kod z pytaniem złamie najłatwiej (np z jednym niepustym komórce lub gdy występują puste komórki w między ) z powodów wyjaśnionych w odpowiedzi przez Siddharth frezowania tutaj (wyszukaj „xlDown jest równie wiarygodne.” ) 👎
  • Każde rozwiązanie oparte na Counting ( CountAlub Cells*.Count) lub .CurrentRegionrównież ulegnie awarii w obecności pustych komórek lub wierszy 👎
  • Rozwiązanie polegające .End(xlUp)na wyszukiwaniu wstecz od końca kolumny będzie, podobnie jak CTRL + UP, wyszukiwać dane (formuły generujące pustą wartość są uważane za „dane”) w widocznych wierszach (więc użycie go z włączonym autofiltrem może dawać nieprawidłowe wyniki ⚠️ ).

    Musisz uważać, aby uniknąć standardowych pułapek (w celu uzyskania szczegółowych informacji ponownie odniosę się tutaj do odpowiedzi Siddharth Rout , poszukaj sekcji „Znajdź ostatni wiersz w kolumnie” ), na przykład zakodowania ostatniego wiersza ( Range("A65536").End(xlUp)) zamiast polegać na sht.Rows.Count.

  • .SpecialCells(xlLastCell)jest równoważne CTRL + END, zwracając najniżej położoną i prawą komórkę „używanego zakresu”, więc wszystkie zastrzeżenia dotyczące polegania na „używanym zakresie” dotyczą również tej metody. Ponadto „używany zakres” jest resetowany tylko podczas zapisywania skoroszytu i uzyskiwania dostępu worksheet.UsedRange, więc xlLastCellmoże dawać nieaktualne wyniki⚠️ z niezapisanymi modyfikacjami (np. Po usunięciu niektórych wierszy). Zobacz pobliską odpowiedź dotNET .
  • sht.UsedRange(opisany szczegółowo w odpowiedzi tutaj przez sancho.s ) uwzględnia zarówno dane, jak i formatowanie (choć nie formatowanie warunkowe) i resetuje „używany zakres” arkusza roboczego , który może, ale nie musi być tym, czego chcesz.

    Zauważ, że powszechnym błędem ️ jest użycie .UsedRange.Rows.Count⚠️, który zwraca liczbę wierszy w używanym zakresie, a nie ostatni numer wiersza (będą różne, jeśli kilka pierwszych wierszy będzie pustych). Szczegółowe informacje można znaleźć w odpowiedzi newguy na Jak znaleźć ostatni wiersz zawierający dane w arkuszu Excel z makrem?

  • .Findpozwala znaleźć ostatni wiersz z dowolnymi danymi (w tym formułami) lub niepustą wartością w dowolnej kolumnie . Możesz wybrać, czy interesują Cię formuły, czy wartości, ale haczyk polega na tym, że resetuje ustawienia domyślne w oknie dialogowym Znajdź w programie Excel ️️⚠️, co może być bardzo mylące dla użytkowników. Należy go również ostrożnie stosować, patrz odpowiedź Siddharth Rout tutaj (sekcja „Znajdź ostatni wiersz w arkuszu” )
  • Bardziej wyraźne rozwiązania, które sprawdzają poszczególne Cells„w pętli” są na ogół wolniejsze niż ponowne użycie funkcji Excela (choć nadal może być wydajne), ale pozwalają dokładnie określić, co chcesz znaleźć. Zobacz moje rozwiązanie oparte na UsedRangetablicach i VBA, aby znaleźć ostatnią komórkę z danymi w danej kolumnie - obsługuje ukryte wiersze, filtry, spacje, nie modyfikuje wartości domyślnych Znajdź i jest dość wydajny.

Niezależnie od wybranego rozwiązania, zachowaj ostrożność

  • używać Longzamiast Integerdo przechowywania numerów wierszy (aby uniknąć uzyskania Overflowwięcej niż 65 tysięcy wierszy) i
  • aby zawsze określać arkusz, z którym pracujesz (tj. Dim ws As Worksheet ... ws.Range(...)zamiast Range(...))
  • podczas używania .Value(co jest a Variant) unikaj niejawnych rzutowań, .Value <> ""ponieważ zakończą się niepowodzeniem, jeśli komórka zawiera wartość błędu.
Nickolay
źródło
4

Jednak to pytanie ma na celu znalezienie ostatniego wiersza za pomocą VBA, myślę, że dobrze byłoby dołączyć formułę tablicową dla funkcji arkusza, ponieważ jest ona często odwiedzana:

{=ADDRESS(MATCH(INDEX(D:D,MAX(IF(D:D<>"",ROW(D:D)-ROW(D1)+1)),1),D:D,0),COLUMN(D:D))}

Musisz wprowadzić formułę bez nawiasów, a następnie nacisnąć Shift+ Ctrl+, Enteraby stała się formułą tablicową.

To da ci adres ostatnio używanej komórki w kolumnie D.

M--
źródło
1
Lubię to. Mogę nieznacznie zmienić, aby uzyskać tylko numer wiersza ... ”{= MATCH (INDEX (D: D, MAX (IF (D: D <>" ", ROW (D: D) -ROW (D1) +1))) , 1), D: D, 0)} '
PGSystemTester
3
sub last_filled_cell()
msgbox range("A65536").end(xlup).row
end sub

Oto A65536ostatnia komórka w kolumnie A, ten kod został przetestowany w programie Excel 2003.

Ashwith Ullal
źródło
Czy możesz wyjaśnić, w jaki sposób twój kod odpowiada na to stare pytanie?
shoover
1
Chociaż ta odpowiedź jest prawdopodobnie poprawna i przydatna, najlepiej jest dołączyć wraz z nią wyjaśnienie wyjaśniające, w jaki sposób pomaga rozwiązać problem. Staje się to szczególnie przydatne w przyszłości, jeśli nastąpi zmiana (prawdopodobnie niezwiązana), która spowoduje, że przestanie ona działać, a użytkownicy będą musieli zrozumieć, jak kiedyś działała.
Kevin Brown
2

Szukałem sposobu naśladowania CTRL+ Shift+ End, więc rozwiązanie dotNET jest świetne, z wyjątkiem mojego Excela 2010 muszę dodać, setjeśli chcę uniknąć błędu:

Function GetLastCell(sh As Worksheet) As Range
  Set GetLastCell = sh.Cells(1, 1).SpecialCells(xlLastCell)
End Function

i jak to sprawdzić samemu:

Sub test()
  Dim ws As Worksheet, r As Range
  Set ws = ActiveWorkbook.Sheets("Sheet1")
  Set r = GetLastCell(ws)
  MsgBox r.Column & "-" & r.Row
End Sub
J. Chomel
źródło
1

Oto moje dwa centy.

IMHO ryzyko ukrytego wiersza z wykluczeniem danych jest zbyt duże, aby xlUpmożna było uznać je za odpowiedź One Stop . Zgadzam się, że jest to proste i będzie działać przez większość czasu, ale stwarza ryzyko zaniżenia ostatniego rzędu bez żadnego ostrzeżenia. To może produkować katastrofalnych wyników w pewnym poinit dla kogoś, kto skoczył na stosie Overlow i szukałem do „pewien sposób”, aby uchwycić tę wartość.

FindMetoda jest bez zarzutu, a ja pochwalam to jako jeden przystanek Odpowiedź . Jednak wada zmiany Findustawień może być denerwująca, szczególnie jeśli jest to część UDF.

Pozostałe opublikowane odpowiedzi są w porządku, jednak złożoność staje się nieco nadmierna. Oto moja próba znalezienia równowagi między niezawodnością, minimalną złożonością i niestosowaniem Find.

Function LastRowNumber(Optional rng As Range) As Long

If rng Is Nothing Then
    Set rng = ActiveSheet.UsedRange
Else
    Set rng = Intersect(rng.Parent.UsedRange, rng.EntireColumn)
    If rng Is Nothing Then
        LastRowNumber = 1
        Exit Function
    ElseIf isE = 0 Then
        LastRowNumber = 1
        Exit Function

    End If

End If

LastRowNumber = rng.Cells(rng.Rows.Count, 1).Row

Do While IsEmpty(Intersect(rng, _
    rng.Parent.Rows(LastRowNumber)))

    LastRowNumber = LastRowNumber - 1
Loop

End Function

Dlaczego to jest dobre:

  • Racjonalnie proste, niewiele zmiennych.
  • Pozwala na wiele kolumn.
  • Nie modyfikuje Findustawień
  • Dynamiczny, jeśli jest używany jako UDF z wybraną całą kolumną.

Dlaczego to jest złe:

  • Przy bardzo dużych zestawach danych i ogromnej luce między użytym zakresem a ostatnim rzędem w określonych kolumnach, będzie to działać wolniej, w rzadkich przypadkach ZNACZNIE wolniej.

Uważam jednak, że rozwiązanie kompleksowe, które ma wadę polegającą na nieuporządkowaniu findustawień lub spowolnieniu działania, jest lepszym rozwiązaniem ogólnym. Użytkownik może następnie majstrować przy swoich ustawieniach, aby spróbować poprawić, wiedząc, co się dzieje z ich kodem. Korzystanie xLUpnie ostrzega przed potencjalnym ryzykiem i może kontynuować, kto wie, jak długo nie wiedząc, że ich kod nie działa poprawnie.

PGSystemTester
źródło
1

Od ponad 3 lat są to funkcje, których używam do znajdowania ostatniego wiersza i ostatniej kolumny na zdefiniowaną kolumnę (dla wiersza) i wiersz (dla kolumny):

Ostatnia kolumna:

Function lastCol(Optional wsName As String, Optional rowToCheck As Long = 1) As Long

    Dim ws  As Worksheet

    If wsName = vbNullString Then
        Set ws = ActiveSheet
    Else
        Set ws = Worksheets(wsName)
    End If

    lastCol = ws.Cells(rowToCheck, ws.Columns.Count).End(xlToLeft).Column

End Function

Ostatni wiersz:

Function lastRow(Optional wsName As String, Optional columnToCheck As Long = 1) As Long

    Dim ws As Worksheet

    If wsName = vbNullString Then
        Set ws = ActiveSheet
    Else
        Set ws = Worksheets(wsName)
    End If

    lastRow = ws.Cells(ws.Rows.Count, columnToCheck).End(xlUp).Row

End Function

W przypadku PO jest to sposób na uzyskanie ostatniego wiersza w kolumnie E:

Debug.Print lastRow(columnToCheck:=Range("E4:E48").Column)

Ostatni wiersz, licząc puste wiersze z danymi:

W tym przypadku możemy użyć dobrze znanych formuł Excela , które dają nam ostatni wiersz arkusza roboczego w Excelu, bez udziału VBA -=IFERROR(LOOKUP(2,1/(NOT(ISBLANK(A:A))),ROW(A:A)),0)

Aby umieścić to w VBA i nie pisać niczego w Excelu, używając parametrów dla tych ostatnich funkcji, można mieć na myśli coś takiego:

Public Function LastRowWithHidden(Optional wsName As String, Optional columnToCheck As Long = 1) As Long

    Dim ws As Worksheet

    If wsName = vbNullString Then
        Set ws = ActiveSheet
    Else
        Set ws = Worksheets(wsName)
    End If

    Dim letters As String
    letters = ColLettersGenerator(columnToCheck)
    LastRowWithHidden = ws.Evaluate("=IFERROR(LOOKUP(2,1/(NOT(ISBLANK(" & letters & "))),ROW(" & letters & " )),0)")

End Function

Function ColLettersGenerator(col As Long) As String

    Dim result As Variant
    result = Split(Cells(1, col).Address(True, False), "$")
    ColLettersGenerator = result(0) & ":" & result(0)

End Function
Vityata
źródło
Zwróci niepoprawny wynik, jeśli ostatni wiersz / kolumna jest ukryty.
PGSystemTester
@PGSystemTester - tak, ale w moim rozumieniu, kiedy go programuję, jeśli jest ukryty, nie jest potrzebna ostatnia kolumna / wiersz.
Vityata
Cieszę się, że dla ciebie działa. Podejrzewam, że twoja sytuacja nie jest typowym przypadkiem użycia. Częściej, gdy pracuję z klientami, którzy potrzebują ostatniego wiersza, szukają najniższej komórki z danymi, a nie najniższej widocznej komórki z danymi. W każdym razie ... cieszę się, że to działa. 👍
PGSystemTester
@PGSystemTester - Mam rację, ale dbanie o strukturę i niedopuszczanie niewidzialnych komórek działa jak urok.
Vityata,
@PGSystemTester - tak, jeśli zadanie prawdopodobnie dopuszcza puste wiersze, prawdopodobnie użyłbym EVAL()słynnej formuły Excel. Chociaż ludzie mogą myśleć, że Eval()to zło, to kolejna ciekawa historia, o której można pisać ...
Vityata,
0
Sub lastRow()

    Dim i As Long
        i = Cells(Rows.Count, 1).End(xlUp).Row
            MsgBox i

End Sub

sub LastRow()

'Paste & for better understanding of the working use F8 Key to run the code .

dim WS as worksheet
dim i as long

set ws = thisworkbook("SheetName")

ws.activate

ws.range("a1").select

ws.range("a1048576").select

activecell.end(xlup).select

i= activecell.row

msgbox "My Last Row Is " & i

End sub
użytkownik85489
źródło