在VBA中,Rows属性有一种奇怪的行为

时间:2017-01-03 15:31:52

标签: excel vba excel-vba

我试图弄清楚如何在大范围内的特定行上工作。但是,使用rows属性创建的范围似乎与简单范围的行为不同。检查以下代码,第一次定义变量SpecificRow时,无法选择特定的单元格。然而,通过重新定义范围的奇怪解决方法,它工作正常。您是否知道为什么以及如何以更优雅的方式定义范围?

'The following shows the weird behavior of Rows property
Dim SourceRng As Range
Dim SpecificRow As Range
Dim i As Long

i = 3

Set SourceRng = Range("A1:D20")
Set SpecificRow = SourceRng.Rows(i)

'This will show the address of the selected row ("A3:D3")
MsgBox SpecificRow.Address

'Unexplicable behavior of the range when trying to select a specific cell
'where it will instead consider the whole row (within the limits of SourceRng)
MsgBox SpecificRow(1).Address
MsgBox SpecificRow(2).Address

'This would send an error
'MsgBox SpecificRow(1, 1).Address

'Workaround
Set SpecificRow = Intersect(SpecificRow, SpecificRow)

'The following will select the same address than before
MsgBox SpecificRow.Address

'However, now it has a correct behavior when selecting a specific cell
MsgBox SpecificRow(1).Address
MsgBox SpecificRow(2).Address

3 个答案:

答案 0 :(得分:5)

如果您将索引属性传递给错误的参数,则应该会出现奇怪的行为。正如您的代码所示,SourceRng.Rows(i)返回的范围实际上是正确的。它只是没有做你认为它做的事情。 Rows的{​​{1}}属性只返回指向它所调用的完全相同的Range对象的指针。您可以在其类型库定义中看到:

Range

请注意,它不会采取任何参数。返回的HRESULT _stdcall Rows([out, retval] Range** RHS); 对象就是您为其提供索引的对象,您可以根据其Range的默认属性对其进行索引(从技术上讲,它是' s) Item,但2可互换)。第一个参数(您唯一使用_Default传递的参数)是Rows(i)。因此RowIndexRows(i)完全相同。您实际上可以在您提供Rows.Item(RowIndex:=i)索引时弹出的IntelliSense工具提示中 参见

IntelliSense

Excel会在此调用中以不同方式处理索引,因为为第二个参数提供任何值参数是运行时错误' 1004'。请注意,当您致电Row时,会进行类似的财产通话。同样,SpecificRow(1).Address的默认属性为Range,因此您需要再次指定一行 - 一列。 Range.Item()SpecificRow(1).Address完全相同。

Excel中的奇怪之处似乎是SpecificRow.Item(RowIndex:=1).Address"忘记"返回的Range。事实是它是在Range.Rows调用的上下文中调用的,并且不再抑制列索引器。请记住,从上面的typelib定义中返回的对象只是指向原始Rows对象的指针 back 。这意味着Range"泄漏"在狭隘的背景下。

考虑到所有事情,我说Excel SpecificRow(2)实现有点像黑客。 Rows显然正在给你一个新的" hard"范围对象,但最后两行代码是 您应该考虑的内容"更正"行为。同样,当您仅向Application.Intersect(SpecificRow, SpecificRow)提供第一个参数时,它会被声明为Range.Items

Still the RowIndex here too

出现会发生什么是Excel确定此时RowIndex中只有一行,并假设传递的单个参数是Range

正如@CallumDA所指出的那样,你可以完全依赖默认属性并明确提供你需要的所有索引,即:

,从而避免所有这些松鼠行为。
ColumnIndex

答案 1 :(得分:2)

这就是我如何处理这些行中的行和特定单元格。唯一真正的区别是使用Sub WorkingWithRows() Dim rng As Range, rngRow As Range Set rng = Sheet1.Range("A1:C3") For Each rngRow In rng.Rows Debug.Print rngRow.Cells(1, 1).Address Debug.Print rngRow.Cells(1, 2).Address Debug.Print rngRow.Cells(1, 3).Address Next rngRow End Sub

$A$1
$B$1
$C$1
$A$2
$B$2
$C$2
$A$3
$B$3
$C$3

返回:

{{1}}

正如您所料

答案 2 :(得分:1)

我找不到任何适当的文档,但这种观察到的行为实际上看起来非常符合逻辑。

Excel中的Range类有两个重要属性:

  • Range的单个实例足以表示工作表上的任何可能范围
  • 它是可迭代的(可以在For Each循环中使用)

我相信为了实现逻辑上的可迭代性而又避免创建不必要的实体(即CellsCollectionRowsCollectionColumnsCollection等单独的类, Excel开发人员想出了一个设计,其中Range的每个实例都拥有一个private属性,告诉它它将自己计算哪些单位(这样一个范围可以是“行的集合”)另一个范围可能是“一组细胞”。)

当您通过"rows"属性创建范围时,此属性设置为(例如)Rows,当您通过{{1}创建范围时(例如)"columns"当你以任何其他方式创建一个范围时,属性和(比如)Columns

这使您可以做到这一点而不会感到不必要的惊讶:

"cells"
For Each r In SomeRange.Rows
  ' will iterate through rows
Next

这里For Each c In SomeRange.Columns ' will iterate through columns Next Rows都返回相同的类型Columns,它指的是完全相同的工作表区域,但Range循环通过行中的行进行迭代第一个案例和第二个案例中的列,好像For EachRows返回了两种不同的类型(ColumnsRowsCollection)。

有意义的是它是以这种方式设计的,因为ColumnsCollection循环的重要属性是它不能为For Each对象提供多个参数以获取下一个项目(单元格,行或列)。事实上,Range根本无法提供任何参数,它只能询问“下一个请”。

为了支持这一点,For Each类必须能够提供下一个没有参数的“东西”,即使范围是二维的并且需要两个坐标来获取“某些东西”。这就是为什么Range的每个实例必须记住它将以什么单位计算自己。

该设计的一个副作用是,在仅提供一个坐标的Range中查找“某事”是完全正确的。这正是Range机制的作用,我们只是直接跳转到For Each项。
当迭代(或索引到)i返回的范围时,我们将从上到下获得Rows行;对于i返回的范围,我们从左到右得到Columns列;对于i或任何其他方法返回的范围,我们将获得Cells个单元格,从左上角到右上角再到底部。

这种设计的另一个副作用是可以以有意义的方式“走出”范围。也就是说,如果你有一个三个单元格的范围,并且你要求第四个单元格,你仍然可以得到它,它将是由范围的形状和它自己计算的单位决定的单元格:

i

因此,Dim r As Range Set r = Range("A1:C3") ' Contains 9 cells Debug.Print r.Cells(12).Address ' $C$4 - goes outside of the range but maintains its shape 的解决方法会将特定Set SpecificRow = Intersect(SpecificRow, SpecificRow)实例的内部计数模式从(例如)Range重置为(比方说)"rows"

你可以用

实现同样的目标
"cells"

但最好让Set SpecificRow = SpecificRow.Cells MsgBox SpecificRow(1).Address 接近使用点而不是范围创建点:

Cells