Answers:
注意:我打算将其设为“一站式发布”,您可以在其中使用Correct
找到最后一行的方式。这还将涵盖查找最后一行时遵循的最佳实践。因此,每当遇到新的情况/信息时,我都会继续进行更新。
一些最不可靠的查找最后一行的方法,因此永远不要使用。
UsedRange
是否应NEVER被用来寻找具有数据的最后一个单元格。这是非常不可靠的。试试这个实验。
在单元格中输入内容A5
。现在,当您使用下面给出的任何方法计算最后一行时,它将得到5。现在将单元格A10
涂成红色。如果您现在使用以下任何代码,您仍将获得5。Usedrange.Rows.Count
得到的是什么?不会是5。
这是一个演示如何UsedRange
工作的方案。
xlDown
同样是不可靠的。
考虑这段代码
lastrow = Range("A1").End(xlDown).Row
如果只有一个A1
存储数据的单元(),会发生什么?您最终将到达工作表的最后一行!这就像选择单元格A1
,然后End按键,然后Down Arrow按键。如果范围中有空白单元格,这也会给您不可靠的结果。
CountA
也是不可靠的,因为如果它们之间有空白单元格,则会给您不正确的结果。
因此应避免使用UsedRange
,xlDown
并CountA
找到最后一个单元格。
要查找Col E中的最后一行,请使用此
With Sheets("Sheet1")
LastRow = .Range("E" & .Rows.Count).End(xlUp).Row
End With
如果您注意到我们.
之前有过Rows.Count
。我们经常选择忽略这一点。有关可能出现的错误,请参阅此问题。我总是建议使用.
before Rows.Count
和Columns.Count
。这个问题是一个典型的场景,其中的代码将失败,因为Rows.Count
回报65536
为Excel 2003及更早版本1048576
的Excel 2007和更高版本。同样,分别Columns.Count
返回256
和16384
。
以上事实的Excel 2007+具有1048576
行也强调这一事实,我们应该始终宣布将举行的行值作为变量Long
,而不是Integer
其他人,你会得到一个Overflow
错误。
请注意,此方法将跳过任何隐藏的行。回顾上面的A列屏幕截图,如果第8行被隐藏,则此方法将返回5
而不是8
。
要Effective
在工作表中找到最后一行,请使用它。注意使用Application.WorksheetFunction.CountA(.Cells)
。这是必需的,因为如果工作表中没有包含数据的单元格,.Find
则会为您提供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
应用相同的原理,例如获取表第三列的最后一行:
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
Usedrange.Rows.Count
得到的是什么?不会是5。Usedrange非常不可靠地找到最后一行。
remembers
最后的设置。甚至当你手工做Find
,它会记住最后一次设置这实际上是一个福音,如果一个人知道这个“事实”
UsedRange
(找到最后一个有数据的单元格非常不可靠)的描述具有误导性。UsedRange
即使在某些情况下它可能会给出正确的结果,也根本不打算将其用于该目的。我认为所提出的实验加剧了混乱。使用UsedRange
($ A $ 1:$ A $ 8)获得的结果不取决于首先输入数据还是将其删除。即使没有输入数据将其删除,右侧的数字也将保持不变。请看我的回答。
注意:此答案受此评论的激励。的目的与UsedRange
上面答案中提到的目的不同。
关于找到最后使用的电池的正确方法,首先必须确定认为使用了什么电池,然后选择一种合适的方法。我至少想到了三种含义:
已使用=非空白,即具有data。
Used =“ ...正在使用中,表示包含数据或格式的部分。” 根据官方文档,这是Excel在保存时使用的标准。另请参阅此官方文档。如果没有意识到这一点,则该准则可能会产生意想不到的结果,但也可能有意地利用它(不太经常,肯定地),例如,突出显示或打印可能最终没有数据的特定区域。并且,当然,作为保存工作簿时使用范围的标准是可取的,以免丢失一部分工作。
Used =“ ...正在使用中,表示包含数据或格式的部分” 或条件格式。 与2相同,但还包括任何条件格式设置规则的目标单元格。
如何找到最后使用的单元格取决于您的要求(您的标准)。
对于标准1,建议阅读此答案。请注意,这UsedRange
被引用为不可靠的。我认为这具有误导性(即,对而言“不公平” UsedRange
),因为UsedRange
这并不意味着要报告包含数据的最后一个单元格。因此,如该答案所示,不应在这种情况下使用它。另请参阅此评论。
与标准2相比,标准2 UsedRange
是最可靠的选择。甚至没有必要保存工作簿以确保更新了最后一个单元格。
Ctrl+ End将在保存之前进入错误的单元格(“直到保存工作表后,才会重置最后一个单元格”,来自
http://msdn.microsoft.com/zh-cn/library/aa139976%28v=office.10% 29.aspx,它是旧参考,但在这方面有效)。
对于条件3,我不知道任何内置方法。条件2不考虑条件格式。可能有一个基于公式的格式化的单元格,但未被UsedRange
或Ctrl+ 检测到End。在该图中,最后一个单元格是B3,因为格式化已明确应用于该单元格。单元格B6:D7的格式源自条件格式设置规则,即使也不检测到这种格式UsedRange
。为此,需要进行一些VBA编程。
关于您的具体问题: 这是什么原因?
您的代码将E4:E48范围内的第一个单元格用作蹦床,与一起向下跳End(xlDown)
。
如果您的范围中除第一个单元格外没有其他非空白单元格,则将获得“错误”输出。然后,您将在黑暗中跳来跳去,即跳下工作表(应注意空白字符串和空字符串之间的区别!)。
注意:
如果您的范围包含不连续的非空白单元格,那么它也会给出错误的结果。
如果只有一个非空白单元格,但不是第一个,则您的代码仍将为您提供正确的结果。
我创建了此一站式功能,用于确定最后一行,最后一列和单元格,无论是用于数据,已格式化(分组/注释/隐藏)的单元格还是条件格式。
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
结果如下:
为了获得更详细的结果,代码中的某些行可以取消注释:
存在一个限制-如果工作表中有表格,结果可能变得不可靠,因此在这种情况下,我决定避免运行代码:
If .ListObjects.Count > 0 Then
MsgBox "Cannot return reliable results, as there is at least one table in the worksheet."
Exit Sub
End If
使用解决方案时要记住的重要注意事项...
LastRow = ws.Cells.Find(What:="*", After:=ws.range("a1"), SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
...是为了确保您的LastRow
变量是以下Long
类型:
Dim LastRow as Long
否则,在某些情况下,.XLSX工作簿中将出现OVERFLOW错误。
这是我封装的函数,可用于各种代码用途。
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
我想知道没有人提到这件事,但是获取最后使用的单元格最简单的方法是:
Function GetLastCell(sh as Worksheet) As Range
GetLastCell = sh.Cells(1,1).SpecialCells(xlLastCell)
End Function
本质上,它返回选择Cell后通过Ctrl+ 得到的相同单元End格A1
。
请注意:Excel会跟踪工作表中曾经使用过的最右下角的单元格。因此,例如,如果您在B3中输入了某些内容,然后在H8中输入了其他内容,然后稍后删除H8的内容,则按Ctrl+ End仍会转到H8单元格。上面的功能将具有相同的行为。
Last Cell
在Excel中,有时指的是(;)Used Range
与Last Used Cell
;)不同的空白单元格。
Cells(1,1).Select()
是无效的ActiveSheet.Cells(1,1).Select
;同样在VBA中,不建议使用Select
;)。
Set
。
由于原来的问题是关于问题与找到的最后一个单元格,在这个答案我会列出你可以得到意想不到的效果的各种方法 ; 请参阅我的答案:“如何在包含宏的Excel工作表中找到包含数据的最后一行?” 我对解决这个问题的看法。
我会通过扩展开始由sancho.s答案,并通过GlennFromIowa注释,增加了更多的细节:
[...]首先必须决定使用什么。我至少看到6个意思。单元格具有:
- 1)可能是空白值的数据,即公式;
- 2)一个值,即非空白公式或常数;
- 3)格式化;
- 4)条件格式;
- 5)与单元格重叠的形状(包括注释);
- 6)参与表(List Object)。
您想测试哪种组合?有些(例如表格)可能更难测试,而有些则很少(例如数据范围之外的形状),但其他一些则可能根据情况而有所不同(例如,带有空白值的公式)。
您可能需要考虑的其他事项:
考虑到这一点,让我们看看获取“最后一个单元格”的常见方法如何产生意外的结果:
.End(xlDown)
,该问题的代码最容易破坏(例如,使用单个非空单元格或之间有空白单元格)。Count
ing(CountA
或Cells*.Count
)的解决方案,或者.CurrentRegion
在存在空白单元格或行的情况下也会破裂👎.End(xlUp)
就像从CTRL + UP一样,从列尾开始向后搜索的解决方案将在可见行中查找数据(将产生空白值的公式视为“数据”)(因此在启用自动过滤器的情况下使用它可能会产生错误的结果⚠️ )。您必须注意避免出现标准陷阱(有关详细信息,我将再次参考Siddharth Rout的答案,在“查找列中的最后一行”部分中进行查找),例如对最后一行(Range("A65536").End(xlUp)
)进行硬编码而不是依靠sht.Rows.Count
。
.SpecialCells(xlLastCell)
等效于CTRL + END,返回“使用范围”的最底部和最右侧的单元格,因此所有依赖于“使用范围”的警告也适用于此方法。另外,“使用范围”仅在保存工作簿和访问时重置worksheet.UsedRange
,因此xlLastCell
可能会产生未保存的修改(例如,在删除某些行之后)陈旧的结果。通过dotNET查看附近的答案。sht.UsedRange
(在sancho.s的答案中有详细说明),同时考虑了数据和格式设置(虽然不是条件格式),并重置了工作表的“使用范围”,这可能是您想要的,也可能不是。需要注意的是一个常见的错误️is到使用.UsedRange.Rows.Count
⚠️,它返回的行数的使用范围,而不是最后的行号(他们会有所不同,如果前几行是空白),详见newguy的回答让我如何才能找到最后一行包含Excel工作表中带有宏的数据?
.Find
允许您在任何列中查找包含任何数据(包括公式)或非空白值的最后一行。您可以选择对公式还是值感兴趣,但是要注意的是它会重置Excel的“查找”对话框中的默认值 ️️⚠️,这可能会使用户感到困惑。还需要谨慎使用它,请在此处查看Siddharth Rout的答案(“在工作表中查找最后一行”部分)Cells
在循环中检查单个'的更明确的解决方案通常比重用Excel函数要慢(尽管仍然可以执行),但是让您精确地指定要查找的内容。请参阅我的基于UsedRange
和VBA阵列的解决方案,以在给定列中查找包含数据的最后一个单元格-它处理隐藏的行,过滤器,空格,不修改“查找”默认值,并且性能很好。无论选择哪种解决方案,请注意
Long
而不是Integer
存储行号(以避免获得Overflow
超过65k行),并且Dim ws As Worksheet ... ws.Range(...)
代替Range(...)
).Value
(是Variant
)时,请避免进行隐式转换,.Value <> ""
因为如果单元格包含错误值,它们将失败。但是这个问题试图使用VBA查找最后一行,我认为最好为工作表函数包含一个数组公式,因为它经常被访问:
{=ADDRESS(MATCH(INDEX(D:D,MAX(IF(D:D<>"",ROW(D:D)-ROW(D1)+1)),1),D:D,0),COLUMN(D:D))}
您需要输入不带括号的公式,然后按Shift+ Ctrl+ Enter使其成为数组公式。
这将为您提供D列中最后使用的单元格的地址。
我一直在寻找一种模仿CTRL+ Shift+的方法End,因此dotNET解决方案很棒,除非set
要避免出现错误,否则在Excel 2010中我需要添加一个:
Function GetLastCell(sh As Worksheet) As Range
Set GetLastCell = sh.Cells(1, 1).SpecialCells(xlLastCell)
End Function
以及如何自行检查:
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
这是我的两分钱。
恕我直言,隐藏行被排除数据的风险太大,以至于不能xlUp
被认为是一站式答案。我同意这很简单,并且在大多数情况下都可以使用,但是它冒着低估最后一行的风险,而没有任何警告。这可能会产生CATASTROPHIC的一些poinit的人谁对堆栈过低跳下并期待“法子”捕捉到了这个值的结果。
该Find
方法是完美无缺的,我将其视为一站式答案。但是,更改Find
设置可能令人讨厌,特别是如果这是UDF的一部分。
发布的其他答案都可以,但是复杂性有点过高。因此,这是我尝试在可靠性,最小复杂度和不使用之间找到平衡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
为什么这样好:
Find
设置为什么这样不好:
但是,我认为一站式解决方案具有搞砸find
设置或执行速度较慢的缺点,是更好的整体解决方案。然后,用户可以知道自己的代码状况,然后修改自己的设置以尝试改进。使用xLUp
不会警告潜在的风险,他们可以继续为谁知道不知道自己的代码无法正常工作多久。
在过去的3年中,这些是我用来查找每个定义的列(对于行)和row(对于列)的最后一行和最后一列的函数:
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
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
对于OP,这是获取column中最后一行的方法E
:
Debug.Print lastRow(columnToCheck:=Range("E4:E48").Column)
在这里,我们可以使用众所周知的Excel公式,该公式为我们提供了Excel工作表的最后一行,而无需使用VBA-=IFERROR(LOOKUP(2,1/(NOT(ISBLANK(A:A))),ROW(A:A)),0)
为了将其放入VBA中而不使用Excel中的任何内容,请使用后一个函数的参数,应注意以下几点:
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
EVAL()
和著名的Excel公式。尽管人们可能认为这Eval()
是邪恶的,但这是另一个
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