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

我想弄清楚如何在大范围的特定行上工作。 不过,使用rows属性创build的范围看起来并不像简单的范围那样。 检查以下代码,第一次定义variablesSpecificRow时,不可能select特定的单元格。 然而,一个奇怪的解决方法,重新定义范围,它工作正常。 你有一个想法,为什么以及如何用更优雅的方式来定义范围?

'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 

我找不到任何适当的文件,但这种观察到的行为似乎是非常合乎逻辑的。

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

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

我相信 ,为了实现逻辑上的可重复性,并避免创build不必要的实体(即像CellsCollectionRowsCollectionColumnsCollection这样的独立类),Excel开发人员想出了一个devise,其中每个Range实例都拥有一个private属性,告诉它在哪个它将自己计数的单位(所以一个范围可以是“一排的集合”,另一个范围可以是“一组单元”)。

当你通过Rows属性创build一个范围,当你通过Columns属性创build一个范围时(比如说) "columns" ,以及当你创build一个"cells"时,这个属性被设置为(比方说) "rows"范围以任何其他方式。

这可以让你做到这一点,而不是不必要的惊讶:

 For Each r In SomeRange.Rows ' will iterate through rows Next 
 For Each c In SomeRange.Columns ' will iterate through columns Next 

这里的RowsColumns都返回相同的types, Range ,指的是完全相同的表单区域,然而For Each循环通过第一种情况的Rows和第二种情况下的列进行迭代,就好像RowsColumns返回了两种不同的types( RowsCollectionColumnsCollection )。

这样devise是有道理的,因为For Each循环的重要属性是它不能为Range对象提供多个参数来获取下一个项目(单元格,行或列)。 实际上, For Each根本不能提供任何参数,只能问“请下一个”。

为了支持这一点, Range类必须能够给出下一个没有参数的“something”,即使range是二维的,需要两个坐标来获取“something”。 这就是为什么Range每个实例都要记住它将自己计数的单位。

该devise的一个副作用是,在只提供一个坐标的Range查找“某些东西”是完全正确的。 这正是For Each机制所要做的,我们直接跳到第i个项目。
当迭代(或索引到) Rows返回的范围时,我们将从上到下得到第i行; 对于由Columns返回的范围,我们从第i列开始,从左到右; 对于由Cells或其他方法返回的范围,我们将获得第i个单元格,从左上angular到右下angular,然后到底部。

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

 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靠近使用点而不是范围创build点:

 MsgBox SpecificRow.Cells(1).Address 

如果您将索引属性传递给不正确的参数,您应该期望奇怪的行为。 如你的代码所示, SourceRng.Rows(i)返回的Range实际上是正确的。 它只是没有做你认为正在做的事情。 一个RangeRows属性只是返回一个指向它所调用的完全相同的Range对象的指针。 你可以在types库定义中看到:

 HRESULT _stdcall Rows([out, retval] Range** RHS); 

请注意,它不需要任何参数。 返回的Range对象就是你提供索引的东西,而且你基于Item的默认属性(技术上它是_Default ,但是2是可以互换的)对它进行索引。 第一个参数(它是唯一一个与Rows(i)传递的是RowIndex ,所以Rows(i)Rows.Item(RowIndex:=i)完全一样,你可以在提供Row索引时popup的IntelliSense工具提示:

智能感知

虽然Excel在这个调用中处理索引的方式不同,因为为第二个参数提供任何值参数是运行时错误“1004”。 请注意,当您调用SpecificRow(1).Address时,类似的属性调用正在进行。 同样, Range的默认属性是Range.Item() ,所以你再次指定一行 – 而不是一列。 SpecificRow(1).AddressSpecificRow.Item(RowIndex:=1).Address

Excel中的奇怪现象似乎是由Range.Rows返回的Range.Rows “忘记”了它Rows调用的上下文中调用的事实,并且不再抑制列索引器。 记得从上面的typelib定义,返回的对象只是一个指向原始Range对象的指针。 这意味着SpecificRow(2)从狭义上下文中“泄漏”出来。

所有的事情考虑,我会说Excel的Rows实施有点黑客。 Application.Intersect(SpecificRow, SpecificRow)显然给你一个新的“硬”Range对象,但最后2行代码不是你应该考虑“正确”的行为。 同样,当您只向Range.Items提供第一个参数时,它将被声明为RowIndex

这里还是RowIndex

看来发生的事情是,Excel确定此时Range中只有一行,并假定传递的单个参数是一个ColumnIndex

正如@CallumDA所指出的那样,通过完全不依赖默认属性并显式提供所有需要的索引,您可以避免所有这些松鼠行为,即:

 Debug.Print SpecificRow.Item(1, 1).Address '...or... Debug.Print SpecificRow.Cells(1, 1).Address 

这是我将如何处理这些行内的行和特定的单元格。 唯一真正的区别是使用.Cells()

 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 

正如你所期望的那样