当我想找到最后使用的单元格值时,我使用:
Dim LastRow As Long
LastRow = Range("E4:E48").End(xlDown).Row
Debug.Print LastRow
当我将单个元素放入单元格时,输出错误。但是当我在单元格中放入多个值时,输出是正确的。 这背后的原因是什么?
答案 0 :(得分:268)
注意:我打算将其作为“一站式帖子”,您可以使用Correct
方式查找最后一行。这也将涵盖查找最后一行时要遵循的最佳做法。因此,每当遇到新的场景/信息时,我都会继续更新它。
查找最后一行的一些最常见的方法是非常不可靠的,因此永远不应该使用。
UsedRange
应从不用于查找包含数据的最后一个单元格。这是非常不可靠的。试试这个实验。
在单元格A5
中输入内容。现在,当您使用下面给出的任何方法计算最后一行时,它将为您提供5.现在为单元格A10
着色为红色。如果您现在使用以下任何代码,您仍然会得到5.如果您使用Usedrange.Rows.Count
,您会得到什么?它不会是5。
以下是展示UsedRange
如何运作的方案。
xlDown
同样不可靠。
考虑这段代码
lastrow = Range("A1").End(xlDown).Row
如果只有一个单元格(A1
)有数据,会发生什么?您将最终到达工作表中的最后一行!这就像选择单元格A1
然后按结束键然后按向下箭头键。如果范围内有空白单元格,这也会给您带来不可靠的结果。
CountA
也不可靠,因为如果中间有空白单元格会导致错误的结果。
因此,应该避免使用UsedRange
,xlDown
和CountA
来查找最后一个单元格。
要查找Col E中的最后一行,请使用此
With Sheets("Sheet1")
LastRow = .Range("E" & .Rows.Count).End(xlUp).Row
End With
如果您发现.
之前我们有Rows.Count
。我们经常选择忽略它。有关可能出现的错误,请参阅THIS问题。我总是建议在.
和Rows.Count
之前使用Columns.Count
。该问题是代码失败的典型场景,因为{2003}及更早版本的Rows.Count
会返回65536
,而Excel 2007及更高版本会返回1048576
。同样,Columns.Count
分别返回256
和16384
。
Excel 2007+有1048576
行的上述事实也强调了这样一个事实,即我们应该始终声明将行值保持为Long
而不是Integer
的变量,否则你将收到Overflow
错误。
要查找工作表中的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
答案 1 :(得分:28)
关于找到最后一个使用过的单元格的正确方法,首先要确定使用的,然后选择合适的方法。我设想至少有三个含义:
已使用=非空白,即数据。
已使用=&#34; ...正在使用中,表示包含数据或格式的部分。&#34; As per official documentation,这是Excel在保存时使用的标准。另见this。 如果没有意识到这一点,则该标准可能产生意外的结果,但也可能有意地利用(不太经常,肯定),例如突出或打印特定区域,这些区域最终可能没有数据。 当然,作为保存工作簿时使用范围的标准是可取的,以免丢失部分工作。
已使用=&#34; ...正在使用中,表示包含数据或格式&#34;的部分或条件格式。 与2.相同,但也包括作为任何条件格式规则的目标的单元格。
如何查找上次使用的单元格取决于您想要的内容(您的标准)。
对于标准1,我建议您阅读this answer 。
请注意,UsedRange
被认为是不可靠的。我认为这是误导性的(即&#34;不公平&#34;到UsedRange
),因为UsedRange
根本不是要报告包含数据的最后一个单元格。所以在这种情况下不应该使用它,如答案所示。另见this comment。
对于标准2,UsedRange
是最可靠的选项,与为此用途设计的其他选项相比。它甚至不必保存工作簿以确保更新最后一个单元格。
Ctrl + End 将在保存之前转到错误的单元格
(“保存工作表之前不会重置最后一个单元格”,来自
http://msdn.microsoft.com/en-us/library/aa139976%28v=office.10%29.aspx。
这是一个旧的参考,但在这方面是有效的。)
对于标准3,我不知道任何内置方法。
标准2不考虑条件格式。可能有基于公式的格式化单元格,UsedRange
或 Ctrl + End 未检测到这些公式。
在图中,最后一个单元格是B3,因为格式化是明确应用于它的。单元格B6:D7具有从条件格式规则派生的格式,即使UsedRange
也未检测到。
考虑到这一点需要一些VBA编程。
关于您的具体问题: 背后的原因是什么?
您的代码使用您的E4系列中的第一个单元格:E48作为蹦床,使用End(xlDown)
跳跃。
&#34;错误&#34;如果您的范围中没有非空白单元格,则输出将获得,而不是第一个。然后,你在黑暗中跳跃,即在工作表下面 (你应该注意空白和空字符串之间的区别!)。
请注意:
如果您的范围包含非连续的非空白单元格,那么它也会产生错误的结果。
如果只有一个非空白单元格,但它不是第一个单元格,那么您的代码仍会提供正确的结果。
答案 2 :(得分:16)
我为确定最后一行,列和单元创建了这个一站式函数,无论是数据,格式化(分组/评论/隐藏)单元格还是条件格式。
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
答案 3 :(得分:9)
使用解决方案时要记住的一个重要注意事项......
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
答案 4 :(得分:8)
我想添加Siddarth Rout给出的答案,可以通过让Find返回一个Range对象而不是行号来跳过CountA调用,然后测试返回的Range对象以查看它是否为Nothing(空白工作表)。
另外,我的任何LastRow过程的版本都会为空白工作表返回零,然后我就知道它是空白的。
答案 5 :(得分:8)
我想知道没有人提到这一点,但获取最后一个使用过的单元格的最简单方法是:
Function GetLastCell(sh as Worksheet) As Range
GetLastCell = sh.Cells(1,1).SpecialCells(xlLastCell)
End Function
选择单元格 A1
后,这基本上会返回与 Ctrl + 结束相同的单元格。
提醒:Excel会跟踪工作表中使用过的最右下角的单元格。因此,例如,如果您在 B3 中输入内容,在 H8 中输入其他内容,然后再删除 H8 的内容,请按 Ctrl + 结束仍会将您带到 H8 单元格。上述功能具有相同的行为。
答案 6 :(得分:6)
由于最初的问题是关于找到最后一个单元格的问题,在这个答案中我会列出各种可以获得意外结果的方法;请参阅my answer to "How can I find last row that contains data in the Excel sheet with a macro?"了解解决这个问题。
我首先要扩展the answer by sancho.s和the comment by GlennFromIowa,添加更多细节:
[...]首先要决定使用什么。我看到至少6个含义。细胞有:
- 1)数据,即公式,可能导致空白值;
- 2)一个值,即非空白公式或常数;
- 3)格式化;
- 4)条件格式化;
- 5)与单元重叠的形状(包括注释);
- 6)参与表(列表对象)。
您想要测试哪种组合?某些(例如表格)可能更难以测试,有些可能很少(例如数据范围之外的形状),但其他可能会因情况而异(例如,具有空白值的公式)。
您可能需要考虑的其他事项:
考虑到这一点,让我们看看获取最后一个单元的常用方法&#34;会产生意想不到的结果:
.End(xlDown)
代码最容易破解(例如单个非空单元格或空白单元格) the answer by Siddharth Rout中解释的原因(搜索&#34; xlDown同样不可靠。&#34; )Count
(CountA
或Cells*.Count
)或.CurrentRegion
的任何解决方案也会因空白单元格或行存在而中断.End(xlUp)
的解决方案,就像CTRL + UP一样,查找数据(生成空白值的公式被视为&#34; 可见行中的数据&#34;)(因此在启用自动过滤时使用它可能会产生不正确的结果⚠️)。 你必须小心避免标准陷阱(有关详细信息,我将在此处再次引用the answer by Siddharth Rout,查找&#34;在列中查找最后一行&#34; 部分),例如对最后一行(Range("A65536").End(xlUp)
)进行硬编码,而不是依赖于sht.Rows.Count
。
.SpecialCells(xlLastCell)
相当于CTRL + END,返回&#34;使用范围&#34;的最底部和最右边的单元格,因此适用于依赖&#34;的所有警告都是如此。使用范围&#34;,也适用于此方法。此外,&#34;使用范围&#34;只有在保存工作簿和访问worksheet.UsedRange
时才会重置,因此xlLastCell
可能会产生陈旧的结果⚠带有未保存的修改(例如,在删除某些行之后)。请参阅nearby answer by dotNET。sht.UsedRange
(此处the answer by sancho.s详细介绍)认为数据和格式(虽然不是条件格式),重置&#34;使用的范围& #34;工作表,可能是你想要的也可能不是。注意一个常见的错误️是使用.UsedRange.Rows.Count
⚠️,它返回行数在使用的范围内,而不是最后一行编号(如果前几行为空,则它们将不同),有关详细信息,请参阅newguy's answer to How can I find last row that contains data in the Excel sheet with a macro?
.Find
允许您查找包含任何数据(包括公式)的最后一行或任何列中的非空值 。您可以选择是否对公式或值感兴趣,但问题是它重置了Excel的查找对话框️️⚠️中的默认值,这可能会让人非常困惑。你的用户。它也需要仔细使用,请参阅the answer by Siddharth Rout此处(&#34;在工作表中查找最后一行&#34; )Cells
&#39;在循环中通常比重新使用Excel函数慢(尽管仍然可以执行),但是让您准确指定要查找的内容。请参阅my solution基于UsedRange
和VBA数组,以查找给定列中包含数据的最后一个单元格 - 它处理隐藏的行,过滤器,空白,不修改查找默认值,并且性能非常高。< / LI>
无论你选择什么解决方案,都要小心
Long
代替Integer
来存储行号(以避免Overflow
行超过65k)和Dim ws As Worksheet ... ws.Range(...)
而不是Range(...)
).Value
(Variant
时)避免使用.Value <> ""
之类的隐式转换,因为如果单元格包含错误值,它们将失败。答案 7 :(得分:5)
sub last_filled_cell()
msgbox range("a65536").end(xlup).row
end sub
“这里a65536是列中的最后一个单元格,此代码在excel sti72003上测试”200
如果你正在使用它 “a1,048,576”
我的代码只是让初学者理解end(xlup)和其他相关命令可以做什么的概念
答案 8 :(得分:3)
然而,这个问题是在寻找使用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 + 输入以使其成为数组公式。
这将为您提供D列中最后使用过的单元格的地址。
答案 9 :(得分:2)
我一直在寻找一种模仿 CTRL + Shift + End 的方法,所以dotNET解决方案很棒,除了我的Excel 2010如果我想避免错误,我需要添加set
:
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
答案 10 :(得分:1)
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
答案 11 :(得分:1)
在过去3年多的时间里,这些是我用于查找每个已定义列(行)和行(列)的最后一行和最后一列的函数:
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的情况,这是获取列E
中最后一行的方法:
Debug.Print lastRow(columnToCheck:=Range("E4:E48").Column)
答案 12 :(得分:1)
这是我的两分钱。
恕我直言,隐藏行被排除数据的风险太大,以至于不能xlUp
被视为一站式答案。我同意这很简单,并且在大多数情况下都可以使用,但是存在冒低估最后一行而没有任何警告的风险。对于跳入Stack Overlow并寻求“确定方法”以捕获此值的某人,这可能会在某点产生 CATASTROPHIC 结果。
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
不会警告潜在的风险,他们可以继续为知道不知道自己的代码不能正常工作多久的人提供服务。
答案 13 :(得分:0)
按范围查找列或表列(ListObject)中的最后一行
找到最后一行需要:
这个提议的解决方案更通用,只需要范围,拼写错误的机会更少,而且很短(只需调用 MyLastRow
函数)。
Sub test()
Dim rng As Range
Dim Result As Long
Set rng = Worksheets(1).Range("D4")
Result = MyLastRow(rng)
End Sub
Function MyLastRow(FirstRow As Range) As Long
Dim WS As Worksheet
Dim TableName As String
Dim ColNumber As Long
Dim LastRow As Long
Dim FirstColumnTable As Long
Dim ColNumberTable As Long
Set WS = FirstRow.Worksheet
TableName = GetTableName(FirstRow)
ColNumber = FirstRow.Column
''If the table (ListObject) does not start in column "A" we need to calculate the
''first Column table and how many Columns from its beginning the Column is located.
If TableName <> vbNullString Then
FirstColumnTable = WS.ListObjects(TableName).ListColumns(1).Range.Column
ColNumberTable = ColNumber - FirstColumnTable + 1
End If
If TableName = vbNullString Then
LastRow = WS.Cells(WS.Rows.Count, ColNumber).End(xlUp).Row
Else
LastRow = WS.ListObjects(TableName).ListColumns(ColNumberTable).Range.Find( _
What:="*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
End If
MyLastRow = LastRow
End Function
''Get Table Name by Cell Range
Function GetTableName(CellRange As Range) As String
If CellRange.ListObject Is Nothing Then
GetTableName = vbNullString
Else
GetTableName = CellRange.ListObject.Name
End If
End Function
答案 14 :(得分:0)
我为最后一行创建了这个通用函数,无论范围类型如何。只需给它任何单元格引用,它就会返回最后一行。 无需知道范围特征,特别是如果您的范围有时是常规范围,有时是 ListObject。 在表上使用常规范围方法可能会返回错误的结果。 当然,您可以提前计划并每次都使用正确的方法,但如果您可以使用通用功能,何必费心呢?
<块引用>Sub RunMyLastRow()
Dim Result As Long
Result = MyLastRow(Worksheets(1).Range("A1"))
End Sub
Function MyLastRow(RefrenceRange As Range) As Long
Dim WS As Worksheet
Dim TableName As String
Dim ColNumber As Long
Dim LastRow As Long
Dim FirstColumnTable As Long
Dim ColNumberTable As Long
Set WS = RefrenceRange.Worksheet
TableName = GetTableName(RefrenceRange)
ColNumber = RefrenceRange.Column
''If the table (ListObject) does not start in column "A" we need to calculate the
''first Column table and how many Columns from its beginning the Column is located.
If TableName <> vbNullString Then
FirstColumnTable = WS.ListObjects(TableName).ListColumns(1).Range.Column
ColNumberTable = ColNumber - FirstColumnTable + 1
End If
If TableName = vbNullString Then
LastRow = WS.Cells(WS.Rows.Count, ColNumber).End(xlUp).Row
Else
LastRow = WS.ListObjects(TableName).ListColumns(ColNumberTable).Range.Find( _
What:="*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
End If
MyLastRow = LastRow
End Function
<块引用>
''Get Table Name by Cell Range
Function GetTableName(RefrenceRange As Range) As String
If RefrenceRange.ListObject Is Nothing Then
GetTableName = vbNullString
Else
GetTableName = RefrenceRange.ListObject.Name
End If
End Function