0

Short version of the question:

The code here

Dim rng As Range
Set rng = Selection
Set rng = rng.Columns(1)
For Each cl In rng
  cl.Select    ' <-- Break #2

gives me this in the immediate window when the selection is A1:B37

? rng.address(External:=True)
[Book2]Sheet1!$A$1:$A$37

? cl.Address(External:=True)
[Book2]Sheet1!$A$1:$A$37

Anyone can help me understanding why cl -> A1:A37 instead of cl -> A1? Note that I imagine rewriting code to get the intended results. But I would like to know what is the problem, and probably learn something new along. This is what the question is about.


Long version of the question (as originally posted):

I have a subroutine, which works on the selected (rectangular) range rng. The code of relevance here is shown below. It branches depending on the number ncols of columns of rng.

When ncols=1, it loops through each cell cl in rng, selecting cl and performing some actions. When the starting selection is A1:A37, this works ok, as shown by the output in the immediate window right after entering the loop at Break #1 (see code below)

? rng.address(External:=True)
[Book2]Sheet1!$A$1:$A$37

? cl.Address(External:=True)
[Book2]Sheet1!$A$1

When ncols<>1, I want to loop through each cell cl in the first column of rng, doing the same as before. Now when the starting selection is A1:B37, this does not work, as shown by the output in the immediate window at Break #2

? rng.address(External:=True)
[Book2]Sheet1!$A$1:$A$37

? cl.Address(External:=True)
[Book2]Sheet1!$A$1:$A$37

Anyone can help me understanding why here cl -> A1:A37 instead of cl -> A1 (as in Break #1)? Note that I imagine rewriting code to get the intended results. But I would like to know what is the problem, and probably learn something new along. This is what the question is about.

Dim rng As Range
Set rng = Selection
Dim ncols As Long
ncols = rng.Columns.Count
Dim cl As Range
' 1- If only one column is selected, ...
If (ncols = 1) Then
  For Each cl In rng
    cl.Select    ' <-- Break #1
    ...
  Next cl
' 2- If more than one column is selected, ...
Else
  Set rng = rng.Columns(1)
  For Each cl In rng
    cl.Select    ' <-- Break #2
    Dim rng2 As Range
    Set rng2 = Range(cl, cl.Offset(0, ncols - 1))
    rng2.Select
    ...
  Next cl
End If

1 Answers1

1

I have not had a chance to test your code yet but you may simply be suffering from lack of explicity: cl is a Range, so is a Column and a Row and an Area and any other sort of range-type object. You can use a range iterator like cl : For each cl in Rng.Rows or ...in rng.Columns, or in ...rng.Cells, etc.

In other words, while you may be expecting cl to be a cell range, that may not be the case unless you make it explicit, like:

For each cl in rng.Cells

Or, since you are defining it as a single-column, this would be equivalent:

For Each cl in rng.Rows

(technically, cl represents a row range in that rng, but since it's a single column range, each "row" is a single cell, too).

Your code can acutally be quite streamlined:

Sub f()

    Dim rng As Range
    Dim cl As Range
    Dim rng2 As Range

    Set rng = Range(Selection.Address).Resize(, 1)
    ncols = Range(Selection.Address).Columns.Count

    For Each cl In rng.Cells
      cl.Select    ' <-- Break #2

      If nCols > 1 Then
          Set rng2 = Range(cl, cl.Offset(0, ncols - 1))
          rng2.Select
          '...
      End If
    Next cl

End Sub
David Zemens
  • 53,033
  • 11
  • 81
  • 130
  • On the spot (as usual). I did not know there are different `Type`s of `Range`. Actually, the Excel help for the *Range.Cells Property* is confusing to me: "Returns a Range object that represents the cells in the specified range." It looks to me that it should return the same object as the caller... – sancho.s ReinstateMonicaCellio Oct 01 '14 at 17:14
  • How would one discern the object `rng` from `rng.Cells`, by testing (i.e., without inspecting the code)? – sancho.s ReinstateMonicaCellio Oct 01 '14 at 17:16
  • it's not necessary to discren a difference between `rng` and `rng.Cells`, but by including `.Cells` you are explicitly stating the type of iteration you want to implement. The two objects should be identical, in that `rng` and `rng.Cells` both represent the entire area. But by saying `For each cl in rng.Cells`, you're specifying *how* you want to iterate. – David Zemens Oct 01 '14 at 17:17
  • I understand that it is not necessary for my needs. But now I am curious (and it is perhaps worth another question) about what are the differences between the objects `rng` and `rng.Cells` (not in words, but in terms of specification of what methods/properties and results are available for each). There should be some, as VBA works differently on them. But I do not find the specification. I also want to know if one can test a `Range` object for which "subtype" it is. – sancho.s ReinstateMonicaCellio Oct 01 '14 at 17:23
  • 1
    I think you are making this entirely too complicated. `rng` is an object of type `Range`. All of those things that come *after* the dot are methods or properties of that object. Unless qualified otherwise, they will all return the *entire* range object. VBA does not "work differently" on these. But when you are using this in a loop, `rng.Cells` expressly stipulates that your `cl` range object is one of the iterable items in the `rng.Cells` collection, whereas if you do `rng.Rows` then `cl` will be one of the iterable *rows* in the `rng.Rows` collection, etc. – David Zemens Oct 01 '14 at 17:44
  • It would seem, from observation, that the default iterable item in a `Range` object is a `Column`, unless otherwise specified using a property like `.Rows`, `.Cells`, `.Columns`, etc. – David Zemens Oct 01 '14 at 17:49
  • But then your own comment suggests that the objects are different (even if they cover the same address; I am proposing that `rng.Address`=`rng2.Address` does not imply `rng`=`rng2`). Think about using `rng.Rows` as the argument for a `Sub` taking a `Range`, vs. using `rng`. The `Sub` might produce different outcomes, see [this question](http://stackoverflow.com/q/26147298/2707864). – sancho.s ReinstateMonicaCellio Oct 07 '14 at 05:31
  • 2
    The objects **are** different, and this can be observed if you do: `Debug.Print ObjPtr(rng) = ObjPtr(rng.Cells)`. With that in mind, I believe I have sufficiently answered your original question, which is: *can help me understanding why `cl` -> `A1:A371` instead of `cl` -> `A1?`*. What answer at this point will satisfy this question as "closed" and accepted answer? – David Zemens Oct 07 '14 at 14:23
  • On rereading, what was missing was documentation detailing the difference between the objects (which we agree are different). You talk about *cell* ranges and *row* ranges. I did not find anything explaining that. Thus, I think that the answer is an "experimental" hint to the difference, but not a specification explaining the difference. I started a bounty [here](http://stackoverflow.com/q/26147298/2707864). – sancho.s ReinstateMonicaCellio Dec 22 '14 at 06:32