web-dev-qa-db-fra.com

Erreur lors de la recherche de la dernière cellule utilisée dans Excel avec VBA

Quand je veux trouver la dernière valeur de cellule utilisée, j'utilise:

Dim LastRow As Long

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

Debug.Print LastRow

Je me trompe de sortie lorsque je mets un seul élément dans une cellule. Mais lorsque je mets plus d'une valeur dans la cellule, la sortie est correcte. Quelle est la raison derrière cela?

164
james

NOTE: J'ai l'intention d'en faire un "poste unique" où vous pouvez utiliser la méthode Correct pour trouver la dernière ligne. Cela couvrira également les meilleures pratiques à suivre pour trouver la dernière ligne. Et par conséquent, je continuerai à le mettre à jour chaque fois que je tomberai sur un nouveau scénario/informations.


Façons non fiables de trouver la dernière ligne

Certains des moyens les plus courants de trouver la dernière ligne sont très peu fiables et ne doivent donc jamais être utilisés.

  1. UsedRange
  2. xlDown
  3. ComteA

UsedRange devrait JAMAIS être utilisé pour trouver la dernière cellule contenant des données. C'est très peu fiable. Essayez cette expérience.

Tapez quelque chose dans la cellule A5. Maintenant, lorsque vous calculez la dernière ligne avec l’une des méthodes indiquées ci-dessous, vous obtenez 5. Maintenant, coloriez la cellule A10 rouge. Si vous utilisez maintenant l'un des codes ci-dessous, vous aurez toujours 5. Si vous utilisez Usedrange.Rows.Count qu'est-ce que vous obtenez? Ce ne sera pas 5.

Voici un scénario pour montrer comment fonctionne UsedRange.

enter image description here

xlDown est également peu fiable.

Considérons ce code

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

Que se passerait-il s'il n'y avait qu'une seule cellule (A1) qui contenait des données? Vous finirez par atteindre la dernière ligne de la feuille de calcul! C'est comme si vous sélectionniez la cellule A1 puis appuyez sur End touche puis en appuyant sur Down Arrow clé. Cela vous donnera également des résultats peu fiables s'il y a des cellules vides dans une plage.

CountA n'est pas fiable non plus car il vous donnera un résultat incorrect s'il y a des cellules vides entre les deux.

Et par conséquent, il faut éviter d'utiliser UsedRange, xlDown et CountA pour trouver la dernière cellule.


Trouver la dernière ligne dans une colonne

Pour trouver la dernière ligne de la colonne E, utilisez cette commande.

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

Si vous remarquez que nous avons un . avant Rows.Count. Nous avons souvent choisi d'ignorer cela. Voir THIS question sur l’erreur possible que vous pourriez obtenir. Je conseille toujours d'utiliser . avant Rows.Count et Columns.Count. Cette question est un scénario classique dans lequel le code va échouer car le Rows.Count renvoie 65536 pour Excel 2003 et versions antérieures et 1048576 pour Excel 2007 et versions ultérieures. De même, Columns.Count renvoie 256 et 16384, respectivement.

Le fait ci-dessus que Excel 2007+ comporte 1048576 lignes souligne également le fait que nous devons toujours déclarer la variable qui contiendra la valeur de la ligne sous la forme Long au lieu de Integer sinon vous obtiendrez un Overflow error.


Trouver la dernière ligne dans une feuille

Pour trouver la Effective dernière ligne de la feuille, utilisez ceci. Notez l'utilisation de Application.WorksheetFunction.CountA(.Cells). Ceci est nécessaire car s'il n'y a pas de cellules avec des données dans la feuille de calcul, alors .Find vous donnera Run 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

Rechercher la dernière ligne d'une table (ListObject)

Les mêmes principes s'appliquent, par exemple, pour obtenir la dernière ligne de la troisième colonne d'un tableau:

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
277
Siddharth Rout

Remarque: cette réponse a été motivée par ce commentaire . Le but de UsedRange diffère de ce qui est mentionné dans la réponse ci-dessus.

Quant à la manière correcte de trouver la dernière cellule utilisée, il faut d’abord décider de ce qui est considéré utilisé , puis sélectionner une méthode appropriée . Je conçois au moins trois significations:

  1. Utilisé = non vide, c'est-à-dire ayant des données .

  2. Used = "... en cours d'utilisation, c'est-à-dire la section qui contient des données ou un formatage ." Selon la documentation officielle , il s'agit du critère utilisé par Excel au moment de la sauvegarde. Voir aussi this . Si ce n’est pas le cas, le critère peut produire des résultats inattendus, mais il peut également être exploité intentionnellement (moins souvent, sûrement), par exemple pour mettre en évidence ou imprimer des régions spécifiques, qui risquent de ne pas contenir de données. Et, bien sûr, il est souhaitable que la plage à utiliser comme critère de sauvegarde lors de la sauvegarde d’un classeur pour éviter de perdre une partie de son travail.

  3. Used = "... en cours d'utilisation, c'est-à-dire la section qui contient des données ou un formatage " ou un formatage conditionnel. Identique à 2., mais en incluant les cellules qui sont la cible de toute règle de mise en forme conditionnelle.

Comment trouver la dernière cellule utilisée dépend de ce que vous voulez (votre critère) .

Pour le critère 1, je suggère de lire cette réponse. Notez que UsedRange est cité comme non fiable. Je pense que cela est trompeur (c'est-à-dire "injuste" pour UsedRange), car UsedRange ne vise tout simplement pas à rapporter la dernière cellule contenant des données. Il ne devrait donc pas être utilisé dans ce cas, comme indiqué dans cette réponse. Voir aussi ce commentaire .

Pour le critère 2, UsedRange est l'option la plus fiable , par rapport aux autres options également conçues pour cet usage. Il est même inutile de sauvegarder un classeur pour vous assurer que la dernière cellule est mise à jour. Ctrl+End ira dans une mauvaise cellule avant d’enregistrer ("La dernière cellule n’est réinitialisée que lorsque vous enregistrez la feuille de calcul", à partir de http://msdn.Microsoft.com/en-us/library/aa139976%28v=office .10% 29.aspx . C’est une référence ancienne, mais valide à cet égard).

Pour le critère 3, je ne connais aucune méthode intégrée . Le critère 2 ne prend pas en compte la mise en forme conditionnelle. On peut avoir des cellules formatées, basées sur des formules, qui ne sont pas détectées par UsedRange ou Ctrl+End. Dans la figure, la dernière cellule est B3, car le formatage lui a été explicitement appliqué. Les cellules B6: D7 ont un format dérivé d'une règle de mise en forme conditionnelle, même si UsedRange ne le détecte pas. La comptabilisation de cela nécessiterait une programmation VBA.

enter image description here


En ce qui concerne votre question spécifique : Quelle est la raison de cela?

Votre code utilise la première cellule de votre plage E4: E48 comme trampoline, pour sauter avec End(xlDown).

La sortie "erronée" sera obtenue s'il n'y a pas de cellules non vides dans votre plage autre que peut-être la première. Ensuite, vous sautez dans le noir , c’est-à-dire en bas de la feuille de travail (vous devriez noter la différence entre vierge et chaîne vide !).

Notez que:

  1. Si votre plage contient des cellules non vides non contiguës, cela donnera également un résultat erroné.

  2. S'il n'y a qu'une seule cellule non vide, mais que ce n'est pas la première, votre code vous donnera toujours le résultat correct.

31
sancho.s

J'ai créé cette fonction unique pour déterminer la dernière ligne, colonne et cellule, que ce soit pour des données, des cellules formatées (groupées/commentées/masquées) ou une mise en forme conditionnelle.

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

Les résultats ressemblent à ceci:
determine last cell

Pour des résultats plus détaillés, certaines lignes du code peuvent être décommentées:
last column, row

Une limitation existe: s'il y a des tables dans la feuille, les résultats peuvent devenir peu fiables. J'ai donc décidé d'éviter d'exécuter le code dans ce cas:

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

Une note importante à garder à l'esprit lors de l'utilisation de la solution ...

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

... est de s'assurer que votre variable LastRow est de Long type:

Dim LastRow as Long

Sinon, vous obtiendrez des erreurs OVERFLOW dans certaines situations dans les classeurs .XLSX

C’est ma fonction encapsulée que j’aborde dans diverses utilisations de code.

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
10
Bishop

J'ajouterais à la réponse donnée par Siddarth Rout de dire que l'appel CountA peut être ignoré en faisant en sorte que Find renvoie un objet Range au lieu d'un numéro de ligne, puis teste l'objet Range renvoyé pour voir s'il est Nothing (feuille de calcul vierge). .

De plus, je demanderais à ma version de toute procédure LastRow de renvoyer un zéro pour une feuille de calcul vierge, pour que je sache qu'elle est vide.

8
no comprende

Je me demande si personne n’a mentionné cela, mais le moyen le plus simple d’obtenir la dernière cellule utilisée est:

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

Cela renvoie essentiellement la même cellule que vous obtenez par Ctrl + End après avoir sélectionné Cell A1.

Un mot d'avertissement: Excel garde la trace de la cellule la plus en bas à droite jamais utilisée dans une feuille de calcul. Donc, si, par exemple, vous entrez quelque chose dans B et quelque chose d'autre dans H8, puis supprimez plus tard le contenu de H8, en appuyant sur Ctrl + End vous conduira toujours à H8 cellule. La fonction ci-dessus aura le même comportement.

8
dotNET

Puisque la question initiale concerne des problèmes pour trouver la dernière cellule, dans cette réponse, je vais énumérer les différentes manières d'obtenir des résultats inattendus; voir ma réponse à "Comment trouver la dernière ligne contenant des données dans la feuille Excel avec une macro?" pour résoudre ce problème.

Je vais commencer par développer la réponse de sancho.s et le commentaire de GlennFromIowa , en ajoutant encore plus de détails:

[...] il faut d'abord décider de ce qui est considéré comme utilisé. Je vois au moins 6 significations. La cellule a:

  • 1) des données, c’est-à-dire une formule, pouvant éventuellement donner une valeur vide;
  • 2) une valeur, c'est-à-dire une formule ou une constante non vide;
  • 3) mise en forme;
  • 4) mise en forme conditionnelle;
  • 5) une forme (y compris un commentaire) recouvrant la cellule;
  • 6) implication dans une table (objet liste).

Quelle combinaison voulez-vous tester? Certains (tels que les tableaux) peuvent être plus difficiles à tester, et certains peuvent être rares (comme une forme en dehors de la plage de données), mais d'autres peuvent varier en fonction de la situation (par exemple, les formules avec des valeurs vides).

Autres points à considérer:

  • A) Peut-il y avoir des lignes cachées (par exemple, autofilter), des cellules vides ou des lignes vides?
  • B) Quel type de performance est acceptable?
  • C) La macro VBA peut-elle affecter le classeur ou les paramètres de l'application de quelque manière que ce soit?

Dans cet esprit, voyons comment les méthodes courantes pour obtenir la "dernière cellule" peuvent produire des résultats inattendus:

  • Le code .End(xlDown) de la question se cassera le plus facilement (par exemple avec ne seule cellule non vide ou lorsqu'il y a cellules vides entre) pour les raisons expliquées in la réponse de Siddharth Rout ici (recherchez "" xlDown est également peu fiable. ") ????
  • Toute solution basée sur Counting (CountA ou Cells*.Count) ou .CurrentRegion sera également interrompue en présence de cellules ou de lignes vides ????
  • Une solution impliquant .End(xlUp) pour rechercher en arrière à partir de la fin d'une colonne, tout comme CTRL + UP, recherchera data (les formules produisant une valeur vide sont considérées comme des "données") dans - lignes visibles (son utilisation avec le filtre automatique activé risque alors de produire des résultats incorrects).

    Vous devez veiller à éviter les pièges classiques (pour plus de détails, nous reviendrons à la réponse de Siddharth Rout ici, recherchez le "Trouver la dernière ligne dans une colonne. " section), telle que coder en dur la dernière ligne (Range("A65536").End(xlUp)) au lieu de s'appuyer sur sht.Rows.Count.

  • .SpecialCells(xlLastCell) est équivalent à CTRL + FIN, renvoyant la cellule la plus basse et la plus à droite de la "plage utilisée". Par conséquent, tous les avertissements qui s'appliquent à l'utilisation de la "plage utilisée" s'appliquent également à cette méthode. De plus, la "plage utilisée" n'est réinitialisée que lors de la sauvegarde du classeur et de l'accès à worksheet.UsedRange, de sorte que xlLastCell puisse produire des résultats obsolètes avec des modifications non enregistrées (par exemple, après la suppression de certaines lignes). Voir le réponse à proximité par dotNET .
  • sht.UsedRange (décrit en détail dans la réponse de sancho.s ici) considère les données et le formatage (bien que le formatage ne soit pas conditionnel) et réinitialise le "gamme utilisée" de la feuille de calcul, qui peut ne pas être ce que vous voulez.

    Notez qu'une erreur courante ️ est d'utiliser .UsedRange.Rows.Count⚠️, qui renvoie le nombre de lignes dans la plage utilisée, et non le dernier numéro de ligne (ils seront différents si les premières lignes sont vides), voir réponse de newguy à . Comment trouver la dernière ligne contenant des données dans la feuille Excel avec une macro?

  • .Find vous permet de trouver la dernière ligne avec toutes les données (y compris les formules) ou une valeur non vide dans n'importe quelle colonne. Vous pouvez choisir si vous êtes intéressé par des formules ou des valeurs, mais le problème est que cela réinitialise les valeurs par défaut dans la boîte de dialogue Rechercher d'Excel ️️⚠️, ce qui peut être très déroutant pour vos utilisateurs. Il doit également être utilisé avec précaution, voir réponse de Siddharth Rout ici (section "Trouver la dernière ligne d'une feuille" )
  • Des solutions plus explicites qui vérifient chaque Cells 'dans une boucle sont généralement plus lentes que de réutiliser une fonction Excel (même si elles peuvent toujours être performantes), mais vous permettent de spécifier exactement ce que vous voulez trouver. Voir ma solution sur la base de UsedRange et des tableaux VBA pour trouver la dernière cellule avec les données de la colonne donnée - elle gère les lignes cachées, les filtres, les blancs, ne modifie pas les valeurs par défaut de Find et est assez performant.

Quelle que soit la solution que vous choisissez, soyez prudent

  • utiliser Long au lieu de Integer pour stocker les numéros de ligne (pour éviter d'obtenir Overflow avec plus de 65 000 lignes) et
  • pour toujours spécifier la feuille de travail avec laquelle vous travaillez (c'est-à-dire Dim ws As Worksheet ... ws.Range(...) au lieu de Range(...))
  • lors de l'utilisation de .Value (qui est un Variant), évitez les conversions implicites telles que .Value <> "", car elles échoueront si la cellule contient une valeur d'erreur.
6
Nickolay
sub last_filled_cell()
msgbox range("a65536").end(xlup).row
end sub

"ici a65536 est la dernière cellule de la colonne où ce code a été testé sur Excel sti72003" 200

et si vous utilisez son "a1,048,576"

mon code est juste pour que les débutants comprennent les concepts de ce que fin (xlup) et d'autres commandes associées peuvent faire

5
Ashwith Ullal

Cependant, si cette question cherche à trouver la dernière ligne à l'aide de VBA, je pense qu'il serait bon d'inclure une formule matricielle pour la fonction de feuille de calcul, car celle-ci est visitée fréquemment:

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

Vous devez entrer la formule sans crochets, puis appuyer sur Shift + Ctrl + Enter pour en faire une formule matricielle.

Cela vous donnera l'adresse de la dernière cellule utilisée dans la colonne D.

3
M--

Je cherchais un moyen de mimer le CTRL+Shift+EndDonc, la solution dotNET est excellente, sauf avec Excel 2010, je dois ajouter un set si je veux éviter une erreur:

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

et comment vérifier cela par vous-même:

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
2
J. Chomel

Pour les 3 dernières années, ce sont les fonctions que j'utilise pour trouver la dernière ligne et la dernière colonne par colonne définie (pour ligne) et ligne (pour colonne):

Dernière colonne:

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

Dernière rangée:

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

Pour le cas de l'OP, voici comment obtenir la dernière ligne de la colonne E:

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

1
Vityata
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
1
user85489